Some bugs fixed.
This commit is contained in:
parent
c4e9df5348
commit
1ab5476a8e
23 changed files with 796 additions and 309 deletions
99
CHANGELOG
99
CHANGELOG
|
|
@ -394,7 +394,9 @@ API Changes
|
||||||
over the sound family, and carries a gain of its own on Hypseus's 0 to
|
over the sound family, and carries a gain of its own on Hypseus's 0 to
|
||||||
128 scale: musicSetVolume moves no sound effect and soundSetVolume
|
128 scale: musicSetVolume moves no sound effect and soundSetVolume
|
||||||
moves no music. Music starts as loud as the sound effects, at whatever
|
moves no music. Music starts as loud as the sound effects, at whatever
|
||||||
--volume_nonvldp set. Ten more Hypseus names -- hypseusGetWidth,
|
--volume_nonvldp set. musicPlay answers the handle it was given, as
|
||||||
|
Hypseus's does, so a game can keep it to stop the piece, and musicStop
|
||||||
|
takes nil as none. Ten more Hypseus names -- hypseusGetWidth,
|
||||||
hypseusGetHeight, takeScreenshot, mouseHowManyReal and the six
|
hypseusGetHeight, takeScreenshot, mouseHowManyReal and the six
|
||||||
spriteAnim ones -- are aliases in Framework.singe.
|
spriteAnim ones -- are aliases in Framework.singe.
|
||||||
|
|
||||||
|
|
@ -1197,6 +1199,29 @@ New Features
|
||||||
overlay shows wherever the front layer draws nothing, and GUIs still go on
|
overlay shows wherever the front layer draws nothing, and GUIs still go on
|
||||||
top. LAYER_SCENE puts a node back.
|
top. LAYER_SCENE puts a node back.
|
||||||
|
|
||||||
|
- The menu opens with no games at all. It used to quit, which left a new
|
||||||
|
cabinet no way to reach Get Games; now it says how to sign in and fetch
|
||||||
|
some. Whatever Get Games downloads or removes shows in the list as soon as
|
||||||
|
the service tools close, where before it took a restart.
|
||||||
|
|
||||||
|
- A model that uses one picture for several of its maps, or across several
|
||||||
|
of its materials, sends it to the graphics card once rather than once a
|
||||||
|
map. A picture read both as colour and as data, such as one used for the
|
||||||
|
base colour and the occlusion, is sent twice, once each way. Models that
|
||||||
|
pack their maps together this way load faster and take less memory.
|
||||||
|
|
||||||
|
- Large terrain draws faster. A heightmap bigger than 64 by 64 cells is
|
||||||
|
drawn in squares of that size, and only the squares in view are drawn, or
|
||||||
|
cast shadows where the light can see them. Before, a 1025 by 1025 map was
|
||||||
|
drawn whole, a million vertices, whenever any corner of it was on screen.
|
||||||
|
Nothing changes for the script: it is still one mesh, and its physics and
|
||||||
|
terrainGetHeight work as before.
|
||||||
|
|
||||||
|
- Greyscale PNGs load with their true values. Every shade of an 8-bit grey
|
||||||
|
picture used to come out one step darker, so white was 254, and a 1-bit
|
||||||
|
black and white picture came out black and mid grey. Heightmaps, masks,
|
||||||
|
and grey sprites now read exactly what the file holds, as they did in 2.x.
|
||||||
|
|
||||||
- Sprites can be added, multiplied, faded and tinted. spriteSetBlend chooses
|
- Sprites can be added, multiplied, faded and tinted. spriteSetBlend chooses
|
||||||
SPRITE_ALPHA, SPRITE_ADD or SPRITE_MULTIPLY; spriteSetAlpha fades a sprite
|
SPRITE_ALPHA, SPRITE_ADD or SPRITE_MULTIPLY; spriteSetAlpha fades a sprite
|
||||||
from 255 to nothing; spriteSetTint multiplies its colour, so one white
|
from 255 to nothing; spriteSetTint multiplies its colour, so one white
|
||||||
|
|
@ -1550,6 +1575,22 @@ New Features
|
||||||
are rendered into .builddir/ by the build and by build-docs.sh alike,
|
are rendered into .builddir/ by the build and by build-docs.sh alike,
|
||||||
and the binary embeds the PDF from there.
|
and the binary embeds the PDF from there.
|
||||||
|
|
||||||
|
- Windows builds now carry the icon and version resource, with the version
|
||||||
|
number encoded correctly.
|
||||||
|
|
||||||
|
- The build no longer depends on lyx, upx, or xxd; embedded resources, the
|
||||||
|
version header, and the manual are generated by CMake. The manual is
|
||||||
|
embedded again and extracted as Singe/Manual.pdf.
|
||||||
|
|
||||||
|
- One command builds Singe from a clean checkout on a Debian based
|
||||||
|
system: ./build-all.sh <platform> <arch> installs the host packages,
|
||||||
|
then CMake fetches zig (the compiler for every target), the Pi's
|
||||||
|
platform packages and the macOS SDK into .builddir/toolchains and
|
||||||
|
builds every library and Singe itself. The results are a Linux binary
|
||||||
|
that runs on glibc 2.28 or newer, a Windows 10 or newer binary, a
|
||||||
|
64-bit Raspberry Pi OS (bullseye or newer) binary and a macOS 13 or
|
||||||
|
newer universal (Apple silicon and Intel) binary. The toolchains repository is no longer
|
||||||
|
needed. See INSTALL.
|
||||||
|
|
||||||
Fixes
|
Fixes
|
||||||
-----
|
-----
|
||||||
|
|
@ -1560,27 +1601,6 @@ Fixes
|
||||||
little-endian, swapping only the 64-bit kind, so every sample's low
|
little-endian, swapping only the 64-bit kind, so every sample's low
|
||||||
byte was taken for its colour. The vendored loader swaps both now.
|
byte was taken for its colour. The vendored loader swaps both now.
|
||||||
|
|
||||||
- musicPlay answers the handle it was given, as Hypseus's does. Its
|
|
||||||
games keep that (sndHandle = musicPlay(music)) to stop the piece, and
|
|
||||||
three of them died on the map
|
|
||||||
screen with "musicStop: Argument 1 must be a number" because Singe's
|
|
||||||
answered nothing. musicStop takes a nil handle as none.
|
|
||||||
|
|
||||||
- A machine with no enumerable mouse keeps its pointer. ManyMouse reads
|
|
||||||
/dev/input/event* directly where there is no X server, and those nodes
|
|
||||||
are root:input, so an account that was never added to the "input" group
|
|
||||||
enumerated nothing -- and Singe then switched off all mouse input,
|
|
||||||
including SDL's own pointer, which single-mouse mode is the only user
|
|
||||||
of. On a KMSDRM cabinet or handheld that left the mouse and the light
|
|
||||||
gun dead with only "No mice detected" to explain it. Only --manymouse
|
|
||||||
needs those devices now, and the message names the group.
|
|
||||||
|
|
||||||
- A lone .m2v played with --framefile now finds the .ogg beside it, as a
|
|
||||||
framefile's segments always have. A Daphne era elementary stream
|
|
||||||
carries no audio of its own, so such a .m2v played silently unless it was
|
|
||||||
listed in a framefile or --altaudio was given. Any other container
|
|
||||||
keeps using the audio inside it.
|
|
||||||
|
|
||||||
- --sindengun and --scalefactor are listed in --help and the manual;
|
- --sindengun and --scalefactor are listed in --help and the manual;
|
||||||
they were hidden because mouse positions ignored the Sinden border and
|
they were hidden because mouse positions ignored the Sinden border and
|
||||||
the overscan shrink. Positions now map into the picture actually
|
the overscan shrink. Positions now map into the picture actually
|
||||||
|
|
@ -1649,11 +1669,6 @@ Fixes
|
||||||
copy differs from the running build. Upgrading the binary used to keep
|
copy differs from the running build. Upgrading the binary used to keep
|
||||||
the old Framework.singe and Menu.singe forever.
|
the old Framework.singe and Menu.singe forever.
|
||||||
|
|
||||||
- Left and right move a third of the library, or a screenful, whichever is
|
|
||||||
smaller. A page in a library of nine games was ten, so every press
|
|
||||||
landed on the first game or the last and nothing between them could be
|
|
||||||
reached that way at all.
|
|
||||||
|
|
||||||
- Framefiles whose first line is a relative directory produced garbage
|
- Framefiles whose first line is a relative directory produced garbage
|
||||||
video paths (the directory string was freed before use); a lone "." now
|
video paths (the directory string was freed before use); a lone "." now
|
||||||
means the framefile's own directory.
|
means the framefile's own directory.
|
||||||
|
|
@ -1669,29 +1684,12 @@ Fixes
|
||||||
press in the script that follows. Confirming Exit in a game with
|
press in the script that follows. Confirming Exit in a game with
|
||||||
button 1 used to relaunch it from the menu.
|
button 1 used to relaunch it from the menu.
|
||||||
|
|
||||||
- Windows builds now carry the icon and version resource, with the version
|
|
||||||
number encoded correctly.
|
|
||||||
|
|
||||||
- The build no longer depends on lyx, upx, or xxd; embedded resources, the
|
|
||||||
version header, and the manual are generated by CMake. The manual is
|
|
||||||
embedded again and extracted as Singe/Manual.pdf.
|
|
||||||
|
|
||||||
- The licence Singe is under and the notices its libraries and assets
|
- The licence Singe is under and the notices its libraries and assets
|
||||||
require are embedded and extracted as Singe/COPYING and
|
require are embedded and extracted as Singe/COPYING and
|
||||||
Singe/LICENSES, the way the manual is. Nothing in the build copied
|
Singe/LICENSES, the way the manual is. Nothing in the build copied
|
||||||
them beside the binary, so a download carried the program without the
|
them beside the binary, so a download carried the program without the
|
||||||
terms it is offered under.
|
terms it is offered under.
|
||||||
|
|
||||||
- One command builds Singe from a clean checkout on a Debian based
|
|
||||||
system: ./build-all.sh <platform> <arch> installs the host packages,
|
|
||||||
then CMake fetches zig (the compiler for every target), the Pi's
|
|
||||||
platform packages and the macOS SDK into .builddir/toolchains and
|
|
||||||
builds every library and Singe itself. The results are a Linux binary
|
|
||||||
that runs on glibc 2.28 or newer, a Windows 10 or newer binary, a
|
|
||||||
64-bit Raspberry Pi OS (bullseye or newer) binary and a macOS 13 or
|
|
||||||
newer universal (Apple silicon and Intel) binary. The toolchains repository is no longer
|
|
||||||
needed. See INSTALL.
|
|
||||||
|
|
||||||
- On a plain video disc, discSearch, discSkipToFrame, discSkipForward,
|
- On a plain video disc, discSearch, discSkipToFrame, discSkipForward,
|
||||||
discSkipBackward, discStepForward and discStepBackward clamp at the
|
discSkipBackward, discStepForward and discStepBackward clamp at the
|
||||||
first and last frame instead of wrapping around the disc.
|
first and last frame instead of wrapping around the disc.
|
||||||
|
|
@ -1701,22 +1699,15 @@ Fixes
|
||||||
discGetFrame reports, so a skip in any segment after the first lands
|
discGetFrame reports, so a skip in any segment after the first lands
|
||||||
where it should.
|
where it should.
|
||||||
|
|
||||||
- Video index files are named <name>-<hash>.index, the hash from the
|
|
||||||
video's full name, so two videos with one base name in a game stop
|
|
||||||
rebuilding each other's index. Old <name>.index files are ignored and
|
|
||||||
can be deleted.
|
|
||||||
|
|
||||||
- --stretch hands the script mouse positions in the game's coordinates
|
- --stretch hands the script mouse positions in the game's coordinates
|
||||||
at any window size. --scalefactor with --sindengun shrinks the picture
|
at any window size. --scalefactor with --sindengun shrinks the picture
|
||||||
inside the Sinden border instead of being cancelled by it. A negative
|
inside the Sinden border instead of being cancelled by it. A negative
|
||||||
--audio track number is ignored. With no --datadir the controls.cfg
|
--audio track number is ignored. With no --datadir the controls.cfg
|
||||||
beside the script is read once, not twice.
|
beside the script is read once, not twice.
|
||||||
|
|
||||||
- Quitting with a stick past its dead zone no longer crashes; the
|
- Plugging in a gamepad no longer resets the positions mouseGetPosition
|
||||||
release reaches onInputReleased after onShutdown, before the state
|
reports, and a controls.cfg mapping that lists the same code twice fires
|
||||||
closes. Plugging in a gamepad no longer resets the positions
|
its switch once.
|
||||||
mouseGetPosition reports, and a controls.cfg mapping that lists the
|
|
||||||
same code twice fires its switch once.
|
|
||||||
|
|
||||||
SINGE 2.10
|
SINGE 2.10
|
||||||
==========
|
==========
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,30 @@ function compareTitles(a, b)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- Makes a game the selection, wrapping at either end of the list, and shows it.
|
-- What the page shows when there is nothing to choose: the way to get something.
|
||||||
|
MENU_EMPTY = {
|
||||||
|
EMPTY = true,
|
||||||
|
TITLE = "No games yet",
|
||||||
|
DESCRIPTION = "Press SERVICE for the service tools. Sign in under Online Account, then choose " ..
|
||||||
|
"what to fetch in Get Games. What you fetch is listed here as soon as you leave the tools.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Makes a game the selection, wrapping at either end of the list, and shows it. An empty library
|
||||||
|
-- has no selection, only MENU_EMPTY.
|
||||||
function gameSelect(index)
|
function gameSelect(index)
|
||||||
|
unloadGameAssets()
|
||||||
|
if GAME_COUNT == 0 then
|
||||||
|
GAME_SELECTED = nil
|
||||||
|
MENU_RENDER.showGame(MENU_EMPTY)
|
||||||
|
menuRefreshNotice()
|
||||||
|
return
|
||||||
|
end
|
||||||
if index < 1 then
|
if index < 1 then
|
||||||
index = GAME_COUNT
|
index = GAME_COUNT
|
||||||
elseif index > GAME_COUNT then
|
elseif index > GAME_COUNT then
|
||||||
index = 1
|
index = 1
|
||||||
end
|
end
|
||||||
unloadGameAssets()
|
|
||||||
GAME_SELECTED = index
|
GAME_SELECTED = index
|
||||||
loadGameAssets()
|
loadGameAssets()
|
||||||
end
|
end
|
||||||
|
|
@ -169,14 +185,19 @@ function onInputPressed(what)
|
||||||
-- The service tools take every switch while they are up, including the one that closes them.
|
-- The service tools take every switch while they are up, including the one that closes them.
|
||||||
if toolsActive() then
|
if toolsActive() then
|
||||||
toolsInput(what)
|
toolsInput(what)
|
||||||
|
if MENU_LIBRARY_CHANGED and not toolsActive() then
|
||||||
|
menuRescanGames()
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Are we displaying the grid background?
|
-- Are we displaying the grid background? With no games, SERVICE is all there is to press.
|
||||||
if menuActive() then
|
if menuActive() then
|
||||||
|
|
||||||
if what == SWITCH_SERVICE then
|
if what == SWITCH_SERVICE then
|
||||||
toolsBegin()
|
toolsBegin()
|
||||||
|
elseif GAME_COUNT == 0 then
|
||||||
|
return
|
||||||
elseif what == SWITCH_UP then
|
elseif what == SWITCH_UP then
|
||||||
gameSelect(GAME_SELECTED - 1)
|
gameSelect(GAME_SELECTED - 1)
|
||||||
elseif what == SWITCH_DOWN then
|
elseif what == SWITCH_DOWN then
|
||||||
|
|
@ -247,10 +268,6 @@ end
|
||||||
|
|
||||||
|
|
||||||
function onShutdown()
|
function onShutdown()
|
||||||
-- With no games found there is no renderer and nothing was loaded; quitting is all there is.
|
|
||||||
if MENU_RENDER == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
unloadGameAssets()
|
unloadGameAssets()
|
||||||
saveConfig(not SHUTDOWN_FROM_PUSH)
|
saveConfig(not SHUTDOWN_FROM_PUSH)
|
||||||
MENU_RENDER.finish()
|
MENU_RENDER.finish()
|
||||||
|
|
@ -352,43 +369,71 @@ local function loadGamesDat(source, container)
|
||||||
GAMES = {}
|
GAMES = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Search for games.dat files in subdirectories and inside .game databases.
|
-- Search for games.dat files in subdirectories and inside .game databases, starting the list over.
|
||||||
--
|
--
|
||||||
-- The extension is tested before the mode, and that order matters: the engine presents a packed
|
-- The extension is tested before the mode, and that order matters: the engine presents a packed
|
||||||
-- game through the VFS, so lfs.attributes reports a .game as a *directory*, not a file. Testing
|
-- game through the VFS, so lfs.attributes reports a .game as a *directory*, not a file. Testing
|
||||||
-- the mode first sent every container down the loose-directory branch, which loads its games.dat
|
-- the mode first sent every container down the loose-directory branch, which loads its games.dat
|
||||||
-- with no container -- and then every CABINET, MARQUEE and ATTRACT path in it named a file that
|
-- with no container -- and then every CABINET, MARQUEE and ATTRACT path in it named a file that
|
||||||
-- exists only inside the database. A packed library could not show a single piece of its art.
|
-- exists only inside the database. A packed library could not show a single piece of its art.
|
||||||
GAME_LIST = {}
|
--
|
||||||
GAME_COUNT = 0
|
-- What this machine holds, by the id and version each games.dat carries, is rebuilt with it.
|
||||||
for dir in lfs.dir(".") do
|
-- VERSION is stamped into a packed game by the service when it is published; a game without one
|
||||||
if dir ~= "." and dir ~= ".." then
|
-- is version 0. MENU_VERSIONS is the only record of installed versions anywhere: the service
|
||||||
local dirattr = lfs.attributes(dir)
|
-- keeps none.
|
||||||
if dir:sub(-5):lower() == ".game" then
|
function menuScanGames()
|
||||||
loadGamesDat(dir .. "/games.dat", dir)
|
GAME_LIST = {}
|
||||||
elseif dirattr and (dirattr.mode == "directory") then
|
GAME_COUNT = 0
|
||||||
for file in lfs.dir(dir .. "/.") do
|
MENU_VERSIONS = {}
|
||||||
if file == "games.dat" then
|
for dir in lfs.dir(".") do
|
||||||
loadGamesDat(dir .. "/games.dat", nil)
|
if dir ~= "." and dir ~= ".." then
|
||||||
|
local dirattr = lfs.attributes(dir)
|
||||||
|
if dir:sub(-5):lower() == ".game" then
|
||||||
|
loadGamesDat(dir .. "/games.dat", dir)
|
||||||
|
elseif dirattr and (dirattr.mode == "directory") then
|
||||||
|
for file in lfs.dir(dir .. "/.") do
|
||||||
|
if file == "games.dat" then
|
||||||
|
loadGamesDat(dir .. "/games.dat", nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
table.sort(GAME_LIST, compareTitles)
|
||||||
table.sort(GAME_LIST, compareTitles)
|
for _, game in ipairs(GAME_LIST) do
|
||||||
|
if game.GAME_ID then
|
||||||
-- What this machine holds, by the id and version each games.dat carries. VERSION is stamped
|
MENU_VERSIONS[game.GAME_ID] = math.floor(tonumber(game.VERSION) or 0)
|
||||||
-- into a packed game by the service when it is published; a game without one is version 0.
|
end
|
||||||
-- This table is the only record of installed versions anywhere: the service keeps none.
|
|
||||||
MENU_VERSIONS = {}
|
|
||||||
MENU_UPDATES = {}
|
|
||||||
for _, game in ipairs(GAME_LIST) do
|
|
||||||
if game.GAME_ID then
|
|
||||||
MENU_VERSIONS[game.GAME_ID] = math.floor(tonumber(game.VERSION) or 0)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- The Get Games tool added or removed a game: the list is read again once the tools close, and
|
||||||
|
-- the game that was selected stays selected when it is still here.
|
||||||
|
function menuRescanGames()
|
||||||
|
local was = GAME_SELECTED and GAME_LIST[GAME_SELECTED] or nil
|
||||||
|
local key = was and (was.GAME_ID or was.SCRIPT) or nil
|
||||||
|
local at = 1
|
||||||
|
|
||||||
|
MENU_LIBRARY_CHANGED = false
|
||||||
|
unloadGameAssets()
|
||||||
|
GAME_SELECTED = nil
|
||||||
|
menuScanGames()
|
||||||
|
for index, game in ipairs(GAME_LIST) do
|
||||||
|
if key and ((game.GAME_ID or game.SCRIPT) == key) then
|
||||||
|
at = index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
MENU_RENDER.buildList()
|
||||||
|
gameSelect(at)
|
||||||
|
menuCheckVersions()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
menuScanGames()
|
||||||
|
MENU_UPDATES = {}
|
||||||
|
|
||||||
|
|
||||||
-- Asks the service which of the games here have a newer version. No sign-in is needed to ask;
|
-- Asks the service which of the games here have a newer version. No sign-in is needed to ask;
|
||||||
-- the answer arrives whenever it arrives and the renderers pick it up. Called once at startup
|
-- the answer arrives whenever it arrives and the renderers pick it up. Called once at startup
|
||||||
-- and again whenever the Get Games tool opens.
|
-- and again whenever the Get Games tool opens.
|
||||||
|
|
@ -520,10 +565,8 @@ function menuCheckRelease(force)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
if GAME_COUNT == 0 then
|
-- An empty library still gets the menu: the service tools are how games are fetched.
|
||||||
debugPrint("No games found! Exiting.")
|
do
|
||||||
singeQuit()
|
|
||||||
else
|
|
||||||
overlaySetResolution(discGetWidth(), discGetHeight())
|
overlaySetResolution(discGetWidth(), discGetHeight())
|
||||||
|
|
||||||
-- No disc: the menu is laid out on the canvas, 720x480 unless --canvas says otherwise, and the
|
-- No disc: the menu is laid out on the canvas, 720x480 unless --canvas says otherwise, and the
|
||||||
|
|
@ -555,16 +598,14 @@ else
|
||||||
SHOW_INTRO = true
|
SHOW_INTRO = true
|
||||||
CONFIG_FILE = singeGetDataPath() .. "menu.dat"
|
CONFIG_FILE = singeGetDataPath() .. "menu.dat"
|
||||||
local confattr = lfs.attributes(CONFIG_FILE)
|
local confattr = lfs.attributes(CONFIG_FILE)
|
||||||
|
local wanted = 1
|
||||||
if confattr then
|
if confattr then
|
||||||
dofile(CONFIG_FILE)
|
dofile(CONFIG_FILE)
|
||||||
-- A damaged menu.dat must not take the menu down with it.
|
-- A damaged menu.dat must not take the menu down with it. What it asks for is held here
|
||||||
GAME_SELECTED = tonumber(GAME_SELECTED) or 1
|
-- until gameSelect places it: nothing is selected yet, so there is nothing to let go of.
|
||||||
if GAME_SELECTED > GAME_COUNT then
|
wanted = math.min(tonumber(GAME_SELECTED) or 1, GAME_COUNT)
|
||||||
GAME_SELECTED = GAME_COUNT
|
|
||||||
end
|
|
||||||
else
|
|
||||||
GAME_SELECTED = 1
|
|
||||||
end
|
end
|
||||||
|
GAME_SELECTED = nil
|
||||||
|
|
||||||
-- The renderer. A GUI document is drawn through the GPU device the 3D scene uses, so without
|
-- The renderer. A GUI document is drawn through the GPU device the 3D scene uses, so without
|
||||||
-- one there is no document to build and guiNew would end the script rather than answer false;
|
-- one there is no document to build and guiNew would end the script rather than answer false;
|
||||||
|
|
@ -588,5 +629,5 @@ else
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Prime the pump
|
-- Prime the pump
|
||||||
gameSelect(GAME_SELECTED)
|
gameSelect(wanted)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
-- backdropReady() the intro is over and the menu may take input
|
-- backdropReady() the intro is over and the menu may take input
|
||||||
-- layout() place the regions, from the overlay size
|
-- layout() place the regions, from the overlay size
|
||||||
-- buildList() the whole game list is known
|
-- buildList() the whole game list is known
|
||||||
-- showGame(game) this is the selection now
|
-- showGame(game) this is the selection now, or MENU_EMPTY when there are no games
|
||||||
-- hideGame() let go of the last selection
|
-- hideGame() let go of the last selection
|
||||||
-- pageSize() how many rows left and right should move by
|
-- pageSize() how many rows left and right should move by
|
||||||
-- frame() draw the game page
|
-- frame() draw the game page
|
||||||
|
|
@ -286,16 +286,27 @@ MENU_RENDER.notice = function(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- MENU_EMPTY, the page for an empty library, has no row, no art, and no facts: only its title and
|
||||||
|
-- what to do.
|
||||||
MENU_RENDER.showGame = function(game)
|
MENU_RENDER.showGame = function(game)
|
||||||
menuRow(GAME_SELECTED):SetClass("selected", true)
|
if game.EMPTY then
|
||||||
scrollPending = true
|
menuElement("poster").style.decorator = "none"
|
||||||
menuArt("poster", game.CABINET)
|
menuElement("marquee").style.decorator = "none"
|
||||||
menuArt("marquee", game.MARQUEE)
|
guiSetValue(GUI, DOCUMENT, "credits", "")
|
||||||
|
guiSetValue(GUI, DOCUMENT, "developer", "")
|
||||||
|
guiSetValue(GUI, DOCUMENT, "publisher", "")
|
||||||
|
guiSetValue(GUI, DOCUMENT, "port", "")
|
||||||
|
else
|
||||||
|
menuRow(GAME_SELECTED):SetClass("selected", true)
|
||||||
|
scrollPending = true
|
||||||
|
menuArt("poster", game.CABINET)
|
||||||
|
menuArt("marquee", game.MARQUEE)
|
||||||
|
guiSetValue(GUI, DOCUMENT, "credits", menuLabel("Year", game.YEAR) .. menuLabel("Genre", game.GENRE) .. menuLabel("Platform", game.PLATFORM))
|
||||||
|
guiSetValue(GUI, DOCUMENT, "developer", menuLabel("Developer", game.DEVELOPER))
|
||||||
|
guiSetValue(GUI, DOCUMENT, "publisher", menuLabel("Publisher", game.PUBLISHER))
|
||||||
|
guiSetValue(GUI, DOCUMENT, "port", menuLabel("Singe port", game.CREATOR) .. menuLabel("Source", game.SOURCE))
|
||||||
|
end
|
||||||
guiSetValue(GUI, DOCUMENT, "title", menuEscape(game.TITLE))
|
guiSetValue(GUI, DOCUMENT, "title", menuEscape(game.TITLE))
|
||||||
guiSetValue(GUI, DOCUMENT, "credits", menuLabel("Year", game.YEAR) .. menuLabel("Genre", game.GENRE) .. menuLabel("Platform", game.PLATFORM))
|
|
||||||
guiSetValue(GUI, DOCUMENT, "developer", menuLabel("Developer", game.DEVELOPER))
|
|
||||||
guiSetValue(GUI, DOCUMENT, "publisher", menuLabel("Publisher", game.PUBLISHER))
|
|
||||||
guiSetValue(GUI, DOCUMENT, "port", menuLabel("Singe port", game.CREATOR) .. menuLabel("Source", game.SOURCE))
|
|
||||||
guiSetValue(GUI, DOCUMENT, "description", menuEscape(game.DESCRIPTION))
|
guiSetValue(GUI, DOCUMENT, "description", menuEscape(game.DESCRIPTION))
|
||||||
menuElement("description").scroll_top = 0
|
menuElement("description").scroll_top = 0
|
||||||
detailScroll = 0
|
detailScroll = 0
|
||||||
|
|
|
||||||
|
|
@ -426,21 +426,25 @@ MENU_RENDER.notice = function(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- MENU_EMPTY, the page for an empty library, has no art and no facts: only what to do.
|
||||||
MENU_RENDER.showGame = function(game)
|
MENU_RENDER.showGame = function(game)
|
||||||
spriteCabinet = spriteLoad(menuArtPath(game.CABINET))
|
local textBox = field(game.DESCRIPTION)
|
||||||
spriteMarquee = spriteLoad(menuArtPath(game.MARQUEE))
|
|
||||||
fitSprite(spriteCabinet, cabinetW, cabinetH)
|
|
||||||
fitSprite(spriteMarquee, marqueeW, marqueeH)
|
|
||||||
|
|
||||||
-- The details, grouped the way the document renderer groups them: the short fields share a
|
if not game.EMPTY then
|
||||||
-- line, and the description comes last because it is the one that can run on. It used to come
|
spriteCabinet = spriteLoad(menuArtPath(game.CABINET))
|
||||||
-- first, which meant a long description pushed every fact about the game off the bottom.
|
spriteMarquee = spriteLoad(menuArtPath(game.MARQUEE))
|
||||||
local textBox = "Year: " .. field(game.YEAR) .. " Genre: " .. field(game.GENRE) ..
|
fitSprite(spriteCabinet, cabinetW, cabinetH)
|
||||||
" Platform: " .. field(game.PLATFORM) .. WRAP_BREAK ..
|
fitSprite(spriteMarquee, marqueeW, marqueeH)
|
||||||
"Developer: " .. field(game.DEVELOPER) .. WRAP_BREAK ..
|
-- The details, grouped the way the document renderer groups them: the short fields share a
|
||||||
"Publisher: " .. field(game.PUBLISHER) .. WRAP_BREAK ..
|
-- line, and the description comes last because it is the one that can run on. It used to
|
||||||
"Singe port: " .. field(game.CREATOR) .. " Source: " .. field(game.SOURCE) .. WRAP_BREAK ..
|
-- come first, which meant a long description pushed every fact about the game off the bottom.
|
||||||
WRAP_BREAK .. field(game.DESCRIPTION)
|
textBox = "Year: " .. field(game.YEAR) .. " Genre: " .. field(game.GENRE) ..
|
||||||
|
" Platform: " .. field(game.PLATFORM) .. WRAP_BREAK ..
|
||||||
|
"Developer: " .. field(game.DEVELOPER) .. WRAP_BREAK ..
|
||||||
|
"Publisher: " .. field(game.PUBLISHER) .. WRAP_BREAK ..
|
||||||
|
"Singe port: " .. field(game.CREATOR) .. " Source: " .. field(game.SOURCE) .. WRAP_BREAK ..
|
||||||
|
WRAP_BREAK .. textBox
|
||||||
|
end
|
||||||
-- fontToSprite bakes the colour in, so it is set before the sprites are made rather than
|
-- fontToSprite bakes the colour in, so it is set before the sprites are made rather than
|
||||||
-- before they are drawn.
|
-- before they are drawn.
|
||||||
-- Twice over: black first, drawn a couple of pixels down and across behind the white, because
|
-- Twice over: black first, drawn a couple of pixels down and across behind the white, because
|
||||||
|
|
@ -519,15 +523,15 @@ MENU_RENDER.frame = function()
|
||||||
local x = 0
|
local x = 0
|
||||||
local y = 0
|
local y = 0
|
||||||
|
|
||||||
-- Cabinet image
|
-- Cabinet and marquee images, which an empty library has none of
|
||||||
x = cabinetX + (cabinetW - spriteGetWidth(spriteCabinet)) * 0.5
|
if spriteCabinet then
|
||||||
y = cabinetY + (cabinetH - spriteGetHeight(spriteCabinet)) * 0.5
|
x = cabinetX + (cabinetW - spriteGetWidth(spriteCabinet)) * 0.5
|
||||||
spriteDraw(spriteCabinet, x, y)
|
y = cabinetY + (cabinetH - spriteGetHeight(spriteCabinet)) * 0.5
|
||||||
|
spriteDraw(spriteCabinet, x, y)
|
||||||
-- Marquee image
|
x = marqueeX + (marqueeW - spriteGetWidth(spriteMarquee)) * 0.5
|
||||||
x = marqueeX + (marqueeW - spriteGetWidth(spriteMarquee)) * 0.5
|
y = marqueeY + (marqueeH - spriteGetHeight(spriteMarquee)) * 0.5
|
||||||
y = marqueeY + (marqueeH - spriteGetHeight(spriteMarquee)) * 0.5
|
spriteDraw(spriteMarquee, x, y)
|
||||||
spriteDraw(spriteMarquee, x, y)
|
end
|
||||||
|
|
||||||
-- Attract mode video
|
-- Attract mode video
|
||||||
if VIDEO_ATTRACT then
|
if VIDEO_ATTRACT then
|
||||||
|
|
@ -536,9 +540,13 @@ MENU_RENDER.frame = function()
|
||||||
|
|
||||||
-- Which game this is, since there is no list to look at. It heads the details rather than
|
-- Which game this is, since there is no list to look at. It heads the details rather than
|
||||||
-- sitting over the cabinet artwork, where it was unreadable against whatever the picture was.
|
-- sitting over the cabinet artwork, where it was unreadable against whatever the picture was.
|
||||||
local title = string.format("%s (%d of %d)", field(GAME_LIST[GAME_SELECTED].TITLE), GAME_SELECTED, GAME_COUNT)
|
local title = MENU_EMPTY.TITLE
|
||||||
if menuUpdateFor(GAME_LIST[GAME_SELECTED]) then
|
|
||||||
title = title .. " [update]"
|
if GAME_SELECTED then
|
||||||
|
title = string.format("%s (%d of %d)", field(GAME_LIST[GAME_SELECTED].TITLE), GAME_SELECTED, GAME_COUNT)
|
||||||
|
if menuUpdateFor(GAME_LIST[GAME_SELECTED]) then
|
||||||
|
title = title .. " [update]"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
colorForeground(0, 0, 0, 255)
|
colorForeground(0, 0, 0, 255)
|
||||||
|
|
|
||||||
|
|
@ -1524,6 +1524,8 @@ function shopInstall(game, onProgress, onDone)
|
||||||
if MENU_UPDATES then
|
if MENU_UPDATES then
|
||||||
MENU_UPDATES[game.id] = nil
|
MENU_UPDATES[game.id] = nil
|
||||||
end
|
end
|
||||||
|
-- And the menu reads its list again once the tools close, so a new game shows up.
|
||||||
|
MENU_LIBRARY_CHANGED = true
|
||||||
onDone(true, game.title .. " is ready. " .. result.bytes .. " bytes.")
|
onDone(true, game.title .. " is ready. " .. result.bytes .. " bytes.")
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -1569,6 +1571,7 @@ local function shopRemove()
|
||||||
if MENU_VERSIONS and game.id then
|
if MENU_VERSIONS and game.id then
|
||||||
MENU_VERSIONS[game.id] = nil
|
MENU_VERSIONS[game.id] = nil
|
||||||
end
|
end
|
||||||
|
MENU_LIBRARY_CHANGED = true
|
||||||
end
|
end
|
||||||
shopShow()
|
shopShow()
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -961,6 +961,11 @@ function K.run(scenes)
|
||||||
if DEMO_STATS then
|
if DEMO_STATS then
|
||||||
K.stats(t, bar)
|
K.stats(t, bar)
|
||||||
end
|
end
|
||||||
|
-- The music's lead-in before the first bar belongs to no scene, so it stays black
|
||||||
|
-- rather than showing whatever 3D sky is set up behind the scenes.
|
||||||
|
if bar < 1 then
|
||||||
|
K.fill(0, 0, 0)
|
||||||
|
end
|
||||||
for i, scene in ipairs(scenes) do
|
for i, scene in ipairs(scenes) do
|
||||||
local on = bar >= scene.from and bar < scene.to
|
local on = bar >= scene.from and bar < scene.to
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,12 @@ local road = { track = {}, start = 0 }
|
||||||
-- The music's time at the end of the last frame, for "what happened since last frame".
|
-- The music's time at the end of the last frame, for "what happened since last frame".
|
||||||
local lastT = 0
|
local lastT = 0
|
||||||
|
|
||||||
local SCROLL_TEXT = "NEON COAST ... A SINGE DEMO ... EVERY BEAT, BAR, AND DRUM HIT HERE WAS READ OUT OF THE MUSIC ... THIS TUNNEL IS WORKED OUT IN LUA, EVERY PIXEL OF EVERY FRAME, AND THE KICK DRUM DRIVES IT ... GREETINGS TO ALL WHO STAY UP LATE MAKING THINGS SHINE ..."
|
local SCROLL_TEXT = "NEON COAST ... A SINGE DEMO ... EVERY BEAT, BAR, AND DRUM HIT HERE WAS READ OUT OF THE MUSIC ... THIS TUNNEL IS CODED IN LUA, EVERY PIXEL OF EVERY FRAME, AND THE KICK DRUM DRIVES IT ... GREETINGS TO ALL WHO STAY UP LATE MAKING THINGS SHINY ..."
|
||||||
local ROAD_TEXT = "THE ROAD IS DRAWN ONE SCANLINE AT A TIME, THE WAY THE ARCADE RACERS DID IT ... THE CAR IS A 3D MODEL THAT SINGE RENDERED INTO SPRITES ... THE CITY ON THE HORIZON IS REAL 3D, TURNING WITH EVERY BEND ..."
|
local ROAD_TEXT = "THE ROAD IS DRAWN ONE SCANLINE AT A TIME, THE WAY THE ARCADE RACERS DID IT ... THE CAR IS A 3D MODEL THAT SINGE RENDERED INTO SPRITES ... THE CITY ON THE HORIZON IS REAL 3D, TURNING WITH EVERY BEND ..."
|
||||||
local CHROME_TEXT = "CHROME, GLASS, AND BLOOM ... POINT LIGHTS AND FOG ... PARTICLES WITH TRAILS ... ALL OF IT REFLECTING A SKY PAINTED BY ARITHMETIC ..."
|
local CHROME_TEXT = "CHROME, GLASS, AND BLOOM ... POINT LIGHTS AND FOG ... PARTICLES WITH TRAILS ... ALL OF IT REFLECTING A SKY PAINTED BY ARITHMETIC ..."
|
||||||
local CIRCLE_TEXT = "A PLASMA WORKED OUT IN LUA, EVERY PIXEL, EVERY FRAME ... "
|
local CIRCLE_TEXT = "A PLASMA CODED IN LUA, EVERY PIXEL, EVERY FRAME ... "
|
||||||
local CRAWL_LINES = { "SINGE 3", "", "LUA 5.4 SCRIPTING", "LASERDISC AND VIDEO PLAYBACK", "3D SCENES WITH GLTF MODELS", "PHYSICS, CLOTH, AND RAGDOLLS", "GUIS, MIDI, AND A MICROPHONE", "WINDOWS, MACOS, LINUX, AND PI" }
|
local CRAWL_LINES = { "SINGE", "", "LUA SCRIPTING", "LASERDISC AND VIDEO PLAYBACK", "3D SCENES WITH GLTF MODELS", "PHYSICS, CLOTH, AND RAGDOLLS", "GUIS, MIDI, AND A MICROPHONE", "WINDOWS, MACOS, LINUX, AND PI" }
|
||||||
local FLAG_TEXT = "THIS FLAG IS A SOFT BODY, BLOWN BY THE KICK DRUM ... SINGE RECORDS ITS OWN VIDEO, PICTURE AND SOUND ... ONLINE HIGH SCORES ... A WHOLE GAME PACKED IN ONE FILE ... FROM DESKTOPS TO HANDHELDS ..."
|
local FLAG_TEXT = "THIS FLAG IS A SOFT BODY, BLOWN BY THE KICK DRUM ... ONLINE HIGH SCORES ... A WHOLE GAME PACKED IN ONE FILE ... FROM DESKTOPS TO HANDHELDS ..."
|
||||||
local QUESTION = { "ARE", "YOU", "READY?" }
|
local QUESTION = { "ARE", "YOU", "READY?" }
|
||||||
local ENGINE_TEXT = "G A M E E N G I N E"
|
local ENGINE_TEXT = "G A M E E N G I N E"
|
||||||
-- Each firework's colour as it bursts, and the colour it fades into as it falls.
|
-- Each firework's colour as it bursts, and the colour it fades into as it falls.
|
||||||
|
|
@ -94,6 +94,10 @@ local FIREWORK_COLOURS = {
|
||||||
{ 170, 120, 255, 255, 60, 200 }, -- Lilac to magenta
|
{ 170, 120, 255, 255, 60, 200 }, -- Lilac to magenta
|
||||||
}
|
}
|
||||||
local REWIND_SNOW = 170 -- How strongly the snow shows over a rewind
|
local REWIND_SNOW = 170 -- How strongly the snow shows over a rewind
|
||||||
|
local FLAG_GUST_ALONG = 30 -- A kick's gust on the flag: how hard it throws the cloth out along the pole,
|
||||||
|
local FLAG_GUST_ACROSS = 60 -- how hard it pushes its face, the other way from the last kick so it flaps,
|
||||||
|
local FLAG_GUST_DECAY = 8 -- and how fast it dies away (per second, as K.decay takes it)
|
||||||
|
local FLAG_SWAY_DRUMS = 0.3 -- How much of the breeze's own sway is left once the drums are back
|
||||||
-- The city flight's rewinds, in bars from its start: from, to, and how many times faster than it
|
-- The city flight's rewinds, in bars from its start: from, to, and how many times faster than it
|
||||||
-- played. Between them it plays.
|
-- played. Between them it plays.
|
||||||
local CITY_REWINDS = { { 2, 3, 1.5 }, { 5, 6, 1.5 }, { 8, 10, 3 } }
|
local CITY_REWINDS = { { 2, 3, 1.5 }, { 5, 6, 1.5 }, { 8, 10, 3 } }
|
||||||
|
|
@ -1192,7 +1196,7 @@ local SCENES = {
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
-- Bars 69 to 76: the breakdown, then the kick's return. A flag on the night grid, waving in a
|
-- Bars 69 to 76: the breakdown, then the kick's return. A flag on the night grid, waving in a
|
||||||
-- wind that gusts on every kick once the drums are back, under a zooming scroller.
|
-- wind that gusts hard on every kick once the drums are back, under a zooming scroller.
|
||||||
{
|
{
|
||||||
from = 69, to = 77,
|
from = 69, to = 77,
|
||||||
enter = function(t)
|
enter = function(t)
|
||||||
|
|
@ -1209,12 +1213,19 @@ local SCENES = {
|
||||||
nodeSetPosition(world.floor, 0, 0, (t * 2) % GRID_CELL)
|
nodeSetPosition(world.floor, 0, 0, (t * 2) % GRID_CELL)
|
||||||
-- The wind is the cloth's gravity tipped sideways: a steady breeze along the flag, and a push
|
-- The wind is the cloth's gravity tipped sideways: a steady breeze along the flag, and a push
|
||||||
-- across its face that swings back and forth several times a second, which the soft cloth
|
-- across its face that swings back and forth several times a second, which the soft cloth
|
||||||
-- turns into ripples running out to the free end.
|
-- turns into ripples running out to the free end. Once the drums are back the sway calms
|
||||||
local gust = (bar >= 73) and K.decay("kicks", t, 4) or 0
|
-- and each kick is a gust that throws the cloth out along the pole and pushes its face,
|
||||||
|
-- one way and then the other, so it flaps on the beat. The breakdown before bar 73 has no
|
||||||
|
-- kick drum (its low end is a thirteenth of the bars either side), so the weak hits the
|
||||||
|
-- kick list holds there are left alone.
|
||||||
|
local drums = (bar >= 73)
|
||||||
|
local gust = drums and K.decay("kicks", t, FLAG_GUST_DECAY) or 0
|
||||||
|
local sway = drums and FLAG_SWAY_DRUMS or 1
|
||||||
|
local side = (K.hitCount("kicks", 0, t) % 2 == 1) and -1 or 1
|
||||||
|
|
||||||
physicsSetGravity(20 + 5 * math.sin(t * 2.1) + 20 * gust, -9.81 + 4 * math.sin(t * 9.1), 7 * math.sin(t * 7.3) + 5 * math.sin(t * 11.9 + 1))
|
physicsSetGravity(20 + 5 * math.sin(t * 2.1) + FLAG_GUST_ALONG * gust, -9.81 + 4 * math.sin(t * 9.1), sway * (7 * math.sin(t * 7.3) + 5 * math.sin(t * 11.9 + 1)) + side * FLAG_GUST_ACROSS * gust)
|
||||||
K.scroller(t, art.scrollFont, FLAG_TEXT, H - 80, 0, K.barTime(69), K.barTime(77), "zoom")
|
K.scroller(t, art.scrollFont, FLAG_TEXT, H - 80, 0, K.barTime(69), K.barTime(77), "zoom")
|
||||||
gridPulse(t, 0.12 + ((bar >= 73) and 0.2 or 0))
|
gridPulse(t, 0.12 + (drums and 0.2 or 0))
|
||||||
local fadeIn = 1 - K.smooth(69, 69.5, bar)
|
local fadeIn = 1 - K.smooth(69, 69.5, bar)
|
||||||
|
|
||||||
if fadeIn > 0 then
|
if fadeIn > 0 then
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,10 @@ game wears an `update` mark in the list, the line under its details names the
|
||||||
version, and button 3 on that page fetches it, so a cabinet need not open a
|
version, and button 3 on that page fetches it, so a cabinet need not open a
|
||||||
service tool to stay current. Nothing about this machine is kept anywhere: the
|
service tool to stay current. Nothing about this machine is kept anywhere: the
|
||||||
menu reads the version out of each game's own `games.dat` and asks about its own
|
menu reads the version out of each game's own `games.dat` and asks about its own
|
||||||
list, so a game copied in by hand is known as well as one downloaded here. A list longer than the screen scrolls with the selection rather than running
|
list, so a game copied in by hand is known as well as one downloaded here. A
|
||||||
|
cabinet with no games at all still opens the menu, which says how to fetch some,
|
||||||
|
and whatever Get Games downloads or removes is in the list as soon as the service
|
||||||
|
tools close. A list longer than the screen scrolls with the selection rather than running
|
||||||
off the bottom, with `...` showing there is more either way. A game that stops being offered keeps working and is shown as "installed,
|
off the bottom, with `...` showing there is more either way. A game that stops being offered keeps working and is shown as "installed,
|
||||||
no longer offered": all that stops is being given an update, because a player
|
no longer offered": all that stops is being given an update, because a player
|
||||||
who has a game has it. A download is checked against the digest the catalogue published before it
|
who has a game has it. A download is checked against the digest the catalogue published before it
|
||||||
|
|
|
||||||
|
|
@ -684,7 +684,7 @@ wherever a texture can: composited over the overlay with
|
||||||
the same options page works flat on the screen and on the monitor of a
|
the same options page works flat on the screen and on the monitor of a
|
||||||
modeled arcade cabinet. A GUI needs the GPU device the 3D scene uses, so it
|
modeled arcade cabinet. A GUI needs the GPU device the 3D scene uses, so it
|
||||||
is unavailable on a machine without one (see <<sceneperformance,Performance
|
is unavailable on a machine without one (see <<sceneperformance,Performance
|
||||||
and Requirements>>); `guiNew` raises an error rather than answering false, so
|
and Requirements>>); `guiNew` ends the script rather than answering false, so
|
||||||
ask <<singehasgpu,singeHasGpu>> first if your game can do without. The bundled
|
ask <<singehasgpu,singeHasGpu>> first if your game can do without. The bundled
|
||||||
menu does exactly that, and draws itself into the overlay instead when the
|
menu does exactly that, and draws itself into the overlay instead when the
|
||||||
answer is no (see xref:Manual.adoc#menurenderers[The two renderers]). Every call named here has an entry in the
|
answer is no (see xref:Manual.adoc#menurenderers[The two renderers]). Every call named here has an entry in the
|
||||||
|
|
@ -2776,7 +2776,7 @@ morph target weights are all driven. See the Animation part of
|
||||||
seconds = animationGetTime(node)
|
seconds = animationGetTime(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
The current time into the base layer's clip, playing or paused, in the clip's own seconds (a `speed` of `2` moves it twice as fast). A looping clip wraps back to `0` at its end; a non-looping one stops at its length. Nothing played yet gives `0`. Clips on the upper layers are not reported. A node that is not an instance root raises an error.
|
The current time into the base layer's clip, playing or paused, in the clip's own seconds (a `speed` of `2` moves it twice as fast). A looping clip wraps back to `0` at its end; a non-looping one stops at its length. Nothing played yet gives `0`. Clips on the upper layers are not reported. A node that is not an instance root ends the script.
|
||||||
|
|
||||||
*Returns:* Seconds as a number.
|
*Returns:* Seconds as a number.
|
||||||
|
|
||||||
|
|
@ -2805,7 +2805,7 @@ end
|
||||||
playing = animationIsPlaying(node)
|
playing = animationIsPlaying(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Whether the base layer's clip is advancing on this instance. A non-looping clip stops playing when it reaches its end, and `animationPause` and `animationStop` both make this `false`; a looping clip stays `true` until stopped. Unlike the other calls here, a node that is not an instance root does not raise an error but answers `false`.
|
Whether the base layer's clip is advancing on this instance. A non-looping clip stops playing when it reaches its end, and `animationPause` and `animationStop` both make this `false`; a looping clip stays `true` until stopped. Unlike the other calls here, a node that is not an instance root does not end the script but answers `false`.
|
||||||
|
|
||||||
*Returns:* Boolean.
|
*Returns:* Boolean.
|
||||||
|
|
||||||
|
|
@ -2833,7 +2833,7 @@ end
|
||||||
animationPause(node)
|
animationPause(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Freezes the base layer's clip at its current time; the nodes hold that pose until `animationResume` continues it or another clip is played. Clips on the upper layers keep running. Pausing an instance that has nothing playing is harmless. A node that is not an instance root raises an error.
|
Freezes the base layer's clip at its current time; the nodes hold that pose until `animationResume` continues it or another clip is played. Clips on the upper layers keep running. Pausing an instance that has nothing playing is harmless. A node that is not an instance root ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<animationresume,animationResume>>, <<animationsettime,animationSetTime>>
|
*See also:* <<animationresume,animationResume>>, <<animationsettime,animationSetTime>>
|
||||||
|
|
@ -2861,7 +2861,7 @@ animationPlay(node, nameOrIndex, loop, speed)
|
||||||
animationPlay(node, nameOrIndex, loop, speed, fade)
|
animationPlay(node, nameOrIndex, loop, speed, fade)
|
||||||
----
|
----
|
||||||
|
|
||||||
Starts one of the model's clips from its beginning on the base layer of this instance, replacing whatever was playing there; the pose updates at once. With a `fade` the old clip keeps running while it fades out over that many seconds, and when nothing was playing the fade starts from the pose the nodes are in now (a ragdoll that was just switched off, a scripted pose), with nodes the new clip does not animate easing back to the model's rest pose. A node that is not an instance root, a name the model does not have, or a number outside `1` to the clip count raises an error.
|
Starts one of the model's clips from its beginning on the base layer of this instance, replacing whatever was playing there; the pose updates at once. With a `fade` the old clip keeps running while it fades out over that many seconds, and when nothing was playing the fade starts from the pose the nodes are in now (a ragdoll that was just switched off, a scripted pose), with nodes the new clip does not animate easing back to the model's rest pose. A node that is not an instance root, a name the model does not have, or a number outside `1` to the clip count ends the script.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -2898,7 +2898,7 @@ animationPlayLayer(node, layer, nameOrIndex, loop, speed)
|
||||||
animationPlayLayer(node, layer, nameOrIndex, loop, speed, fade)
|
animationPlayLayer(node, layer, nameOrIndex, loop, speed, fade)
|
||||||
----
|
----
|
||||||
|
|
||||||
Plays a clip on one of the four layers above the base, blended over whatever is beneath it by the layer's weight (`animationSetLayerWeight`, default `1`) and limited to part of the skeleton when a mask is set (`animationSetLayerMask`). Higher layers blend over lower ones; each layer runs its own clip, time and loop independently of the base. `nameOrIndex`, `loop`, `speed` and `fade` mean what they do in `animationPlay`, with one difference: a `fade` on a layer that was empty ramps the layer's weight in from `0` instead of crossfading from a pose. A layer outside `1` to `4`, a node that is not an instance root, or a clip the model does not have raises an error.
|
Plays a clip on one of the four layers above the base, blended over whatever is beneath it by the layer's weight (`animationSetLayerWeight`, default `1`) and limited to part of the skeleton when a mask is set (`animationSetLayerMask`). Higher layers blend over lower ones; each layer runs its own clip, time and loop independently of the base. `nameOrIndex`, `loop`, `speed` and `fade` mean what they do in `animationPlay`, with one difference: a `fade` on a layer that was empty ramps the layer's weight in from `0` instead of crossfading from a pose. A layer outside `1` to `4`, a node that is not an instance root, or a clip the model does not have ends the script.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -2934,7 +2934,7 @@ end
|
||||||
animationResume(node)
|
animationResume(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Continues the base layer's clip from where `animationPause` froze it, or from wherever `animationSetTime` moved it meanwhile. Resuming a clip that is already playing changes nothing. An instance with no clip on its base layer (never played, or stopped) raises an error, as does a node that is not an instance root.
|
Continues the base layer's clip from where `animationPause` froze it, or from wherever `animationSetTime` moved it meanwhile. Resuming a clip that is already playing changes nothing. An instance with no clip on its base layer (never played, or stopped) ends the script, as does a node that is not an instance root.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<animationpause,animationPause>>, <<animationsettime,animationSetTime>>
|
*See also:* <<animationpause,animationPause>>, <<animationsettime,animationSetTime>>
|
||||||
|
|
@ -2959,7 +2959,7 @@ animationSetLayerMask(node, layer, maskNode)
|
||||||
animationSetLayerMask(node, layer)
|
animationSetLayerMask(node, layer)
|
||||||
----
|
----
|
||||||
|
|
||||||
Limits a layer to `maskNode` and everything under it, so the clip on that layer moves only that part of the model and the rest keeps following the layers beneath. `maskNode` must be one of the instance's own nodes, found with `nodeFind(name, node)`; the instance root itself does not count. Leaving `maskNode` out, or passing `nil`, lifts the limit. The mask stays on the layer across later `animationPlayLayer` calls. A layer outside `1` to `4` raises an error, and so does a node that is not an instance root or a `maskNode` that is not part of that instance.
|
Limits a layer to `maskNode` and everything under it, so the clip on that layer moves only that part of the model and the rest keeps following the layers beneath. `maskNode` must be one of the instance's own nodes, found with `nodeFind(name, node)`; the instance root itself does not count. Leaving `maskNode` out, or passing `nil`, lifts the limit. The mask stays on the layer across later `animationPlayLayer` calls. A layer outside `1` to `4` ends the script, and so does a node that is not an instance root or a `maskNode` that is not part of that instance.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -2992,7 +2992,7 @@ end
|
||||||
animationSetLayerWeight(node, layer, weight)
|
animationSetLayerWeight(node, layer, weight)
|
||||||
----
|
----
|
||||||
|
|
||||||
How much a layer's clip shows over what is beneath it, `0` (not at all) to `1` (fully, the default); values outside that range are clamped. The weight belongs to the layer, not the clip, so it survives `animationPlayLayer` and `animationStop` on that layer. Change it a little every frame to fade a layer in or out by hand, or let a `fade` on `animationPlayLayer` do it once. A layer outside `1` to `4` or a node that is not an instance root raises an error.
|
How much a layer's clip shows over what is beneath it, `0` (not at all) to `1` (fully, the default); values outside that range are clamped. The weight belongs to the layer, not the clip, so it survives `animationPlayLayer` and `animationStop` on that layer. Change it a little every frame to fade a layer in or out by hand, or let a `fade` on `animationPlayLayer` do it once. A layer outside `1` to `4` or a node that is not an instance root ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<animationplaylayer,animationPlayLayer>>, <<animationsetlayermask,animationSetLayerMask>>
|
*See also:* <<animationplaylayer,animationPlayLayer>>, <<animationsetlayermask,animationSetLayerMask>>
|
||||||
|
|
@ -3015,7 +3015,7 @@ end
|
||||||
animationSetTime(node, seconds)
|
animationSetTime(node, seconds)
|
||||||
----
|
----
|
||||||
|
|
||||||
Jumps the base layer's clip to a time in its own seconds; negative values become `0`. The pose updates at once whether or not the clip is playing, so a paused instance can be scrubbed to an exact frame, and a playing one continues from the new time. Any crossfade in progress on the base layer is cut short. An instance with no clip on its base layer raises an error, as does a node that is not an instance root.
|
Jumps the base layer's clip to a time in its own seconds; negative values become `0`. The pose updates at once whether or not the clip is playing, so a paused instance can be scrubbed to an exact frame, and a playing one continues from the new time. Any crossfade in progress on the base layer is cut short. An instance with no clip on its base layer ends the script, as does a node that is not an instance root.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<animationgettime,animationGetTime>>, <<animationpause,animationPause>>, <<animationresume,animationResume>>
|
*See also:* <<animationgettime,animationGetTime>>, <<animationpause,animationPause>>, <<animationresume,animationResume>>
|
||||||
|
|
@ -3041,7 +3041,7 @@ animationStop(node)
|
||||||
animationStop(node, layer)
|
animationStop(node, layer)
|
||||||
----
|
----
|
||||||
|
|
||||||
Stops and forgets every clip on the instance, or only the clip on one layer above the base (`1` to `4`); the base layer alone cannot be stopped without the layers above it. The nodes keep their last pose, which a following `animationPlay` with a `fade` eases out of. After stopping everything, `animationResume` and `animationSetTime` raise an error until something is played again. Layer weights and masks are kept. A layer outside `1` to `4` or a node that is not an instance root raises an error.
|
Stops and forgets every clip on the instance, or only the clip on one layer above the base (`1` to `4`); the base layer alone cannot be stopped without the layers above it. The nodes keep their last pose, which a following `animationPlay` with a `fade` eases out of. After stopping everything, `animationResume` and `animationSetTime` end the script until something is played again. Layer weights and masks are kept. A layer outside `1` to `4` or a node that is not an instance root ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<animationplay,animationPlay>>, <<animationplaylayer,animationPlayLayer>>, <<animationpause,animationPause>>
|
*See also:* <<animationplay,animationPlay>>, <<animationplaylayer,animationPlayLayer>>, <<animationpause,animationPause>>
|
||||||
|
|
@ -3281,7 +3281,7 @@ argument aborts the script.
|
||||||
[#body]
|
[#body]
|
||||||
=== Body
|
=== Body
|
||||||
|
|
||||||
A body gives a scene node a place in the physics world. There is one body per node, addressed by the node handle: a static body stays where the node was when the body was made, a kinematic body follows its node each step and pushes without being pushed, and a dynamic body is moved by the simulation and drives its node. Sizes, positions and velocities are in world units (meters in a 3D scene, overlay pixels in a 2D world), masses in kilograms, and time in seconds. Every call here raises an error when the node has no body; a call that only makes sense for one kind of body (a force on a static body, say) is ignored on the others, except `bodySetMass`, which raises. See <<physics,Physics>> in Game Development for how bodies, nodes and the frame fit together.
|
A body gives a scene node a place in the physics world. There is one body per node, addressed by the node handle: a static body stays where the node was when the body was made, a kinematic body follows its node each step and pushes without being pushed, and a dynamic body is moved by the simulation and drives its node. Sizes, positions and velocities are in world units (meters in a 3D scene, overlay pixels in a 2D world), masses in kilograms, and time in seconds. Every call here ends the script when the node has no body; a call that only makes sense for one kind of body (a force on a static body, say) is ignored on the others, except `bodySetMass`, which raises. See <<physics,Physics>> in Game Development for how bodies, nodes and the frame fit together.
|
||||||
|
|
||||||
[#bodynew]
|
[#bodynew]
|
||||||
==== bodyNew
|
==== bodyNew
|
||||||
|
|
@ -3294,7 +3294,7 @@ bodyNew(node, type, shape, a, b)
|
||||||
bodyNew(node, type, shape, a, b, c)
|
bodyNew(node, type, shape, a, b, c)
|
||||||
----
|
----
|
||||||
|
|
||||||
Gives the node a body of the given type, placed where the node is in the world right now (its parents included), with a collision shape sized by `a`, `b` and `c` in world units and scaled by the node's world scale. A second call on the same node replaces the first, and a node that had a player loses it. A dynamic body's mass comes from its shape's volume at the density of water, so set `bodySetMass` on anything meant to feel light. Changing the node's scale later does not resize the body. Raises an error for an unknown type or shape, when physics is not available on this machine, when the world already holds `4096` bodies, or when the shape cannot be built (a dynamic `SHAPE_MESH`, or a hull with no mesh under the node).
|
Gives the node a body of the given type, placed where the node is in the world right now (its parents included), with a collision shape sized by `a`, `b` and `c` in world units and scaled by the node's world scale. A second call on the same node replaces the first, and a node that had a player loses it. A dynamic body's mass comes from its shape's volume at the density of water, so set `bodySetMass` on anything meant to feel light. Changing the node's scale later does not resize the body. Ends the script for an unknown type or shape, when physics is not available on this machine, when the world already holds `4096` bodies, or when the shape cannot be built (a dynamic `SHAPE_MESH`, or a hull with no mesh under the node).
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -3395,7 +3395,7 @@ end
|
||||||
bodyDelete(node)
|
bodyDelete(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Removes the body from the world; the node and its mesh stay where they are. Every joint attached to the body, and any vehicle built on it, goes with it. Deleting the node removes its body too, so this is only needed to keep a node without its body. Raises an error when the node has no body.
|
Removes the body from the world; the node and its mesh stay where they are. Every joint attached to the body, and any vehicle built on it, goes with it. Deleting the node removes its body too, so this is only needed to keep a node without its body. Ends the script when the node has no body.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<bodynew,bodyNew>>, <<bodysetenabled,bodySetEnabled>>, <<jointdelete,jointDelete>>
|
*See also:* <<bodynew,bodyNew>>, <<bodysetenabled,bodySetEnabled>>, <<jointdelete,jointDelete>>
|
||||||
|
|
@ -3667,7 +3667,7 @@ bodySetFriction(pond, 0.02)
|
||||||
bodySetMass(node, kilograms)
|
bodySetMass(node, kilograms)
|
||||||
----
|
----
|
||||||
|
|
||||||
Rescales a dynamic body's mass and, with it, its inertia. Do this for anything meant to feel light or heavy: the default is the shape's volume at the density of water, so a door-sized box weighs about 140 kilograms and a nudge will not move it. Forces and impulses scale with mass, so set the mass before tuning them. Raises an error when the body is not dynamic or the mass is not positive.
|
Rescales a dynamic body's mass and, with it, its inertia. Do this for anything meant to feel light or heavy: the default is the shape's volume at the density of water, so a door-sized box weighs about 140 kilograms and a nudge will not move it. Forces and impulses scale with mass, so set the mass before tuning them. Ends the script when the body is not dynamic or the mass is not positive.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<bodynew,bodyNew>>, <<bodyapplyimpulse,bodyApplyImpulse>>, <<playersetpush,playerSetPush>>
|
*See also:* <<bodynew,bodyNew>>, <<bodyapplyimpulse,bodyApplyImpulse>>, <<playersetpush,playerSetPush>>
|
||||||
|
|
@ -3749,7 +3749,7 @@ bodySetWater(node, density, linearDrag)
|
||||||
bodySetWater(node, density, linearDrag, angularDrag)
|
bodySetWater(node, density, linearDrag, angularDrag)
|
||||||
----
|
----
|
||||||
|
|
||||||
Fills a static body with water and makes it a trigger: the top of its shape is the surface, and every dynamic body inside floats, sinks or rolls by buoyancy against that surface (the submerged part of the shape counts, so a tilted crate rights itself), slowed by the drags. A player whose middle is below the surface swims. Because the volume is a trigger, `onTrigger` fires on the way in and out. The water is not drawn: give the node a blended material, or none. Raises an error when the body is not static.
|
Fills a static body with water and makes it a trigger: the top of its shape is the surface, and every dynamic body inside floats, sinks or rolls by buoyancy against that surface (the submerged part of the shape counts, so a tilted crate rights itself), slowed by the drags. A player whose middle is below the surface swims. Because the volume is a trigger, `onTrigger` fires on the way in and out. The water is not drawn: give the node a blended material, or none. Ends the script when the body is not static.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -3793,7 +3793,7 @@ cameraSet(node)
|
||||||
cameraSet(-1)
|
cameraSet(-1)
|
||||||
----
|
----
|
||||||
|
|
||||||
Makes a node the camera: the scene is drawn from its world position looking down its own -Z, so its parents' movement and rotation count. Any node will do, including one that also carries a mesh or a light, and switching cameras is one call. `-1` restores the default view from `(0, 0, 5)` looking at the origin. Deleting the camera node also falls back to the default view. Raises an error for a node handle that is not valid.
|
Makes a node the camera: the scene is drawn from its world position looking down its own -Z, so its parents' movement and rotation count. Any node will do, including one that also carries a mesh or a light, and switching cameras is one call. `-1` restores the default view from `(0, 0, 5)` looking at the origin. Deleting the camera node also falls back to the default view. Ends the script for a node handle that is not valid.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<nodelookat,nodeLookAt>>, <<camerasetperspective,cameraSetPerspective>>, <<viewsetcamera,viewSetCamera>>, <<nodenew,nodeNew>>
|
*See also:* <<nodelookat,nodeLookAt>>, <<camerasetperspective,cameraSetPerspective>>, <<viewsetcamera,viewSetCamera>>, <<nodenew,nodeNew>>
|
||||||
|
|
@ -5344,7 +5344,7 @@ emitter = emitterNew()
|
||||||
emitter = emitterNew(node)
|
emitter = emitterNew(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Makes an emitter. With no argument it is a 2D one at overlay position `(0, 0)`, moved with `emitterSetPosition`; with a node it is a 3D one whose particles are born at that node's world position, and it needs the 3D scene, so on a machine without one the call raises an error (2D emitters work everywhere). A new emitter is not streaming; call `emitterStart` or `emitterBurst`. Deleting the node deletes its emitters. Defaults: 50 particles a second, living one to two seconds, 30 degree spread, white fading to transparent, normal blending, no gravity, drag, spin, radius, texture, trail or collision, a pool of 1000; a 2D emitter starts them up the screen at 50 to 100 overlay units a second shrinking from 16 to 4, a 3D one up the Y axis at 1 to 2 world units a second shrinking from 0.2 to 0.05.
|
Makes an emitter. With no argument it is a 2D one at overlay position `(0, 0)`, moved with `emitterSetPosition`; with a node it is a 3D one whose particles are born at that node's world position, and it needs the 3D scene, so on a machine without one the call ends the script (2D emitters work everywhere). A new emitter is not streaming; call `emitterStart` or `emitterBurst`. Deleting the node deletes its emitters. Defaults: 50 particles a second, living one to two seconds, 30 degree spread, white fading to transparent, normal blending, no gravity, drag, spin, radius, texture, trail or collision, a pool of 1000; a 2D emitter starts them up the screen at 50 to 100 overlay units a second shrinking from 16 to 4, a 3D one up the Y axis at 1 to 2 world units a second shrinking from 0.2 to 0.05.
|
||||||
|
|
||||||
*Returns:* The emitter handle.
|
*Returns:* The emitter handle.
|
||||||
|
|
||||||
|
|
@ -5374,7 +5374,7 @@ emitterStart(exhaust)
|
||||||
emitterDelete(emitter)
|
emitterDelete(emitter)
|
||||||
----
|
----
|
||||||
|
|
||||||
Removes the emitter, its live particles and its copy of the texture at once; nothing fades out. Use `emitterStop` instead to let the last particles finish. A 3D emitter is also removed on its own when its node is deleted. A handle that is not a live emitter raises an error, so clear the variable afterward.
|
Removes the emitter, its live particles and its copy of the texture at once; nothing fades out. Use `emitterStop` instead to let the last particles finish. A 3D emitter is also removed on its own when its node is deleted. A handle that is not a live emitter ends the script, so clear the variable afterward.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<emitternew,emitterNew>>, <<emitterstop,emitterStop>>, <<emitterisactive,emitterIsActive>>
|
*See also:* <<emitternew,emitterNew>>, <<emitterstop,emitterStop>>, <<emitterisactive,emitterIsActive>>
|
||||||
|
|
@ -5452,7 +5452,7 @@ emitterSetFrames(debris, 4, 7)
|
||||||
emitterSetBlend(emitter, mode)
|
emitterSetBlend(emitter, mode)
|
||||||
----
|
----
|
||||||
|
|
||||||
How particles combine with what is behind them. `PARTICLE_ALPHA` (default) blends normally, for smoke, dust, rain and debris; `PARTICLE_ADD` adds light, for fire, sparks, glows and magic, and never darkens. Takes effect on the live particles at once. Anything other than those two constants raises an error.
|
How particles combine with what is behind them. `PARTICLE_ALPHA` (default) blends normally, for smoke, dust, rain and debris; `PARTICLE_ADD` adds light, for fire, sparks, glows and magic, and never darkens. Takes effect on the live particles at once. Anything other than those two constants ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<emittersetcolor,emitterSetColor>>, <<emittersettexture,emitterSetTexture>>
|
*See also:* <<emittersetcolor,emitterSetColor>>, <<emittersettexture,emitterSetTexture>>
|
||||||
|
|
@ -5475,7 +5475,7 @@ emitterSetColor(smoke, 160, 160, 170, 120, 120, 120, 130, 0)
|
||||||
emitterSetLayer(emitter, layer)
|
emitterSetLayer(emitter, layer)
|
||||||
----
|
----
|
||||||
|
|
||||||
2D only: where `emitterDraw` puts the particles. `PARTICLE_OVER` (default) draws above everything on the overlay; `PARTICLE_UNDER` draws beneath the overlay and above the video and the 3D scene, for smoke behind the score. On a 3D emitter the setting is accepted and ignored. Anything other than those two constants raises an error.
|
2D only: where `emitterDraw` puts the particles. `PARTICLE_OVER` (default) draws above everything on the overlay; `PARTICLE_UNDER` draws beneath the overlay and above the video and the 3D scene, for smoke behind the score. On a 3D emitter the setting is accepted and ignored. Anything other than those two constants ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<emitterdraw,emitterDraw>>, <<emittersetposition,emitterSetPosition>>
|
*See also:* <<emitterdraw,emitterDraw>>, <<emittersetposition,emitterSetPosition>>
|
||||||
|
|
@ -5731,7 +5731,7 @@ emitterSetCollide(emitter, mode, bounce, friction)
|
||||||
emitterSetCollide(emitter, mode, bounce, friction, floor)
|
emitterSetCollide(emitter, mode, bounce, friction, floor)
|
||||||
----
|
----
|
||||||
|
|
||||||
What particles bounce off. `COLLIDE_NONE` (default) is nothing; `COLLIDE_FLOOR` is a horizontal plane at height `floor`, below which (larger Y in 2D, smaller Y in 3D) a particle is put back on the plane and reflected; `COLLIDE_SCENE` is the physics world, for 3D emitters only, by a ray cast per particle along its motion each frame, capped at `200` casts per emitter per frame and resumed where they left off, so a big emitter spreads the cost over a few frames. On a 2D emitter `COLLIDE_SCENE` is accepted and does nothing. A mode other than those three raises an error.
|
What particles bounce off. `COLLIDE_NONE` (default) is nothing; `COLLIDE_FLOOR` is a horizontal plane at height `floor`, below which (larger Y in 2D, smaller Y in 3D) a particle is put back on the plane and reflected; `COLLIDE_SCENE` is the physics world, for 3D emitters only, by a ray cast per particle along its motion each frame, capped at `200` casts per emitter per frame and resumed where they left off, so a big emitter spreads the cost over a few frames. On a 2D emitter `COLLIDE_SCENE` is accepted and does nothing. A mode other than those three ends the script.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -5890,7 +5890,7 @@ emitterStart(aura)
|
||||||
emitterSetMax(emitter, count)
|
emitterSetMax(emitter, count)
|
||||||
----
|
----
|
||||||
|
|
||||||
The pool: how many particles may be alive at once, default `1000`. A full pool makes no more, from the stream or a burst, until some die. Changing the count rebuilds the pool and kills every live particle, so set it before starting. Size the pool to rate times longest life, plus the biggest burst. A count below `1` raises an error.
|
The pool: how many particles may be alive at once, default `1000`. A full pool makes no more, from the stream or a burst, until some die. Changing the count rebuilds the pool and kills every live particle, so set it before starting. Size the pool to rate times longest life, plus the biggest burst. A count below `1` ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<emittersetrate,emitterSetRate>>, <<emitterburst,emitterBurst>>, <<emittergetcount,emitterGetCount>>
|
*See also:* <<emittersetrate,emitterSetRate>>, <<emitterburst,emitterBurst>>, <<emittergetcount,emitterGetCount>>
|
||||||
|
|
@ -5914,7 +5914,7 @@ emitterStart(fountain)
|
||||||
emitterSetPosition(emitter, x, y)
|
emitterSetPosition(emitter, x, y)
|
||||||
----
|
----
|
||||||
|
|
||||||
2D only: where new particles are born, in overlay coordinates; default `(0, 0)`. Set it every frame to follow a sprite or a 2D physics body. Live particles stay where they are unless `emitterSetLocal` is on. A 3D emitter follows its node and raises an error here; move the node instead.
|
2D only: where new particles are born, in overlay coordinates; default `(0, 0)`. Set it every frame to follow a sprite or a 2D physics body. Live particles stay where they are unless `emitterSetLocal` is on. A 3D emitter follows its node and ends the script here; move the node instead.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<emitterdraw,emitterDraw>>, <<emittersetlocal,emitterSetLocal>>, <<emittersetradius,emitterSetRadius>>
|
*See also:* <<emitterdraw,emitterDraw>>, <<emittersetlocal,emitterSetLocal>>, <<emittersetradius,emitterSetRadius>>
|
||||||
|
|
@ -6071,7 +6071,7 @@ end
|
||||||
emitterDraw(emitter)
|
emitterDraw(emitter)
|
||||||
----
|
----
|
||||||
|
|
||||||
2D only, from `onOverlayUpdate`: queues the emitter's particles to be drawn this frame, above or beneath the overlay per `emitterSetLayer`, in overlay coordinates mapped onto the window. Emitters on the same layer draw in the order they were queued. The queue empties every frame, so the call belongs in every frame the particles should show; not drawing an emitter does not stop it. Up to `64` emitters per layer draw in a frame. A 3D emitter draws itself in the scene and raises an error here.
|
2D only, from `onOverlayUpdate`: queues the emitter's particles to be drawn this frame, above or beneath the overlay per `emitterSetLayer`, in overlay coordinates mapped onto the window. Emitters on the same layer draw in the order they were queued. The queue empties every frame, so the call belongs in every frame the particles should show; not drawing an emitter does not stop it. Up to `64` emitters per layer draw in a frame. A 3D emitter draws itself in the scene and ends the script here.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<emittersetlayer,emitterSetLayer>>, <<emittersetposition,emitterSetPosition>>, <<onoverlayupdate,onOverlayUpdate>>
|
*See also:* <<emittersetlayer,emitterSetLayer>>, <<emittersetposition,emitterSetPosition>>, <<onoverlayupdate,onOverlayUpdate>>
|
||||||
|
|
@ -6336,7 +6336,7 @@ end
|
||||||
[#guiref]
|
[#guiref]
|
||||||
=== Gui
|
=== Gui
|
||||||
|
|
||||||
A GUI is an RmlUi document tree of a fixed size rendered into its own texture every frame: `guiNew` makes one and returns an integer handle, `guiLoad` puts an RML document in it and returns a document handle, `guiDraw` composites it over the overlay and `materialSetGui` shows it on a material in the scene. Documents are HTML-like RML styled by CSS-like RCSS, loaded through the virtual file system like sprites; `Singe/gui.rcss` is the shipped theme and `Singe/FreeSansBold.ttf` the fallback face. The by-id helpers `guiGetValue`, `guiSetValue` and `guiSetHandler` reach elements without the object model; everything else is RmlUi's Lua API on the `rmlui` global, where a GUI is the context named `gui` followed by its handle. Up to sixteen GUIs exist at once, each with thirty-two documents. A GUI needs the GPU device, so `guiNew` raises an error on a machine without one; a bad GUI handle raises `No GUI N`, and a bad document handle `No document N in GUI G`. See <<gui,GUI>> in Game Development.
|
A GUI is an RmlUi document tree of a fixed size rendered into its own texture every frame: `guiNew` makes one and returns an integer handle, `guiLoad` puts an RML document in it and returns a document handle, `guiDraw` composites it over the overlay and `materialSetGui` shows it on a material in the scene. Documents are HTML-like RML styled by CSS-like RCSS, loaded through the virtual file system like sprites; `Singe/gui.rcss` is the shipped theme and `Singe/FreeSansBold.ttf` the fallback face. The by-id helpers `guiGetValue`, `guiSetValue` and `guiSetHandler` reach elements without the object model; everything else is RmlUi's Lua API on the `rmlui` global, where a GUI is the context named `gui` followed by its handle. Up to sixteen GUIs exist at once, each with thirty-two documents. A GUI needs the GPU device, so `guiNew` ends the script on a machine without one; a bad GUI handle raises `No GUI N`, and a bad document handle `No document N in GUI G`. See <<gui,GUI>> in Game Development.
|
||||||
|
|
||||||
[#guiclose]
|
[#guiclose]
|
||||||
==== guiClose
|
==== guiClose
|
||||||
|
|
@ -6370,7 +6370,7 @@ end)
|
||||||
guiDelete(gui)
|
guiDelete(gui)
|
||||||
----
|
----
|
||||||
|
|
||||||
Frees the GUI, every document in it and its texture, and stops the per-frame update and render it cost. Materials showing it through `materialSetGui` go back to their own texture. The handle is invalid afterwards and raises an error if used again; since a GUI keeps rendering whether or not it is drawn, delete the ones a game is finished with rather than hiding them.
|
Frees the GUI, every document in it and its texture, and stops the per-frame update and render it cost. Materials showing it through `materialSetGui` go back to their own texture. The handle is invalid afterwards and ends the script if used again; since a GUI keeps rendering whether or not it is drawn, delete the ones a game is finished with rather than hiding them.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<guinew,guiNew>>, <<guiclose,guiClose>>, <<materialsetgui,materialSetGui>>
|
*See also:* <<guinew,guiNew>>, <<guiclose,guiClose>>, <<materialsetgui,materialSetGui>>
|
||||||
|
|
@ -6462,7 +6462,7 @@ end
|
||||||
height = guiGetHeight(gui)
|
height = guiGetHeight(gui)
|
||||||
----
|
----
|
||||||
|
|
||||||
The GUI's height in its own pixels, as given to `guiNew`. Raises an error for a handle that is not valid.
|
The GUI's height in its own pixels, as given to `guiNew`. Ends the script for a handle that is not valid.
|
||||||
|
|
||||||
*Returns:* An integer.
|
*Returns:* An integer.
|
||||||
|
|
||||||
|
|
@ -6509,7 +6509,7 @@ debugPrint(string.format("gui draws %d layers %d filters %d passes %d, %d KB of
|
||||||
value = guiGetValue(gui, document, id)
|
value = guiGetValue(gui, document, id)
|
||||||
----
|
----
|
||||||
|
|
||||||
The value of the element with that `id` in the document, as a string: a form control's value (a text field's text, a `select` option's `value`, a range in RmlUi's own formatting such as `"85.000000"`, a checkbox's or radio button's `value` attribute, `"on"` when it has none, whether or not it is checked; its state is its `checked` attribute, read through RmlUi's API), or, for any other element, its inner RML. Returns `nil` when no element has that id, so a page can be probed without an error; a bad GUI or document handle raises one. Values longer than 4095 characters are cut.
|
The value of the element with that `id` in the document, as a string: a form control's value (a text field's text, a `select` option's `value`, a range in RmlUi's own formatting such as `"85.000000"`, a checkbox's or radio button's `value` attribute, `"on"` when it has none, whether or not it is checked; its state is its `checked` attribute, read through RmlUi's API), or, for any other element, its inner RML. Returns `nil` when no element has that id, so a page can be probed without ending the script; a bad GUI or document handle ends it. Values longer than 4095 characters are cut.
|
||||||
|
|
||||||
*Returns:* A string, or `nil`.
|
*Returns:* A string, or `nil`.
|
||||||
|
|
||||||
|
|
@ -6535,7 +6535,7 @@ end)
|
||||||
width = guiGetWidth(gui)
|
width = guiGetWidth(gui)
|
||||||
----
|
----
|
||||||
|
|
||||||
The GUI's width in its own pixels, as given to `guiNew`. Raises an error for a handle that is not valid.
|
The GUI's width in its own pixels, as given to `guiNew`. Ends the script for a handle that is not valid.
|
||||||
|
|
||||||
*Returns:* An integer.
|
*Returns:* An integer.
|
||||||
|
|
||||||
|
|
@ -6671,7 +6671,7 @@ end
|
||||||
guiRefresh(gui)
|
guiRefresh(gui)
|
||||||
----
|
----
|
||||||
|
|
||||||
Draws the GUI again on the next frame even if nothing about it seems to have changed. A GUI is only redrawn when its picture changes: when a frame's document draws exactly what the last frame drew, and nothing new was made in between, the texture already holds that picture and is kept, which saves the GPU most of a GUI's cost on the frames a HUD or a menu stands still. Changes made through RML, RCSS, the RmlUi Lua interface or the `gui*` calls are all seen. This call is the way out should one ever be missed -- a GUI that stops updating when it should -- and a game should not need it; if it does, that is worth reporting. A GUI that does not exist raises an error.
|
Draws the GUI again on the next frame even if nothing about it seems to have changed. A GUI is only redrawn when its picture changes: when a frame's document draws exactly what the last frame drew, and nothing new was made in between, the texture already holds that picture and is kept, which saves the GPU most of a GUI's cost on the frames a HUD or a menu stands still. Changes made through RML, RCSS, the RmlUi Lua interface or the `gui*` calls are all seen. This call is the way out should one ever be missed -- a GUI that stops updating when it should -- and a game should not need it; if it does, that is worth reporting. A GUI that does not exist ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<guidraw,guiDraw>>, <<guigetstats,guiGetStats>>
|
*See also:* <<guidraw,guiDraw>>, <<guigetstats,guiGetStats>>
|
||||||
|
|
@ -6728,7 +6728,7 @@ end)
|
||||||
guiSetInput(gui, enabled)
|
guiSetInput(gui, enabled)
|
||||||
----
|
----
|
||||||
|
|
||||||
Whether the mouse and light gun, keys, typed text and the framework switches from a pad or mouse reach the GUI. A new GUI only displays, which suits a HUD or a sign; turn input on for a menu or a form. While it is on, the pointer reaches the GUI wherever it is drawn flat (the last drawn on top) or shown on a surface in the scene, every key goes to it except `Tab`, which the GUI is given only while a text field is being typed in (RmlUi uses it to move focus and never hands it back, and a game whose keys include `Tab` would otherwise lose it), a text field with focus turns on SDL's text input, and the pad's directions arrive as arrow keys with `ACTION_1` as Return and `ACTION_2` as Escape. What an element uses never reaches `onInputPressed`, `onKeyPressed` or the mouse callbacks; what nothing used falls through to them as usual. Raises an error for a handle that is not valid.
|
Whether the mouse and light gun, keys, typed text and the framework switches from a pad or mouse reach the GUI. A new GUI only displays, which suits a HUD or a sign; turn input on for a menu or a form. While it is on, the pointer reaches the GUI wherever it is drawn flat (the last drawn on top) or shown on a surface in the scene, every key goes to it except `Tab`, which the GUI is given only while a text field is being typed in (RmlUi uses it to move focus and never hands it back, and a game whose keys include `Tab` would otherwise lose it), a text field with focus turns on SDL's text input, and the pad's directions arrive as arrow keys with `ACTION_1` as Return and `ACTION_2` as Escape. What an element uses never reaches `onInputPressed`, `onKeyPressed` or the mouse callbacks; what nothing used falls through to them as usual. Ends the script for a handle that is not valid.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<guisethandler,guiSetHandler>>, <<guidraw,guiDraw>>, <<sceneprobegui,sceneProbeGui>>
|
*See also:* <<guisethandler,guiSetHandler>>, <<guidraw,guiDraw>>, <<sceneprobegui,sceneProbeGui>>
|
||||||
|
|
@ -6805,7 +6805,7 @@ end
|
||||||
[#joint]
|
[#joint]
|
||||||
=== Joint
|
=== Joint
|
||||||
|
|
||||||
Joints connect two bodies, or a body and the world when the second node is `-1`, and hand back an integer joint handle that the other calls take. Anchors and axes are given in world space at the moment the joint is made, with the bodies where they are then, so place the nodes and give them bodies before jointing them. Distances are in world units and hinge angles in degrees. Deleting either body, or its node, deletes the joint; a handle that has been deleted raises an error. See <<physics,Physics>> in Game Development, Joints.
|
Joints connect two bodies, or a body and the world when the second node is `-1`, and hand back an integer joint handle that the other calls take. Anchors and axes are given in world space at the moment the joint is made, with the bodies where they are then, so place the nodes and give them bodies before jointing them. Distances are in world units and hinge angles in degrees. Deleting either body, or its node, deletes the joint; a handle that has been deleted ends the script. See <<physics,Physics>> in Game Development, Joints.
|
||||||
|
|
||||||
[#jointhinge]
|
[#jointhinge]
|
||||||
==== jointHinge
|
==== jointHinge
|
||||||
|
|
@ -6815,7 +6815,7 @@ Joints connect two bodies, or a body and the world when the second node is `-1`,
|
||||||
joint = jointHinge(nodeA, nodeB, ax, ay, az, dx, dy, dz)
|
joint = jointHinge(nodeA, nodeB, ax, ay, az, dx, dy, dz)
|
||||||
----
|
----
|
||||||
|
|
||||||
Makes a hinge between the two bodies through the world point `(ax, ay, az)` along the axis `(dx, dy, dz)`: a door, a wheel, a lever, a seesaw. The bodies keep that point and axis in common and may only turn about it. The axis is normalized, so its length does not matter; a zero axis becomes `(0, 1, 0)`. Both nodes must carry a body, or `nodeB` may be `-1` to hinge `nodeA` to the world. Raises an error when a node has no body or the joint cannot be made.
|
Makes a hinge between the two bodies through the world point `(ax, ay, az)` along the axis `(dx, dy, dz)`: a door, a wheel, a lever, a seesaw. The bodies keep that point and axis in common and may only turn about it. The axis is normalized, so its length does not matter; a zero axis becomes `(0, 1, 0)`. Both nodes must carry a body, or `nodeB` may be `-1` to hinge `nodeA` to the world. Ends the script when a node has no body or the joint cannot be made.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -6852,7 +6852,7 @@ bodyApplyImpulse(door, 0, 0, -60, 0.5, -0.62, 1.45)
|
||||||
joint = jointBall(nodeA, nodeB, ax, ay, az)
|
joint = jointBall(nodeA, nodeB, ax, ay, az)
|
||||||
----
|
----
|
||||||
|
|
||||||
Makes a ball-and-socket joint at the world point `(ax, ay, az)`: the bodies may turn any way about it but cannot separate. A pendulum, a chain link, a hanging sign, a ragdoll shoulder. Both nodes must carry a body, or `nodeB` may be `-1` to hang `nodeA` from a fixed point in the world. A ball joint has no limits, so `jointSetLimits` refuses it. Raises an error when a node has no body or the joint cannot be made.
|
Makes a ball-and-socket joint at the world point `(ax, ay, az)`: the bodies may turn any way about it but cannot separate. A pendulum, a chain link, a hanging sign, a ragdoll shoulder. Both nodes must carry a body, or `nodeB` may be `-1` to hang `nodeA` from a fixed point in the world. A ball joint has no limits, so `jointSetLimits` refuses it. Ends the script when a node has no body or the joint cannot be made.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -6889,7 +6889,7 @@ end
|
||||||
joint = jointSlider(nodeA, nodeB, ax, ay, az, dx, dy, dz)
|
joint = jointSlider(nodeA, nodeB, ax, ay, az, dx, dy, dz)
|
||||||
----
|
----
|
||||||
|
|
||||||
Lets the bodies slide relative to each other only along the axis `(dx, dy, dz)` through the anchor `(ax, ay, az)`, keeping their orientation: a drawer, a piston, a lift, a sliding door. The axis is normalized, so its length does not matter; a zero axis becomes `(0, 1, 0)`. Both nodes must carry a body, or `nodeB` may be `-1` to run `nodeA` along a rail fixed in the world. Raises an error when a node has no body or the joint cannot be made.
|
Lets the bodies slide relative to each other only along the axis `(dx, dy, dz)` through the anchor `(ax, ay, az)`, keeping their orientation: a drawer, a piston, a lift, a sliding door. The axis is normalized, so its length does not matter; a zero axis becomes `(0, 1, 0)`. Both nodes must carry a body, or `nodeB` may be `-1` to run `nodeA` along a rail fixed in the world. Ends the script when a node has no body or the joint cannot be made.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -6924,7 +6924,7 @@ bodySetVelocity(crate, 3, 0, 0)
|
||||||
jointSetLimits(joint, low, high)
|
jointSetLimits(joint, low, high)
|
||||||
----
|
----
|
||||||
|
|
||||||
Bounds a hinge's angle in degrees, or a slider's travel in world units, either side of where the joint started. A positive hinge angle is a right-hand turn of `nodeA` about the axis; a positive slider distance is along the axis. `low` is clamped to at most `0` and `high` to at least `0`, so the starting pose is always inside the range. Call it again to change the range at any time. Raises an error for a ball joint or a handle that no longer exists.
|
Bounds a hinge's angle in degrees, or a slider's travel in world units, either side of where the joint started. A positive hinge angle is a right-hand turn of `nodeA` about the axis; a positive slider distance is along the axis. `low` is clamped to at most `0` and `high` to at least `0`, so the starting pose is always inside the range. Call it again to change the range at any time. Ends the script for a ball joint or a handle that no longer exists.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -6961,7 +6961,7 @@ end
|
||||||
jointDelete(joint)
|
jointDelete(joint)
|
||||||
----
|
----
|
||||||
|
|
||||||
Removes the joint; the bodies are free of each other again and keep whatever velocity they had. Joints also go away with either of their bodies, so this is only needed to free two bodies that both stay. Raises an error for a handle that does not exist or was already deleted.
|
Removes the joint; the bodies are free of each other again and keep whatever velocity they had. Joints also go away with either of their bodies, so this is only needed to free two bodies that both stay. Ends the script for a handle that does not exist or was already deleted.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<jointhinge,jointHinge>>, <<jointball,jointBall>>, <<bodydelete,bodyDelete>>
|
*See also:* <<jointhinge,jointHinge>>, <<jointball,jointBall>>, <<bodydelete,bodyDelete>>
|
||||||
|
|
@ -7264,7 +7264,7 @@ end
|
||||||
[#light]
|
[#light]
|
||||||
=== Light
|
=== Light
|
||||||
|
|
||||||
Lights are nodes carrying a light, made by `lightNew` and moved, parented and animated with the node calls. Up to eight visible lights shine in a frame, in node order; `sceneSetAmbient` and a sky from `sceneSetSky` add light from everywhere. Directional and spot lights shine down the node's -Z, so aim them with `nodeLookAt` or `nodeSetRotation`; point lights only need a position. Colors are integers from `0` to `255`, intensities are multipliers in the scene's linear light, angles are degrees, and distances are world units. Every `lightSet*` call raises an error for a node that does not carry a light. See <<scenes3d,3D Scenes>> for lighting, exposure and shadows.
|
Lights are nodes carrying a light, made by `lightNew` and moved, parented and animated with the node calls. Up to eight visible lights shine in a frame, in node order; `sceneSetAmbient` and a sky from `sceneSetSky` add light from everywhere. Directional and spot lights shine down the node's -Z, so aim them with `nodeLookAt` or `nodeSetRotation`; point lights only need a position. Colors are integers from `0` to `255`, intensities are multipliers in the scene's linear light, angles are degrees, and distances are world units. Every `lightSet*` call ends the script for a node that does not carry a light. See <<scenes3d,3D Scenes>> for lighting, exposure and shadows.
|
||||||
|
|
||||||
[#lightnew]
|
[#lightnew]
|
||||||
==== lightNew
|
==== lightNew
|
||||||
|
|
@ -7275,7 +7275,7 @@ node = lightNew(type)
|
||||||
node = lightNew(type, parent)
|
node = lightNew(type, parent)
|
||||||
----
|
----
|
||||||
|
|
||||||
Makes a new node carrying a light and returns the node handle. The light starts white at intensity `1`, with no range limit and, for a spot, a cone of `20` to `30` degrees. Place and aim it with the node calls: `nodeSetPosition` for a point or spot light, `nodeLookAt` for a directional or spot light. A light parented to a moving node moves with it, so a headlight on a car or a torch in a hand is one call. Raises an error for an unknown type or when no node can be made.
|
Makes a new node carrying a light and returns the node handle. The light starts white at intensity `1`, with no range limit and, for a spot, a cone of `20` to `30` degrees. Place and aim it with the node calls: `nodeSetPosition` for a point or spot light, `nodeLookAt` for a directional or spot light. A light parented to a moving node moves with it, so a headlight on a car or a torch in a hand is one call. Ends the script for an unknown type or when no node can be made.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -7317,7 +7317,7 @@ lightSetIntensity(headlight, 30)
|
||||||
lightSetColor(node, r, g, b)
|
lightSetColor(node, r, g, b)
|
||||||
----
|
----
|
||||||
|
|
||||||
The light's color, `0` to `255` per channel (out-of-range values are clamped), treated as sRGB; white by default. Brightness belongs in `lightSetIntensity`, so keep the color for the hue (a warm `255, 230, 190` bulb, a cool `150, 190, 255` moon) and leave at least one channel near `255`. Raises an error when the node carries no light.
|
The light's color, `0` to `255` per channel (out-of-range values are clamped), treated as sRGB; white by default. Brightness belongs in `lightSetIntensity`, so keep the color for the hue (a warm `255, 230, 190` bulb, a cool `150, 190, 255` moon) and leave at least one channel near `255`. Ends the script when the node carries no light.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<lightsetintensity,lightSetIntensity>>, <<lightnew,lightNew>>
|
*See also:* <<lightsetintensity,lightSetIntensity>>, <<lightnew,lightNew>>
|
||||||
|
|
@ -7347,7 +7347,7 @@ end
|
||||||
lightSetCone(node, innerDegrees, outerDegrees)
|
lightSetCone(node, innerDegrees, outerDegrees)
|
||||||
----
|
----
|
||||||
|
|
||||||
The shape of a spot light's cone, both angles measured from the axis (the node's -Z): full brightness within `innerDegrees`, fading smoothly to nothing at `outerDegrees`. The defaults are `20` and `30`. A narrow gap between the two gives a hard-edged theater spot, a wide one a soft pool. The values are stored for every light but only a `LIGHT_SPOT` uses them. Raises an error when the node carries no light.
|
The shape of a spot light's cone, both angles measured from the axis (the node's -Z): full brightness within `innerDegrees`, fading smoothly to nothing at `outerDegrees`. The defaults are `20` and `30`. A narrow gap between the two gives a hard-edged theater spot, a wide one a soft pool. The values are stored for every light but only a `LIGHT_SPOT` uses them. Ends the script when the node carries no light.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -7379,7 +7379,7 @@ lightSetShadow(spot, true)
|
||||||
lightSetIntensity(node, intensity)
|
lightSetIntensity(node, intensity)
|
||||||
----
|
----
|
||||||
|
|
||||||
The light's brightness multiplier in the scene's linear light, default `1`. A directional light of intensity `1` lights a white surface facing it to white. A point or spot light fades with distance as `1 / (1 + d^2)`, so its intensity is the brightness at the light itself, half of it one unit away and a hundredth ten units away; bulbs usually want values well above `1`, `10` to `50` for a room, and `sceneSetExposure` brings the whole scene back into range. Raises an error when the node carries no light.
|
The light's brightness multiplier in the scene's linear light, default `1`. A directional light of intensity `1` lights a white surface facing it to white. A point or spot light fades with distance as `1 / (1 + d^2)`, so its intensity is the brightness at the light itself, half of it one unit away and a hundredth ten units away; bulbs usually want values well above `1`, `10` to `50` for a room, and `sceneSetExposure` brings the whole scene back into range. Ends the script when the node carries no light.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<lightsetrange,lightSetRange>>, <<scenesetexposure,sceneSetExposure>>, <<lightsetcolor,lightSetColor>>
|
*See also:* <<lightsetrange,lightSetRange>>, <<scenesetexposure,sceneSetExposure>>, <<lightsetcolor,lightSetColor>>
|
||||||
|
|
@ -7407,7 +7407,7 @@ end
|
||||||
lightSetRange(node, range)
|
lightSetRange(node, range)
|
||||||
----
|
----
|
||||||
|
|
||||||
How far a point or spot light reaches, in world units; `0` (the default) means no limit. The light fades to exactly nothing at the range, on top of its distance falloff, so lights do not pop as objects cross it. A range also bounds how far a shadow-casting light's shadow map has to reach, which keeps its shadows sharp. Directional lights ignore it. Raises an error when the node carries no light.
|
How far a point or spot light reaches, in world units; `0` (the default) means no limit. The light fades to exactly nothing at the range, on top of its distance falloff, so lights do not pop as objects cross it. A range also bounds how far a shadow-casting light's shadow map has to reach, which keeps its shadows sharp. Directional lights ignore it. Ends the script when the node carries no light.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<lightsetintensity,lightSetIntensity>>, <<lightsetshadow,lightSetShadow>>
|
*See also:* <<lightsetintensity,lightSetIntensity>>, <<lightsetshadow,lightSetShadow>>
|
||||||
|
|
@ -7433,7 +7433,7 @@ end
|
||||||
lightSetShadow(node, shadow)
|
lightSetShadow(node, shadow)
|
||||||
----
|
----
|
||||||
|
|
||||||
Makes the light cast shadows from everything opaque in the scene, or stops it; off by default. Any of the eight lights may cast, in any mix. A spot light gets one shadow map; a directional light gets one map per cascade (`sceneSetShadowCascades`); a point light gets a cube map of six views, which covers every direction (a bulb inside a room) at six times the cost. `nodeSetShadow` excuses a mesh from casting. Raises an error when the node carries no light.
|
Makes the light cast shadows from everything opaque in the scene, or stops it; off by default. Any of the eight lights may cast, in any mix. A spot light gets one shadow map; a directional light gets one map per cascade (`sceneSetShadowCascades`); a point light gets a cube map of six views, which covers every direction (a bulb inside a room) at six times the cost. `nodeSetShadow` excuses a mesh from casting. Ends the script when the node carries no light.
|
||||||
|
|
||||||
*Notes:* Each shadow map costs one extra pass over the scene per frame, a cube map six, and a cascaded sun as many as it has cascades; the maps' size is set by `sceneSetShadowSize`. A modern integrated GPU manages eight casters comfortably; a Raspberry Pi wants one or two at `512`.
|
*Notes:* Each shadow map costs one extra pass over the scene per frame, a cube map six, and a cascaded sun as many as it has cascades; the maps' size is set by `sceneSetShadowSize`. A modern integrated GPU manages eight casters comfortably; a Raspberry Pi wants one or two at `512`.
|
||||||
|
|
||||||
|
|
@ -7460,7 +7460,7 @@ nodeSetShadow(bulbGlass, false)
|
||||||
[#material]
|
[#material]
|
||||||
=== Material
|
=== Material
|
||||||
|
|
||||||
A material is how a mesh looks: a base color with alpha, an optional base texture from a sprite, a KTX2 file, a video or a rendered view, and the glTF metallic-roughness parameters with four optional maps. `materialNew` returns an integer handle; pass it to `nodeSetMesh` or `nodeSetMaterial`, and to every `materialSet*` call. Materials are shared, so changing one changes every mesh drawn with it, and a node without one draws white and half rough. Colors are integers from `0` to `255` per channel and are treated as sRGB; factors such as roughness run from `0` to `1`. A bad handle raises an error. See <<scenes3d,3D Scenes>> for how materials are lit.
|
A material is how a mesh looks: a base color with alpha, an optional base texture from a sprite, a KTX2 file, a video or a rendered view, and the glTF metallic-roughness parameters with four optional maps. `materialNew` returns an integer handle; pass it to `nodeSetMesh` or `nodeSetMaterial`, and to every `materialSet*` call. Materials are shared, so changing one changes every mesh drawn with it, and a node without one draws white and half rough. Colors are integers from `0` to `255` per channel and are treated as sRGB; factors such as roughness run from `0` to `1`. A bad handle ends the script. See <<scenes3d,3D Scenes>> for how materials are lit.
|
||||||
|
|
||||||
[#materialnew]
|
[#materialnew]
|
||||||
==== materialNew
|
==== materialNew
|
||||||
|
|
@ -7470,7 +7470,7 @@ A material is how a mesh looks: a base color with alpha, an optional base textur
|
||||||
material = materialNew()
|
material = materialNew()
|
||||||
----
|
----
|
||||||
|
|
||||||
Allocates a new material and returns its handle. It starts white and opaque (color `255, 255, 255, 255`), with roughness `0.5`, metallic `0`, no emissive color, tiling `1, 1`, linear filtering, single sided, lit, and no texture or maps. Set what differs from that and hand the handle to `nodeSetMesh`. Loaded models bring their own materials, so this is for meshes built from script. Raises an error on a machine with no usable GPU.
|
Allocates a new material and returns its handle. It starts white and opaque (color `255, 255, 255, 255`), with roughness `0.5`, metallic `0`, no emissive color, tiling `1, 1`, linear filtering, single sided, lit, and no texture or maps. Set what differs from that and hand the handle to `nodeSetMesh`. Loaded models bring their own materials, so this is for meshes built from script. Ends the script on a machine with no usable GPU.
|
||||||
|
|
||||||
*Returns:* The material handle, an integer.
|
*Returns:* The material handle, an integer.
|
||||||
|
|
||||||
|
|
@ -7503,7 +7503,7 @@ nodeSetMesh(trophy, meshCone(0.3, 0.8, 24), gold)
|
||||||
materialDelete(material)
|
materialDelete(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
Frees the material and any texture it uploaded. Every node that was drawn with it falls back to the default look (white, half rough), so reassign those nodes with `nodeSetMaterial` first if they should keep a look. The handle is invalid afterwards and raises an error if used again. The private material a node makes for itself under `nodeSetSprite` or `nodeSetText` belongs to the node and cannot be deleted; the call raises an error, and clearing the sprite or text frees it. Materials are cheap; deleting them matters mostly for ones that carried large textures.
|
Frees the material and any texture it uploaded. Every node that was drawn with it falls back to the default look (white, half rough), so reassign those nodes with `nodeSetMaterial` first if they should keep a look. The handle is invalid afterwards and ends the script if used again. The private material a node makes for itself under `nodeSetSprite` or `nodeSetText` belongs to the node and cannot be deleted; the call ends the script, and clearing the sprite or text frees it. Materials are cheap; deleting them matters mostly for ones that carried large textures.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<materialnew,materialNew>>, <<nodesetmaterial,nodeSetMaterial>>
|
*See also:* <<materialnew,materialNew>>, <<nodesetmaterial,nodeSetMaterial>>
|
||||||
|
|
@ -7659,7 +7659,7 @@ materialSetEmissiveMap(material, nil)
|
||||||
materialSetEmissiveMap(material)
|
materialSetEmissiveMap(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
A texture saying which parts of the surface glow. Its color is multiplied by the color from `materialSetEmissive`, so set that too: white to use the map's own colors, or a tint. The image comes from a loaded sprite (a copy is uploaded, so the sprite may be unloaded afterwards) or from a `.ktx2` file by name, kept compressed on the GPU. With `nil` or no second argument the map is removed. Raises an error when the texture cannot be uploaded or the file is not a KTX2 texture.
|
A texture saying which parts of the surface glow. Its color is multiplied by the color from `materialSetEmissive`, so set that too: white to use the map's own colors, or a tint. The image comes from a loaded sprite (a copy is uploaded, so the sprite may be unloaded afterwards) or from a `.ktx2` file by name, kept compressed on the GPU. With `nil` or no second argument the map is removed. Ends the script when the texture cannot be uploaded or the file is not a KTX2 texture.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<materialsetemissive,materialSetEmissive>>, <<materialsettexture,materialSetTexture>>, <<materialsettiling,materialSetTiling>>
|
*See also:* <<materialsetemissive,materialSetEmissive>>, <<materialsettexture,materialSetTexture>>, <<materialsettiling,materialSetTiling>>
|
||||||
|
|
@ -7688,7 +7688,7 @@ end
|
||||||
materialSetFilter(material, filter)
|
materialSetFilter(material, filter)
|
||||||
----
|
----
|
||||||
|
|
||||||
How the material's textures are sampled. `FILTER_LINEAR` (the default) blends between texels and between mipmap levels, with anisotropic filtering on surfaces seen at a grazing angle. `FILTER_NEAREST` picks the closest texel and the closest mipmap level, so pixel art stays crisp on a quad. Every texture carries a full mipmap chain, so a texture seen small never sparkles either way. Any other value raises an error.
|
How the material's textures are sampled. `FILTER_LINEAR` (the default) blends between texels and between mipmap levels, with anisotropic filtering on surfaces seen at a grazing angle. `FILTER_NEAREST` picks the closest texel and the closest mipmap level, so pixel art stays crisp on a quad. Every texture carries a full mipmap chain, so a texture seen small never sparkles either way. Any other value ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<materialsettexture,materialSetTexture>>, <<materialsetunlit,materialSetUnlit>>
|
*See also:* <<materialsettexture,materialSetTexture>>, <<materialsetunlit,materialSetUnlit>>
|
||||||
|
|
@ -7715,7 +7715,7 @@ materialSetGui(material, nil)
|
||||||
materialSetGui(material)
|
materialSetGui(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
Shows a GUI from `guiNew` as the material's base color texture, updated every frame: a monitor, a cockpit panel, a sign, with the same document a `guiDraw` would show flat. Usually paired with `materialSetUnlit` so the page is not shaded by the lights, and tinted by the base color like any texture. With `nil` or no second argument the material goes back to its own texture, which is kept underneath; deleting the GUI does the same. Setting a GUI drops any video or view the material was showing. While the GUI takes input, the mouse or a light gun pointing at a surface drawn with the material reaches it through the same probe as `sceneProbeGui`. Raises an error for a GUI handle that is not valid.
|
Shows a GUI from `guiNew` as the material's base color texture, updated every frame: a monitor, a cockpit panel, a sign, with the same document a `guiDraw` would show flat. Usually paired with `materialSetUnlit` so the page is not shaded by the lights, and tinted by the base color like any texture. With `nil` or no second argument the material goes back to its own texture, which is kept underneath; deleting the GUI does the same. Setting a GUI drops any video or view the material was showing. While the GUI takes input, the mouse or a light gun pointing at a surface drawn with the material reaches it through the same probe as `sceneProbeGui`. Ends the script for a GUI handle that is not valid.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<guinew,guiNew>>, <<guidraw,guiDraw>>, <<sceneprobegui,sceneProbeGui>>, <<materialsetview,materialSetView>>, <<materialsetvideo,materialSetVideo>>, <<materialsetunlit,materialSetUnlit>>
|
*See also:* <<guinew,guiNew>>, <<guidraw,guiDraw>>, <<sceneprobegui,sceneProbeGui>>, <<materialsetview,materialSetView>>, <<materialsetvideo,materialSetVideo>>, <<materialsetunlit,materialSetUnlit>>
|
||||||
|
|
@ -7773,7 +7773,7 @@ materialSetMetallicRoughnessMap(material, nil)
|
||||||
materialSetMetallicRoughnessMap(material)
|
materialSetMetallicRoughnessMap(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
A texture varying the finish across the surface: its green channel multiplies the roughness from `materialSetRoughness` and its blue channel the metallic value from `materialSetMetallic`, the way glTF packs them, so a map from a glTF tool works as is. Set both factors to `1` to use the map's values alone. The image comes from a sprite or a `.ktx2` file by name; `nil` or no second argument removes the map. Raises an error when the texture cannot be uploaded.
|
A texture varying the finish across the surface: its green channel multiplies the roughness from `materialSetRoughness` and its blue channel the metallic value from `materialSetMetallic`, the way glTF packs them, so a map from a glTF tool works as is. Set both factors to `1` to use the map's values alone. The image comes from a sprite or a `.ktx2` file by name; `nil` or no second argument removes the map. Ends the script when the texture cannot be uploaded.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<materialsetmetallic,materialSetMetallic>>, <<materialsetroughness,materialSetRoughness>>, <<materialsettexture,materialSetTexture>>
|
*See also:* <<materialsetmetallic,materialSetMetallic>>, <<materialsetroughness,materialSetRoughness>>, <<materialsettexture,materialSetTexture>>
|
||||||
|
|
@ -7804,7 +7804,7 @@ materialSetNormalMap(material, nil)
|
||||||
materialSetNormalMap(material)
|
materialSetNormalMap(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
A tangent-space normal map (the usual bluish kind, flat at `128, 128, 255`) that tilts the lighting per texel so bricks, grooves and rivets catch the light without extra geometry. Every mesh has tangents for it: a model's own when the file carries them, otherwise computed from the UVs. The image comes from a sprite or a `.ktx2` file by name; `nil` or no second argument removes the map. Raises an error when the texture cannot be uploaded.
|
A tangent-space normal map (the usual bluish kind, flat at `128, 128, 255`) that tilts the lighting per texel so bricks, grooves and rivets catch the light without extra geometry. Every mesh has tangents for it: a model's own when the file carries them, otherwise computed from the UVs. The image comes from a sprite or a `.ktx2` file by name; `nil` or no second argument removes the map. Ends the script when the texture cannot be uploaded.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -7874,7 +7874,7 @@ materialSetOcclusionMap(material, nil)
|
||||||
materialSetOcclusionMap(material)
|
materialSetOcclusionMap(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
An ambient occlusion map, occlusion in the red channel (white for open, dark for crevices), that darkens the ambient and sky light in corners and folds. Direct light from light nodes is not affected, as in glTF. The image comes from a sprite or a `.ktx2` file by name; `nil` or no second argument removes the map. Raises an error when the texture cannot be uploaded.
|
An ambient occlusion map, occlusion in the red channel (white for open, dark for crevices), that darkens the ambient and sky light in corners and folds. Direct light from light nodes is not affected, as in glTF. The image comes from a sprite or a `.ktx2` file by name; `nil` or no second argument removes the map. Ends the script when the texture cannot be uploaded.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -7939,7 +7939,7 @@ materialSetTexture(material, nil)
|
||||||
materialSetTexture(material)
|
materialSetTexture(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
Uses a sprite's image as the base color texture (a copy is uploaded, so the sprite may be unloaded afterwards), or a KTX2 file by name, kept compressed on the GPU in the block format the machine has (see Compressed textures in <<scenes3d,3D Scenes>>). With `nil` or no second argument the texture is removed and the material shows its base color alone. Setting a texture also drops any video or view the material was showing. Texture coordinates come from the mesh; the primitives map an image once across each face, and `materialSetTiling` repeats it. A name that is not a KTX2 file raises an error (load other pictures with `spriteLoad`), as does a failed upload.
|
Uses a sprite's image as the base color texture (a copy is uploaded, so the sprite may be unloaded afterwards), or a KTX2 file by name, kept compressed on the GPU in the block format the machine has (see Compressed textures in <<scenes3d,3D Scenes>>). With `nil` or no second argument the texture is removed and the material shows its base color alone. Setting a texture also drops any video or view the material was showing. Texture coordinates come from the mesh; the primitives map an image once across each face, and `materialSetTiling` repeats it. A name that is not a KTX2 file ends the script (load other pictures with `spriteLoad`), as does a failed upload.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<spriteload,spriteLoad>>, <<materialsettiling,materialSetTiling>>, <<materialsetvideo,materialSetVideo>>, <<materialsetview,materialSetView>>
|
*See also:* <<spriteload,spriteLoad>>, <<materialsettiling,materialSetTiling>>, <<materialsetvideo,materialSetVideo>>, <<materialsetview,materialSetView>>
|
||||||
|
|
@ -8027,7 +8027,7 @@ materialSetView(material, nil)
|
||||||
materialSetView(material)
|
materialSetView(material)
|
||||||
----
|
----
|
||||||
|
|
||||||
Shows a rendered view from `viewNew` as the material's base color texture, updated every frame: a security monitor, a rear-view mirror, a portal. Usually paired with `materialSetUnlit` so the picture is not shaded by the lights, and tinted by the base color like any texture. With `nil` or no second argument the material goes back to its own texture, which is kept underneath. Setting a view drops any video the material was showing. Raises an error for a view handle that is not valid.
|
Shows a rendered view from `viewNew` as the material's base color texture, updated every frame: a security monitor, a rear-view mirror, a portal. Usually paired with `materialSetUnlit` so the picture is not shaded by the lights, and tinted by the base color like any texture. With `nil` or no second argument the material goes back to its own texture, which is kept underneath. Setting a view drops any video the material was showing. Ends the script for a view handle that is not valid.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<viewnew,viewNew>>, <<viewsetcamera,viewSetCamera>>, <<materialsetvideo,materialSetVideo>>, <<materialsetunlit,materialSetUnlit>>
|
*See also:* <<viewnew,viewNew>>, <<viewsetcamera,viewSetCamera>>, <<materialsetvideo,materialSetVideo>>, <<materialsetunlit,materialSetUnlit>>
|
||||||
|
|
@ -8058,7 +8058,7 @@ materialSetVideo(material, video)
|
||||||
|
|
||||||
Uses the laserdisc (no `video`) or a video loaded with `videoLoad` as the base color texture, updated every frame while it plays. Usually paired with `materialSetUnlit` so the picture is not tinted by the lights. The video keeps playing whether or not it is also drawn on the overlay, and several materials may show the same one. Setting a video drops any texture or view the material had; `materialSetTexture` takes it off again.
|
Uses the laserdisc (no `video`) or a video loaded with `videoLoad` as the base color texture, updated every frame while it plays. Usually paired with `materialSetUnlit` so the picture is not tinted by the lights. The video keeps playing whether or not it is also drawn on the overlay, and several materials may show the same one. Setting a video drops any texture or view the material had; `materialSetTexture` takes it off again.
|
||||||
|
|
||||||
*Notes:* The disc form raises an error in a game without a disc; a bad video handle raises one too.
|
*Notes:* The disc form ends the script in a game without a disc, and so does a bad video handle.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<videoload,videoLoad>>, <<materialsetunlit,materialSetUnlit>>, <<materialsettexture,materialSetTexture>>
|
*See also:* <<videoload,videoLoad>>, <<materialsetunlit,materialSetUnlit>>, <<materialsettexture,materialSetTexture>>
|
||||||
|
|
@ -9162,7 +9162,7 @@ end
|
||||||
modelDelete(model)
|
modelDelete(model)
|
||||||
----
|
----
|
||||||
|
|
||||||
Frees the model's meshes, materials and parsed data. Every instance made from it is forgotten at the same time: its nodes stay in the scene (position, parent and children intact) but draw nothing, its animations stop where they are, and a later `animation*` call on its root raises an error because the node is no longer a model instance. Delete the instance roots with `nodeDelete` when the nodes are not wanted either. A handle that is not a loaded model raises an error.
|
Frees the model's meshes, materials and parsed data. Every instance made from it is forgotten at the same time: its nodes stay in the scene (position, parent and children intact) but draw nothing, its animations stop where they are, and a later `animation*` call on its root ends the script because the node is no longer a model instance. Delete the instance roots with `nodeDelete` when the nodes are not wanted either. A handle that is not a loaded model ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<modelload,modelLoad>>, <<modelinstance,modelInstance>>, <<nodedelete,nodeDelete>>
|
*See also:* <<modelload,modelLoad>>, <<modelinstance,modelInstance>>, <<nodedelete,nodeDelete>>
|
||||||
|
|
@ -9189,7 +9189,7 @@ end
|
||||||
names = modelGetAnimations(model)
|
names = modelGetAnimations(model)
|
||||||
----
|
----
|
||||||
|
|
||||||
Lists the model's animation clips in file order as a table of strings, so `names[3]` is the clip that `animationPlay(node, 3)` plays. A clip the exporter left unnamed gives an empty string; it can still be played by number. A model with no animations gives an empty table. A handle that is not a loaded model raises an error.
|
Lists the model's animation clips in file order as a table of strings, so `names[3]` is the clip that `animationPlay(node, 3)` plays. A clip the exporter left unnamed gives an empty string; it can still be played by number. A model with no animations gives an empty table. A handle that is not a loaded model ends the script.
|
||||||
|
|
||||||
*Returns:* A table of strings indexed from `1`.
|
*Returns:* A table of strings indexed from `1`.
|
||||||
|
|
||||||
|
|
@ -9219,7 +9219,7 @@ node = modelInstance(model)
|
||||||
node = modelInstance(model, parent)
|
node = modelInstance(model, parent)
|
||||||
----
|
----
|
||||||
|
|
||||||
Builds the model's node tree under a new root node, itself under `parent` or the scene root. The root is a plain node: position, rotate, scale or parent it like any other, and delete it with `nodeDelete` to remove the whole instance. The model's own nodes take their glTF names (or their mesh's name when the node is unnamed) so `nodeFind(name, node)` reaches them; a mesh with several materials becomes one child node per part; lights in the file become scene lights with the file's color, intensity, range and cone; skins are attached and morph weights set. The nodes stand in the file's pose until an animation plays. A handle that is not a loaded model or a `parent` that is not a node raises an error, as does running out of nodes.
|
Builds the model's node tree under a new root node, itself under `parent` or the scene root. The root is a plain node: position, rotate, scale or parent it like any other, and delete it with `nodeDelete` to remove the whole instance. The model's own nodes take their glTF names (or their mesh's name when the node is unnamed) so `nodeFind(name, node)` reaches them; a mesh with several materials becomes one child node per part; lights in the file become scene lights with the file's color, intensity, range and cone; skins are attached and morph weights set. The nodes stand in the file's pose until an animation plays. A handle that is not a loaded model or a `parent` that is not a node ends the script, as does running out of nodes.
|
||||||
|
|
||||||
*Returns:* The instance's root node handle.
|
*Returns:* The instance's root node handle.
|
||||||
|
|
||||||
|
|
@ -9761,7 +9761,7 @@ musicUnload(theme)
|
||||||
[#nav]
|
[#nav]
|
||||||
=== Nav
|
=== Nav
|
||||||
|
|
||||||
Navigation meshes baked from scene geometry, and the crowd agents that walk them (see <<scenes3d,3D Scenes>>, Navigation). `navNew` and `navLoad` return a mesh handle and `navAgentNew` an agent handle; both are integers, checked on every call, and an invalid one terminates the script. Positions are world units, speeds units a second and slopes degrees. Up to eight meshes at once, 128 agents on each and 256 in all. The queries and the agents need a mesh that `navBuild` has baked or `navLoad` has read; before that the queries return `nil`, `navDraw` draws nothing and `navAgentNew` raises an error.
|
Navigation meshes baked from scene geometry, and the crowd agents that walk them (see <<scenes3d,3D Scenes>>, Navigation). `navNew` and `navLoad` return a mesh handle and `navAgentNew` an agent handle; both are integers, checked on every call, and an invalid one terminates the script. Positions are world units, speeds units a second and slopes degrees. Up to eight meshes at once, 128 agents on each and 256 in all. The queries and the agents need a mesh that `navBuild` has baked or `navLoad` has read; before that the queries return `nil`, `navDraw` draws nothing and `navAgentNew` ends the script.
|
||||||
|
|
||||||
[#navaddnode]
|
[#navaddnode]
|
||||||
==== navAddNode
|
==== navAddNode
|
||||||
|
|
@ -9771,7 +9771,7 @@ Navigation meshes baked from scene geometry, and the crowd agents that walk them
|
||||||
navAddNode(nav, node)
|
navAddNode(nav, node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Adds the node's mesh, and the mesh of every node under it, to the geometry the navigation mesh will be baked from, as they stand in the world right now, so place them first. A model instance's root node adds the whole model, and a level built from primitives can be added node by node. Only mesh geometry counts: bodies, sprites and text add nothing. Raises an error once the mesh has been built.
|
Adds the node's mesh, and the mesh of every node under it, to the geometry the navigation mesh will be baked from, as they stand in the world right now, so place them first. A model instance's root node adds the whole model, and a level built from primitives can be added node by node. Only mesh geometry counts: bodies, sprites and text add nothing. Ends the script once the mesh has been built.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<navnew,navNew>>, <<navbuild,navBuild>>, <<modelinstance,modelInstance>>
|
*See also:* <<navnew,navNew>>, <<navbuild,navBuild>>, <<modelinstance,modelInstance>>
|
||||||
|
|
@ -9922,7 +9922,7 @@ end
|
||||||
agent = navAgentNew(nav, node, radius, height, speed)
|
agent = navAgentNew(nav, node, radius, height, speed)
|
||||||
----
|
----
|
||||||
|
|
||||||
Puts an agent of that size on the mesh at the point nearest the node's world position (the raw position when nothing is within 2 units sideways and 4 up or down), walking at `speed` units a second and accelerating at 8 units a second squared. The radius and height are kept at `0.01` or more, as `navNew` keeps its own. From then on the engine moves the node along the mesh every frame, in its parent's frame, and turns it about Y to face the way it is going whenever it moves faster than `0.05` units a second; nodes look down -Z, as models do. The crowd steers agents round each other, anticipates turns and keeps them apart. To drive a player controller on the node instead of placing the node, see `navAgentSetPlayer`. Raises an error when the mesh is not built or 128 agents are already on it.
|
Puts an agent of that size on the mesh at the point nearest the node's world position (the raw position when nothing is within 2 units sideways and 4 up or down), walking at `speed` units a second and accelerating at 8 units a second squared. The radius and height are kept at `0.01` or more, as `navNew` keeps its own. From then on the engine moves the node along the mesh every frame, in its parent's frame, and turns it about Y to face the way it is going whenever it moves faster than `0.05` units a second; nodes look down -Z, as models do. The crowd steers agents round each other, anticipates turns and keeps them apart. To drive a player controller on the node instead of placing the node, see `navAgentSetPlayer`. Ends the script when the mesh is not built or 128 agents are already on it.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -10016,7 +10016,7 @@ end
|
||||||
navBuild(nav)
|
navBuild(nav)
|
||||||
----
|
----
|
||||||
|
|
||||||
Bakes the walkable mesh from everything `navAddNode` added: floors and slopes up to the mesh's angle, steps up to its height, with the agent's radius trimmed off round every wall and drop, and anything with less than the agent's height above it left out. The bake works on cells a third of the agent's radius across and covers at most 4096 by 4096 of them, so a small radius over a big level is slow and can fail; a tenth of a second for Sponza on a desktop, a few seconds on a Raspberry Pi. The geometry that was added is freed afterward, and nothing more can be added; bake another mesh instead. `navSave` keeps the result. Raises an error when nothing was added, the mesh is already built, or the bake fails.
|
Bakes the walkable mesh from everything `navAddNode` added: floors and slopes up to the mesh's angle, steps up to its height, with the agent's radius trimmed off round every wall and drop, and anything with less than the agent's height above it left out. The bake works on cells a third of the agent's radius across and covers at most 4096 by 4096 of them, so a small radius over a big level is slow and can fail; a tenth of a second for Sponza on a desktop, a few seconds on a Raspberry Pi. The geometry that was added is freed afterward, and nothing more can be added; bake another mesh instead. `navSave` keeps the result. Ends the script when nothing was added, the mesh is already built, or the bake fails.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<navnew,navNew>>, <<navaddnode,navAddNode>>, <<navsave,navSave>>, <<navdraw,navDraw>>
|
*See also:* <<navnew,navNew>>, <<navaddnode,navAddNode>>, <<navsave,navSave>>, <<navdraw,navDraw>>
|
||||||
|
|
@ -10107,7 +10107,7 @@ end
|
||||||
nav = navLoad(name, agentRadius, agentHeight)
|
nav = navLoad(name, agentRadius, agentHeight)
|
||||||
----
|
----
|
||||||
|
|
||||||
Loads a mesh `navSave` wrote, looked for among the game's files first (so a baked mesh can ship with the game, packed or not) and then in the game's data folder. The mesh comes back built, ready for agents and queries, and takes no more `navAddNode`. The slope and step it was baked with are in the file; the agent radius and height are what the crowd steers with, so give the values it was baked for. Raises an error when the file cannot be read, is not a navigation mesh, is shorter than the mesh its header describes (truncated, or edited by hand), or no mesh slot is free.
|
Loads a mesh `navSave` wrote, looked for among the game's files first (so a baked mesh can ship with the game, packed or not) and then in the game's data folder. The mesh comes back built, ready for agents and queries, and takes no more `navAddNode`. The slope and step it was baked with are in the file; the agent radius and height are what the crowd steers with, so give the values it was baked for. Ends the script when the file cannot be read, is not a navigation mesh, is shorter than the mesh its header describes (truncated, or edited by hand), or no mesh slot is free.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -10175,7 +10175,7 @@ end
|
||||||
nav = navNew(agentRadius, agentHeight, maxSlope, maxStep)
|
nav = navNew(agentRadius, agentHeight, maxSlope, maxStep)
|
||||||
----
|
----
|
||||||
|
|
||||||
Starts a navigation mesh for agents of that radius and height that can climb steps up to `maxStep` units and slopes up to `maxSlope` degrees. The radius and height are kept at `0.01` or more, the slope between `0` and `89` and the step at `0` or more. The radius also sets the bake's resolution, a cell being a third of it, so it decides how long `navBuild` takes and how big a level it can cover. Add geometry with `navAddNode`, then `navBuild`. Raises an error when eight meshes already exist.
|
Starts a navigation mesh for agents of that radius and height that can climb steps up to `maxStep` units and slopes up to `maxSlope` degrees. The radius and height are kept at `0.01` or more, the slope between `0` and `89` and the step at `0` or more. The radius also sets the bake's resolution, a cell being a third of it, so it decides how long `navBuild` takes and how big a level it can cover. Add geometry with `navAddNode`, then `navBuild`. Ends the script when eight meshes already exist.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -10306,7 +10306,7 @@ end
|
||||||
navSave(nav, name)
|
navSave(nav, name)
|
||||||
----
|
----
|
||||||
|
|
||||||
Writes the baked mesh to the game's data folder under `name`, for `navLoad` next time, or to copy into the game directory to ship. The mesh must be built. Raises an error when it is not, or when the file cannot be written.
|
Writes the baked mesh to the game's data folder under `name`, for `navLoad` next time, or to copy into the game directory to ship. The mesh must be built. Ends the script when it is not, or when the file cannot be written.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<navload,navLoad>>, <<navbuild,navBuild>>
|
*See also:* <<navload,navLoad>>, <<navbuild,navBuild>>
|
||||||
|
|
@ -12109,7 +12109,7 @@ end
|
||||||
[#player]
|
[#player]
|
||||||
=== Player
|
=== Player
|
||||||
|
|
||||||
A player is a character controller on a node: where a body goes where forces send it, a player goes where the script says, walking floors, climbing steps, sliding along walls, riding platforms and shoving light bodies aside. Players are addressed by their node, and a node carries a body or a player, not both. Sizes and speeds are in world units (meters in a 3D scene, overlay pixels in a 2D world) and the controller's tolerances scale with the shape, so a 30 pixel capsule behaves like a 0.3 meter one. The player drives its node's position each step and leaves the rotation to the script. Every call here raises an error when the node has no player. See <<physics,Physics>> in Game Development, Players.
|
A player is a character controller on a node: where a body goes where forces send it, a player goes where the script says, walking floors, climbing steps, sliding along walls, riding platforms and shoving light bodies aside. Players are addressed by their node, and a node carries a body or a player, not both. Sizes and speeds are in world units (meters in a 3D scene, overlay pixels in a 2D world) and the controller's tolerances scale with the shape, so a 30 pixel capsule behaves like a 0.3 meter one. The player drives its node's position each step and leaves the rotation to the script. Every call here ends the script when the node has no player. See <<physics,Physics>> in Game Development, Players.
|
||||||
|
|
||||||
[#playernew]
|
[#playernew]
|
||||||
==== playerNew
|
==== playerNew
|
||||||
|
|
@ -12124,7 +12124,7 @@ playerNew(node, SHAPE_BOX, width, height, depth)
|
||||||
playerNew(node, SHAPE_HULL)
|
playerNew(node, SHAPE_HULL)
|
||||||
----
|
----
|
||||||
|
|
||||||
Puts a player on the node, where the node is in the world right now: a capsule of that radius and total height, or any convex `bodyNew` shape with its sizes, scaled by the node's world scale. The shape stands on the node's origin, so the node is the feet. It starts with a mass of `70` kilograms, a push of `300` newtons, a slope limit of `45` degrees, a step height of `0.3` scaled by the shape, gravity scale `1` and the default swimming rates. Replaces any body or player the node had. Raises an error for `SHAPE_MESH` (a player needs a convex shape; use `SHAPE_HULL`), when physics is not available on this machine, or when the player cannot be made (a hull with too little mesh, or sixteen players already).
|
Puts a player on the node, where the node is in the world right now: a capsule of that radius and total height, or any convex `bodyNew` shape with its sizes, scaled by the node's world scale. The shape stands on the node's origin, so the node is the feet. It starts with a mass of `70` kilograms, a push of `300` newtons, a slope limit of `45` degrees, a step height of `0.3` scaled by the shape, gravity scale `1` and the default swimming rates. Replaces any body or player the node had. Ends the script for `SHAPE_MESH` (a player needs a convex shape; use `SHAPE_HULL`), when physics is not available on this machine, or when the player cannot be made (a hull with too little mesh, or sixteen players already).
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -12161,7 +12161,7 @@ playerNew(robot, SHAPE_BOX, 0.8, 1.6, 0.6)
|
||||||
playerDelete(node)
|
playerDelete(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Removes the player; the node and whatever hangs under it stay where they are. Deleting the node removes its player too, and so does giving the node a body with `bodyNew`. Raises an error when the node has no player.
|
Removes the player; the node and whatever hangs under it stay where they are. Deleting the node removes its player too, and so does giving the node a body with `bodyNew`. Ends the script when the node has no player.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<playernew,playerNew>>, <<playersetenabled,playerSetEnabled>>
|
*See also:* <<playernew,playerNew>>, <<playersetenabled,playerSetEnabled>>
|
||||||
|
|
@ -12619,7 +12619,7 @@ A skinned model's skeleton handed to physics (see <<physics,Physics>>, Ragdolls)
|
||||||
ragdollNew(node)
|
ragdollNew(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Looks at the node and under it for the first skinned mesh and builds a ragdoll recipe from its skeleton: every joint that has a child joint becomes a bone, a capsule running from the joint toward its children, jointed to the nearest bone above it by a swing-twist joint limited to 45 degrees of swing and 30 of twist. Joints with no child joint, such as fingertips and the tip of a tail, ride along with their parent bone and cannot be named in the other calls. Nothing is simulated yet: the model keeps animating until `ragdollActivate`. Calling it again on the same node discards the old ragdoll first. Raises an error when physics is unavailable, when no skinned mesh with bones is under the node, or when eight ragdolls already exist.
|
Looks at the node and under it for the first skinned mesh and builds a ragdoll recipe from its skeleton: every joint that has a child joint becomes a bone, a capsule running from the joint toward its children, jointed to the nearest bone above it by a swing-twist joint limited to 45 degrees of swing and 30 of twist. Joints with no child joint, such as fingertips and the tip of a tail, ride along with their parent bone and cannot be named in the other calls. Nothing is simulated yet: the model keeps animating until `ragdollActivate`. Calling it again on the same node discards the old ragdoll first. Ends the script when physics is unavailable, when no skinned mesh with bones is under the node, or when eight ragdolls already exist.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<ragdollactivate,ragdollActivate>>, <<ragdollsetjoint,ragdollSetJoint>>, <<modelinstance,modelInstance>>
|
*See also:* <<ragdollactivate,ragdollActivate>>, <<ragdollsetjoint,ragdollSetJoint>>, <<modelinstance,modelInstance>>
|
||||||
|
|
@ -12677,7 +12677,7 @@ end
|
||||||
ragdollSetJoint(node, jointName, radius, swingDegrees, twistDegrees)
|
ragdollSetJoint(node, jointName, radius, swingDegrees, twistDegrees)
|
||||||
----
|
----
|
||||||
|
|
||||||
Tunes one bone's recipe. The values are read when the bodies are made, so call it before `ragdollActivate`; a ragdoll that is already active keeps its bodies until it is deactivated and activated again. The radius `0` asks for the automatic capsule, 0.22 of the bone's length and never thinner than `0.02` units. The swing is the half-angle of the cone the bone may move through relative to its parent, the twist how far it may turn about its own axis; both are clamped to `0` through `179`. Only bones can be named, not the leaf joints; the name is matched without regard to case, and a name with no bone raises an error.
|
Tunes one bone's recipe. The values are read when the bodies are made, so call it before `ragdollActivate`; a ragdoll that is already active keeps its bodies until it is deactivated and activated again. The radius `0` asks for the automatic capsule, 0.22 of the bone's length and never thinner than `0.02` units. The swing is the half-angle of the cone the bone may move through relative to its parent, the twist how far it may turn about its own axis; both are clamped to `0` through `179`. Only bones can be named, not the leaf joints; the name is matched without regard to case, and a name with no bone ends the script.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -12710,7 +12710,7 @@ ragdollActivate(fox)
|
||||||
ragdollActivate(node)
|
ragdollActivate(node)
|
||||||
----
|
----
|
||||||
|
|
||||||
Hands the skeleton to physics from its current pose: every animation layer on the node stops, a dynamic capsule is made for each bone where its joint stands right now, the swing-twist joints are made between them, and from then on each physics step drives the joint nodes from the bodies, so the skinned mesh follows. The bones collide with the world and with each other, except each bone with its parent. The pose at this moment is what `ragdollSetStrength` pulls toward. A ragdoll that is already active is left as it is. Raises an error when a bone's body cannot be made.
|
Hands the skeleton to physics from its current pose: every animation layer on the node stops, a dynamic capsule is made for each bone where its joint stands right now, the swing-twist joints are made between them, and from then on each physics step drives the joint nodes from the bodies, so the skinned mesh follows. The bones collide with the world and with each other, except each bone with its parent. The pose at this moment is what `ragdollSetStrength` pulls toward. A ragdoll that is already active is left as it is. Ends the script when a bone's body cannot be made.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<ragdolldeactivate,ragdollDeactivate>>, <<ragdollapplyimpulse,ragdollApplyImpulse>>, <<ragdollsetstrength,ragdollSetStrength>>
|
*See also:* <<ragdolldeactivate,ragdollDeactivate>>, <<ragdollapplyimpulse,ragdollApplyImpulse>>, <<ragdollsetstrength,ragdollSetStrength>>
|
||||||
|
|
@ -13103,7 +13103,7 @@ The scene is the 3D layer drawn between the disc video and the overlay; nodes pu
|
||||||
sceneEnable(enabled)
|
sceneEnable(enabled)
|
||||||
----
|
----
|
||||||
|
|
||||||
Turns the 3D layer on or off. Nodes, meshes and materials can be built before it is on and survive it being turned off; only drawing stops. Raises an error when turning it on with no usable GPU (see <<scenes3d,3D Scenes>>), so wrap the first call in `pcall` if the game has a 2D fallback. The display refreshes on the next frame either way.
|
Turns the 3D layer on or off. Nodes, meshes and materials can be built before it is on and survive it being turned off; only drawing stops. Ends the script when turning it on with no usable GPU (see <<scenes3d,3D Scenes>>), so a game with a 2D fallback asks `singeCapabilities().SCENE` first. The display refreshes on the next frame either way.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<scenesetbackground,sceneSetBackground>>, <<scenesetantialias,sceneSetAntialias>>
|
*See also:* <<scenesetbackground,sceneSetBackground>>, <<scenesetantialias,sceneSetAntialias>>
|
||||||
|
|
@ -13112,8 +13112,9 @@ Turns the 3D layer on or off. Nodes, meshes and materials can be built before it
|
||||||
[source,lua]
|
[source,lua]
|
||||||
----
|
----
|
||||||
-- Fall back to the plain overlay when 3D is unavailable.
|
-- Fall back to the plain overlay when 3D is unavailable.
|
||||||
has3d = pcall(sceneEnable, true)
|
has3d = singeCapabilities().SCENE
|
||||||
if has3d then
|
if has3d then
|
||||||
|
sceneEnable(true)
|
||||||
buildArena()
|
buildArena()
|
||||||
else
|
else
|
||||||
debugPrint("No 3D here; using the 2D attract mode.")
|
debugPrint("No 3D here; using the 2D attract mode.")
|
||||||
|
|
@ -13499,7 +13500,7 @@ sceneSetFog(r, g, b, near, far)
|
||||||
sceneSetFog()
|
sceneSetFog()
|
||||||
----
|
----
|
||||||
|
|
||||||
Distance fog: clear up to `near` units from the camera, fully the fog color at `far` and beyond, on meshes (unlit ones too) and particles alike; additive particles fade out instead of going gray. With no arguments, or a `far` no greater than `near`, there is no fog. The sky is not fogged, so pick a color close to its horizon, or to the background when there is no sky. Any other number of arguments raises an error.
|
Distance fog: clear up to `near` units from the camera, fully the fog color at `far` and beyond, on meshes (unlit ones too) and particles alike; additive particles fade out instead of going gray. With no arguments, or a `far` no greater than `near`, there is no fog. The sky is not fogged, so pick a color close to its horizon, or to the background when there is no sky. Any other number of arguments ends the script.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -13610,7 +13611,7 @@ sceneSetSky(nil)
|
||||||
sceneSetSky()
|
sceneSetSky()
|
||||||
----
|
----
|
||||||
|
|
||||||
Wraps the scene in an equirectangular panorama: a Radiance `.hdr` (as HDRI sites ship, keeping its range), or a PNG, JPEG or other image SDL_image reads, decoded from sRGB. The name is game relative like `spriteLoad`. The sky is drawn behind everything in place of the background color (so the video no longer shows through) and lights the scene unless `sceneSetEnvironment(false)`. Its center faces -Z and its top is up. It is resampled into a cube of up to 1024 texels a face, so a 2048 by 1024 image is plenty and larger ones take a moment to prepare on a Raspberry Pi. With no argument, or `nil`, the sky is removed. Raises an error when the file cannot be read or 3D is unavailable.
|
Wraps the scene in an equirectangular panorama: a Radiance `.hdr` (as HDRI sites ship, keeping its range), or a PNG, JPEG or other image SDL_image reads, decoded from sRGB. The name is game relative like `spriteLoad`. The sky is drawn behind everything in place of the background color (so the video no longer shows through) and lights the scene unless `sceneSetEnvironment(false)`. Its center faces -Z and its top is up. It is resampled into a cube of up to 1024 texels a face, so a 2048 by 1024 image is plenty and larger ones take a moment to prepare on a Raspberry Pi. With no argument, or `nil`, the sky is removed. Ends the script when the file cannot be read or 3D is unavailable.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<scenesetskyintensity,sceneSetSkyIntensity>>, <<scenesetenvironment,sceneSetEnvironment>>, <<scenesetfog,sceneSetFog>>, <<scenesetbackground,sceneSetBackground>>
|
*See also:* <<scenesetskyintensity,sceneSetSkyIntensity>>, <<scenesetenvironment,sceneSetEnvironment>>, <<scenesetfog,sceneSetFog>>, <<scenesetbackground,sceneSetBackground>>
|
||||||
|
|
@ -13665,7 +13666,7 @@ end
|
||||||
sceneSetTonemap(curve)
|
sceneSetTonemap(curve)
|
||||||
----
|
----
|
||||||
|
|
||||||
The curve that maps the scene's linear light to the screen, applied after exposure. `TONEMAP_NEUTRAL` (the default) keeps colors faithful and compresses only the brightest light; `TONEMAP_ACES` is the filmic curve with more contrast and a warmer roll-off; `TONEMAP_NONE` clips at white. Video materials, unlit surfaces and the sky pass through the same curve, so a game whose disc must look exactly as filmed wants `TONEMAP_NONE` with an exposure of `0`. Any other value raises an error.
|
The curve that maps the scene's linear light to the screen, applied after exposure. `TONEMAP_NEUTRAL` (the default) keeps colors faithful and compresses only the brightest light; `TONEMAP_ACES` is the filmic curve with more contrast and a warmer roll-off; `TONEMAP_NONE` clips at white. Video materials, unlit surfaces and the sky pass through the same curve, so a game whose disc must look exactly as filmed wants `TONEMAP_NONE` with an exposure of `0`. Any other value ends the script.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<scenesetexposure,sceneSetExposure>>, <<scenesetbloom,sceneSetBloom>>
|
*See also:* <<scenesetexposure,sceneSetExposure>>, <<scenesetbloom,sceneSetBloom>>
|
||||||
|
|
@ -14813,7 +14814,7 @@ softNew(node, SOFT_BODY)
|
||||||
softNew(node, SOFT_ROPE, x, y, z, segments, radius)
|
softNew(node, SOFT_ROPE, x, y, z, segments, radius)
|
||||||
----
|
----
|
||||||
|
|
||||||
For `SOFT_CLOTH` and `SOFT_BODY` the node's mesh, of three vertices or more, becomes the particles: vertices at the same position (within `0.0001` units) are welded into one, placed where the node's transform puts them in the world, and the triangles give the stretch, shear and bend constraints. Cloth collides on both faces and is what an open sheet wants; a body is a closed mesh that `softSetPressure` can inflate. For `SOFT_ROPE` the node needs no mesh: `segments + 1` particles run in a line from the node's world position to the world point, the node is given a new six-sided tube mesh of that radius round them (keeping whatever material it had), and the particles collide as spheres of that radius, where cloth and body particles are spheres of radius `0.03`. Every kind starts with stretch `0.9`, bend `0.2`, mass `1` kilogram, damping `0.1` and pressure `0`. A soft body already on the node is removed before the new one is made, so a call that fails leaves the node with none; a rope's tube mesh goes with it, which is why a rope's node cannot become cloth or a body (the call fails for want of a mesh). Raises an error when physics is unavailable, the kind is unknown, the argument count does not fit the kind, the mesh is missing, a rope has no segments or no radius, or 16 soft bodies already exist.
|
For `SOFT_CLOTH` and `SOFT_BODY` the node's mesh, of three vertices or more, becomes the particles: vertices at the same position (within `0.0001` units) are welded into one, placed where the node's transform puts them in the world, and the triangles give the stretch, shear and bend constraints. Cloth collides on both faces and is what an open sheet wants; a body is a closed mesh that `softSetPressure` can inflate. For `SOFT_ROPE` the node needs no mesh: `segments + 1` particles run in a line from the node's world position to the world point, the node is given a new six-sided tube mesh of that radius round them (keeping whatever material it had), and the particles collide as spheres of that radius, where cloth and body particles are spheres of radius `0.03`. Every kind starts with stretch `0.9`, bend `0.2`, mass `1` kilogram, damping `0.1` and pressure `0`. A soft body already on the node is removed before the new one is made, so a call that fails leaves the node with none; a rope's tube mesh goes with it, which is why a rope's node cannot become cloth or a body (the call fails for want of a mesh). Ends the script when physics is unavailable, the kind is unknown, the argument count does not fit the kind, the mesh is missing, a rope has no segments or no radius, or 16 soft bodies already exist.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -16461,7 +16462,7 @@ spriteSetBlend(id, SPRITE_ALPHA | SPRITE_ADD | SPRITE_MULTIPLY)
|
||||||
|
|
||||||
How every draw of the sprite lays it over what is already on the overlay. `SPRITE_ALPHA`, the default, draws it over by its alpha. `SPRITE_ADD` adds its colour, scaled by its alpha, to what is there, so it can only brighten: glows, light, sparks, and the shadebobs of the old demos, where a ball drawn over itself again and again burns to white. `SPRITE_MULTIPLY` darkens what is there by its colour, for shadows and tinted glass.
|
How every draw of the sprite lays it over what is already on the overlay. `SPRITE_ALPHA`, the default, draws it over by its alpha. `SPRITE_ADD` adds its colour, scaled by its alpha, to what is there, so it can only brighten: glows, light, sparks, and the shadebobs of the old demos, where a ball drawn over itself again and again burns to white. `SPRITE_MULTIPLY` darkens what is there by its colour, for shadows and tinted glass.
|
||||||
|
|
||||||
Added and multiplied pixels keep the overlay's own alpha, so they show only where something is already drawn on the overlay: over a transparent overlay, above a video or the 3D scene, they change nothing. Draw something opaque first, or use `emitterSetBlend` with `PARTICLE_ADD`, which adds over everything. Anything other than the three constants raises an error.
|
Added and multiplied pixels keep the overlay's own alpha, so they show only where something is already drawn on the overlay: over a transparent overlay, above a video or the 3D scene, they change nothing. Draw something opaque first, or use `emitterSetBlend` with `PARTICLE_ADD`, which adds over everything. Anything other than the three constants ends the script.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -16982,7 +16983,7 @@ Heightmap ground made with `meshHeightmap`, which turns a grayscale image into a
|
||||||
height = terrainGetHeight(node, x, z)
|
height = terrainGetHeight(node, x, z)
|
||||||
----
|
----
|
||||||
|
|
||||||
The world height of the heightmap mesh on `node` at world `x, z`, interpolated between the nearest four samples, or `nil` beyond its edges or when the node's mesh is not a heightmap. The node's world position and scale count; its rotation is ignored, since terrains lie flat. Use it for placing trees, spawning things on the ground, or a camera that follows the surface, without the cost of a ray cast. Raises an error for a node handle that is not valid.
|
The world height of the heightmap mesh on `node` at world `x, z`, interpolated between the nearest four samples, or `nil` beyond its edges or when the node's mesh is not a heightmap. The node's world position and scale count; its rotation is ignored, since terrains lie flat. Use it for placing trees, spawning things on the ground, or a camera that follows the surface, without the cost of a ray cast. Ends the script for a node handle that is not valid.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -17236,7 +17237,7 @@ Cars, motorcycles, tanks and boats built on a chassis body (see <<physics,Physic
|
||||||
vehicleNew(node, kind)
|
vehicleNew(node, kind)
|
||||||
----
|
----
|
||||||
|
|
||||||
Turns the dynamic body on `node` into the chassis of a vehicle. A car, motorcycle or tank needs at least two wheels from `vehicleAddWheel` before the next physics step builds it; a boat has no wheels and is pushed by a propeller that only works under a surface made with `bodySetWater`. The new vehicle starts from the defaults: a 500 Nm engine peaking at 6000 rpm and idling at 1000, Jolt's five-speed automatic gearbox, 35 degrees of steering lock, 1500 Nm brakes and a 4000 Nm hand brake, 1.5 Hz suspension at 0.5 damping and no anti-roll bars. Calling it again on the same node discards the old vehicle and its wheels first. Raises an error for an unknown kind, a node whose body is not dynamic, a wheeled kind in a 2D world, or when 16 vehicles already exist.
|
Turns the dynamic body on `node` into the chassis of a vehicle. A car, motorcycle or tank needs at least two wheels from `vehicleAddWheel` before the next physics step builds it; a boat has no wheels and is pushed by a propeller that only works under a surface made with `bodySetWater`. The new vehicle starts from the defaults: a 500 Nm engine peaking at 6000 rpm and idling at 1000, Jolt's five-speed automatic gearbox, 35 degrees of steering lock, 1500 Nm brakes and a 4000 Nm hand brake, 1.5 Hz suspension at 0.5 damping and no anti-roll bars. Calling it again on the same node discards the old vehicle and its wheels first. Ends the script for an unknown kind, a node whose body is not dynamic, a wheeled kind in a 2D world, or when 16 vehicles already exist.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -17297,7 +17298,7 @@ end
|
||||||
index = vehicleAddWheel(node, wheelNode, radius, width, suspension)
|
index = vehicleAddWheel(node, wheelNode, radius, width, suspension)
|
||||||
----
|
----
|
||||||
|
|
||||||
Adds a wheel to the vehicle where `wheelNode` stands relative to the chassis when the vehicle is next built. The suspension hangs the wheel `suspension` units below that point when unloaded and compresses to half that. From then on the engine writes the wheel node's world transform after every step, with X as the axle and Y up, so the visible tire belongs on a child node: a `meshCylinder` turned 90 degrees about Z. Wheels ahead of the average wheel position along the nose count as front wheels: they steer by default and carry no hand brake. A tank sorts its wheels onto the left or right track by which side of the chassis they sit on and needs at least one on each side. Values below `0.001` are raised to it. Raises an error at the 17th wheel.
|
Adds a wheel to the vehicle where `wheelNode` stands relative to the chassis when the vehicle is next built. The suspension hangs the wheel `suspension` units below that point when unloaded and compresses to half that. From then on the engine writes the wheel node's world transform after every step, with X as the axle and Y up, so the visible tire belongs on a child node: a `meshCylinder` turned 90 degrees about Z. Wheels ahead of the average wheel position along the nose count as front wheels: they steer by default and carry no hand brake. A tank sorts its wheels onto the left or right track by which side of the chassis they sit on and needs at least one on each side. Values below `0.001` are raised to it. Ends the script at the 17th wheel.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -17370,7 +17371,7 @@ vehicleSetRudder(raft, 300)
|
||||||
vehicleSetWheel(node, index, steered, driven)
|
vehicleSetWheel(node, index, steered, driven)
|
||||||
----
|
----
|
||||||
|
|
||||||
Says whether one wheel turns with the steering input and whether the engine drives it, replacing the defaults for that wheel: front wheels steer, and every wheel drives except a motorcycle's front one. Driven wheels pair up across the chassis into differentials, one per axle, and the engine's torque is shared equally between the axles; a driven wheel with no partner is an axle of its own. A tank ignores both flags, since its tracks drive every wheel and turn the hull. Takes effect at the next step's rebuild. Raises an error for an index the vehicle has no wheel for.
|
Says whether one wheel turns with the steering input and whether the engine drives it, replacing the defaults for that wheel: front wheels steer, and every wheel drives except a motorcycle's front one. Driven wheels pair up across the chassis into differentials, one per axle, and the engine's torque is shared equally between the axles; a driven wheel with no partner is an axle of its own. A tank ignores both flags, since its tracks drive every wheel and turn the hull. Takes effect at the next step's rebuild. Ends the script for an index the vehicle has no wheel for.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -17444,7 +17445,7 @@ vehicleSetGears(node, { ratio, ... }, reverseRatio)
|
||||||
vehicleSetGears(node, { ratio, ... }, reverseRatio, automatic)
|
vehicleSetGears(node, { ratio, ... }, reverseRatio, automatic)
|
||||||
----
|
----
|
||||||
|
|
||||||
Sets the gearbox: one to eight forward ratios, first gear first, the reverse ratio (default `2.9`, given as a positive number) and whether the box shifts by itself (default `true`). A vehicle that never gets this call runs Jolt's five-speed, `2.66`, `1.78`, `1.3`, `1.0` and `0.74`, with the same reverse. With `automatic` false the box no longer shifts on its own, and there is no call to shift it from script. A boat has no gearbox and ignores the call. Takes effect at the next step's rebuild. Raises an error when the table is empty or holds more than eight ratios.
|
Sets the gearbox: one to eight forward ratios, first gear first, the reverse ratio (default `2.9`, given as a positive number) and whether the box shifts by itself (default `true`). A vehicle that never gets this call runs Jolt's five-speed, `2.66`, `1.78`, `1.3`, `1.0` and `0.74`, with the same reverse. With `automatic` false the box no longer shifts on its own, and there is no call to shift it from script. A boat has no gearbox and ignores the call. Takes effect at the next step's rebuild. Ends the script when the table is empty or holds more than eight ratios.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -17812,7 +17813,7 @@ end
|
||||||
[#view]
|
[#view]
|
||||||
=== View
|
=== View
|
||||||
|
|
||||||
A view is a second camera rendered to a texture every frame, for a monitor, a mirror or a portal in the scene: `viewNew` makes one and returns an integer handle, `viewSetCamera` points it at a node, and `materialSetView` shows it on a material. Up to four exist at once; each renders the whole scene again at its own size, in overlay-independent pixels, with the main camera's projection, the frame's shadows (a directional light's cascades included: each point takes the finest cascade that covers it) and no bloom, so keep them few and small. Every view turns billboards (`nodeSetBillboard`) to its own camera and sorts blended draws back to front from it, so a view whose camera looks from elsewhere still sees billboards face on and transparent objects in the right order. A bad handle raises an error. See <<scenes3d,3D Scenes>>.
|
A view is a second camera rendered to a texture every frame, for a monitor, a mirror or a portal in the scene: `viewNew` makes one and returns an integer handle, `viewSetCamera` points it at a node, and `materialSetView` shows it on a material. Up to four exist at once; each renders the whole scene again at its own size, in overlay-independent pixels, with the main camera's projection, the frame's shadows (a directional light's cascades included: each point takes the finest cascade that covers it) and no bloom, so keep them few and small. Every view turns billboards (`nodeSetBillboard`) to its own camera and sorts blended draws back to front from it, so a view whose camera looks from elsewhere still sees billboards face on and transparent objects in the right order. A bad handle ends the script. See <<scenes3d,3D Scenes>>.
|
||||||
|
|
||||||
[#viewdelete]
|
[#viewdelete]
|
||||||
==== viewDelete
|
==== viewDelete
|
||||||
|
|
@ -17822,7 +17823,7 @@ A view is a second camera rendered to a texture every frame, for a monitor, a mi
|
||||||
viewDelete(view)
|
viewDelete(view)
|
||||||
----
|
----
|
||||||
|
|
||||||
Frees the view and its textures, and stops the extra render pass it cost. Materials showing it go back to their own texture. The handle is invalid afterwards and raises an error if used again; since only four views exist at once, delete the ones a level no longer needs.
|
Frees the view and its textures, and stops the extra render pass it cost. Materials showing it go back to their own texture. The handle is invalid afterwards and ends the script if used again; since only four views exist at once, delete the ones a level no longer needs.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<viewnew,viewNew>>, <<materialsetview,materialSetView>>
|
*See also:* <<viewnew,viewNew>>, <<materialsetview,materialSetView>>
|
||||||
|
|
@ -17851,7 +17852,7 @@ end
|
||||||
view = viewNew(width, height)
|
view = viewNew(width, height)
|
||||||
----
|
----
|
||||||
|
|
||||||
Makes a camera rendered to a `width` by `height` texture every frame (each clamped to `1` to `4096`), from the default view until `viewSetCamera` names a node, and returns the view handle for `materialSetView`. The view uses the projection from `cameraSetPerspective` or `cameraSetOrthographic` at its own aspect ratio. The scene renders once more per view, so `512` by `384` is plenty for a monitor and a Raspberry Pi wants smaller. Raises an error when 3D is unavailable or four views are already in use.
|
Makes a camera rendered to a `width` by `height` texture every frame (each clamped to `1` to `4096`), from the default view until `viewSetCamera` names a node, and returns the view handle for `materialSetView`. The view uses the projection from `cameraSetPerspective` or `cameraSetOrthographic` at its own aspect ratio. The scene renders once more per view, so `512` by `384` is plenty for a monitor and a Raspberry Pi wants smaller. Ends the script when 3D is unavailable or four views are already in use.
|
||||||
|
|
||||||
*Parameters:*
|
*Parameters:*
|
||||||
|
|
||||||
|
|
@ -17889,7 +17890,7 @@ viewSetCamera(view, nil)
|
||||||
viewSetCamera(view)
|
viewSetCamera(view)
|
||||||
----
|
----
|
||||||
|
|
||||||
The node the view looks from, down its own -Z like the main camera, so its parents' movement counts. With `nil` or no second argument the view goes back to the default view from `(0, 0, 5)` looking at the origin. Parent the node to a car for a rear-view mirror, or reflect the main camera's position about a wall for a mirror in it. Raises an error for a node handle that is not valid.
|
The node the view looks from, down its own -Z like the main camera, so its parents' movement counts. With `nil` or no second argument the view goes back to the default view from `(0, 0, 5)` looking at the origin. Parent the node to a car for a rear-view mirror, or reflect the main camera's position about a wall for a mirror in it. Ends the script for a node handle that is not valid.
|
||||||
|
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<viewnew,viewNew>>, <<cameraset,cameraSet>>, <<nodelookat,nodeLookAt>>
|
*See also:* <<viewnew,viewNew>>, <<cameraset,cameraSet>>, <<nodelookat,nodeLookAt>>
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,16 @@ vertically.
|
||||||
There is a catch in that first code block, and it is the second thing everybody
|
There is a catch in that first code block, and it is the second thing everybody
|
||||||
gets wrong. *The colour is baked in when the sprite is made, not when it is
|
gets wrong. *The colour is baked in when the sprite is made, not when it is
|
||||||
drawn.* `colorForeground` above `fontToSprite` changes the text. Putting it
|
drawn.* `colorForeground` above `fontToSprite` changes the text. Putting it
|
||||||
above `spriteDraw` instead does nothing at all, because a sprite is drawn with
|
above `spriteDraw` instead does nothing at all, because `colorForeground` is a
|
||||||
its own pixels and is never tinted. If your carefully coloured title comes out
|
setting for drawing text and lines, and a sprite is drawn with its own pixels.
|
||||||
white, this is why.
|
If your carefully coloured title comes out white, this is why.
|
||||||
|
|
||||||
|
A sprite can be recoloured after it is made, but by a sprite call, not by
|
||||||
|
`colorForeground`. `spriteSetTint(image, red, green, blue)` multiplies every
|
||||||
|
pixel of the sprite by that colour each time it is drawn, so text rendered in
|
||||||
|
white can be drawn yellow today and red tomorrow without being made again.
|
||||||
|
White is the colour to render in when you mean to tint: anything multiplied
|
||||||
|
by white stays what it was, so the tint is exactly the colour you ask for.
|
||||||
|
|
||||||
=== What Printing Costs
|
=== What Printing Costs
|
||||||
|
|
||||||
|
|
@ -439,7 +446,9 @@ and it removes a whole category of puzzled evening.
|
||||||
and watch what happens behind every string.
|
and watch what happens behind every string.
|
||||||
. *Make the hint yellow.* Move the `colorForeground(140, 200, 255)` line from
|
. *Make the hint yellow.* Move the `colorForeground(140, 200, 255)` line from
|
||||||
above `fontToSprite` to just above the `spriteDraw` that draws `hintImage`,
|
above `fontToSprite` to just above the `spriteDraw` that draws `hintImage`,
|
||||||
and change the numbers. Work out why nothing happens.
|
and change the numbers. Work out why nothing happens. Then put it back, make
|
||||||
|
the hint white instead, and turn it yellow with `spriteSetTint` just before
|
||||||
|
it is drawn.
|
||||||
. *Right align the clock.* The clock is drawn with `fontPrint`, which cannot be
|
. *Right align the clock.* The clock is drawn with `fontPrint`, which cannot be
|
||||||
measured. Get it against the right margin anyway. There is only one way, and
|
measured. Get it against the right margin anyway. There is only one way, and
|
||||||
finding it is the point.
|
finding it is the point.
|
||||||
|
|
@ -491,7 +500,8 @@ thing, several hundred lines away from the `fontUnload` that caused it.
|
||||||
`sprite*` calls can scale and turn. Use it for that, not for speed.
|
`sprite*` calls can scale and turn. Use it for that, not for speed.
|
||||||
* Measuring is how you centre or right align text, because nothing else can
|
* Measuring is how you centre or right align text, because nothing else can
|
||||||
tell you how wide a string will be.
|
tell you how wide a string will be.
|
||||||
* A sprite's colour is fixed when it is made, not when it is drawn.
|
* `colorForeground` reaches text when it is made, never a sprite when it is
|
||||||
|
drawn; `spriteSetTint` is how a sprite is recoloured afterwards.
|
||||||
* `fontUnload` frees a font; the sprites it made survive it.
|
* `fontUnload` frees a font; the sprites it made survive it.
|
||||||
|
|
||||||
=== Next Time
|
=== Next Time
|
||||||
|
|
|
||||||
45
src/model.c
45
src/model.c
|
|
@ -52,6 +52,7 @@
|
||||||
#define POSE_SCALE 4
|
#define POSE_SCALE 4
|
||||||
#define POSE_MORPH 8
|
#define POSE_MORPH 8
|
||||||
#define BASE_LAYER 0
|
#define BASE_LAYER 0
|
||||||
|
#define IMAGE_UPLOADS 2 // One upload of an image as colour (sRGB), one as data
|
||||||
#define CUBIC_SPLINE_STRIDE 3 // Output elements per keyframe: in-tangent, value, out-tangent
|
#define CUBIC_SPLINE_STRIDE 3 // Output elements per keyframe: in-tangent, value, out-tangent
|
||||||
#define CUBIC_SPLINE_IN 0 // Which of the three is which
|
#define CUBIC_SPLINE_IN 0 // Which of the three is which
|
||||||
#define CUBIC_SPLINE_VALUE 1
|
#define CUBIC_SPLINE_VALUE 1
|
||||||
|
|
@ -153,12 +154,17 @@ typedef struct InstanceS {
|
||||||
PoseT *fromPose; // ... and what it fades from
|
PoseT *fromPose; // ... and what it fades from
|
||||||
} InstanceT;
|
} InstanceT;
|
||||||
|
|
||||||
// One glTF image decoded at most once while a model's materials load, however many textures use it.
|
// One glTF image decoded at most once while a model's materials load, however many textures use it,
|
||||||
|
// and uploaded at most once for each way a map reads it: the material and map that took each
|
||||||
|
// upload are kept, and later uses share that texture.
|
||||||
typedef struct CachedImageS {
|
typedef struct CachedImageS {
|
||||||
SDL_Surface *surface;
|
SDL_Surface *surface;
|
||||||
Ktx2ImageT ktx2;
|
Ktx2ImageT ktx2;
|
||||||
bool surfaceTried;
|
bool surfaceTried;
|
||||||
bool ktx2Tried;
|
bool ktx2Tried;
|
||||||
|
int32_t uploadMaterial[IMAGE_UPLOADS];
|
||||||
|
MaterialMapE uploadMap[IMAGE_UPLOADS];
|
||||||
|
int32_t uploadCount;
|
||||||
} CachedImageT;
|
} CachedImageT;
|
||||||
|
|
||||||
typedef struct ImageCacheS {
|
typedef struct ImageCacheS {
|
||||||
|
|
@ -284,7 +290,12 @@ static int32_t _allocSlot(void **items, int32_t *count, int32_t *capacity, size_
|
||||||
// the GPU's block format, or an ordinary image decoded to a surface.
|
// the GPU's block format, or an ordinary image decoded to a surface.
|
||||||
static void _applyTexture(int32_t handle, ImageCacheT *cache, const cgltf_texture_view *view, MaterialMapE map, float strength) {
|
static void _applyTexture(int32_t handle, ImageCacheT *cache, const cgltf_texture_view *view, MaterialMapE map, float strength) {
|
||||||
const cgltf_texture *texture = view->texture;
|
const cgltf_texture *texture = view->texture;
|
||||||
|
const cgltf_image *source;
|
||||||
|
CachedImageT *cached;
|
||||||
SDL_Surface *image;
|
SDL_Surface *image;
|
||||||
|
const Ktx2ImageT *ktx2;
|
||||||
|
bool placed = false;
|
||||||
|
int32_t x;
|
||||||
|
|
||||||
if (texture == NULL) {
|
if (texture == NULL) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -295,22 +306,30 @@ static void _applyTexture(int32_t handle, ImageCacheT *cache, const cgltf_textur
|
||||||
if (view->has_transform) {
|
if (view->has_transform) {
|
||||||
_warn("A texture's KHR_texture_transform is ignored.");
|
_warn("A texture's KHR_texture_transform is ignored.");
|
||||||
}
|
}
|
||||||
if (texture->has_basisu && (texture->basisu_image != NULL)) {
|
source = (texture->has_basisu && (texture->basisu_image != NULL)) ? texture->basisu_image : texture->image;
|
||||||
const Ktx2ImageT *ktx2 = _cachedKtx2(cache, texture->basisu_image);
|
if (source == NULL) {
|
||||||
|
return;
|
||||||
if (ktx2 != NULL) {
|
}
|
||||||
materialSetMap(handle, map, ktx2, strength);
|
// An image already uploaded the way this map reads it is shared rather than sent again.
|
||||||
|
cached = &cache->items[source - cache->images];
|
||||||
|
for (x = 0; x < cached->uploadCount; x++) {
|
||||||
|
if (materialCopyMap(handle, map, cached->uploadMaterial[x], cached->uploadMap[x], strength)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (texture->image == NULL) {
|
if (source == texture->basisu_image) {
|
||||||
return;
|
ktx2 = _cachedKtx2(cache, source);
|
||||||
|
placed = (ktx2 != NULL) && materialSetMap(handle, map, ktx2, strength);
|
||||||
|
} else {
|
||||||
|
image = _cachedSurface(cache, source);
|
||||||
|
placed = (image != NULL) && materialSetMapSurface(handle, map, image, strength);
|
||||||
}
|
}
|
||||||
image = _cachedSurface(cache, texture->image);
|
// None of the uploads so far read it this way, so this one is kept for the next use.
|
||||||
if (image == NULL) {
|
if (placed && (cached->uploadCount < IMAGE_UPLOADS)) {
|
||||||
return;
|
cached->uploadMaterial[cached->uploadCount] = handle;
|
||||||
|
cached->uploadMap[cached->uploadCount] = map;
|
||||||
|
cached->uploadCount++;
|
||||||
}
|
}
|
||||||
materialSetMapSurface(handle, map, image, strength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,12 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
// Windows and macOS have no system EGL headers to build against; SDL carries the Khronos
|
||||||
|
// definitions for that case, so they are used there and Linux keeps its own.
|
||||||
|
#include <SDL3/SDL_platform_defines.h>
|
||||||
|
#if defined(SDL_PLATFORM_WINDOWS) || defined(SDL_PLATFORM_APPLE)
|
||||||
|
#define SDL_USE_BUILTIN_OPENGL_DEFINITIONS
|
||||||
|
#endif
|
||||||
#include <SDL3/SDL_egl.h>
|
#include <SDL3/SDL_egl.h>
|
||||||
#include "renderGlesApi.h"
|
#include "renderGlesApi.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
|
||||||
272
src/scene.c
272
src/scene.c
|
|
@ -52,6 +52,8 @@
|
||||||
#define BLOOM_MIN_SIZE 8 // Smallest level, pixels
|
#define BLOOM_MIN_SIZE 8 // Smallest level, pixels
|
||||||
#define VIEW_SIZE_MAX 4096 // Pixels per side of a rendered view
|
#define VIEW_SIZE_MAX 4096 // Pixels per side of a rendered view
|
||||||
#define TEXTURE_SIZES_STEP 64 // Growth of the texture size table
|
#define TEXTURE_SIZES_STEP 64 // Growth of the texture size table
|
||||||
|
#define TEXTURE_SHARES_STEP 16 // Growth of the shared texture table
|
||||||
|
#define CHUNK_CELLS 64 // A larger grid is drawn and culled in squares this many cells a side
|
||||||
#define DEFAULT_BLOOM_THRESHOLD 1.0f
|
#define DEFAULT_BLOOM_THRESHOLD 1.0f
|
||||||
#define LOW_QUALITY_DIVISOR 2 // --scenequality low renders the scene at half the overlay's size each way
|
#define LOW_QUALITY_DIVISOR 2 // --scenequality low renders the scene at half the overlay's size each way
|
||||||
#define MATERIAL_SAMPLERS 7 // Base, shadows, normal, occlusion, metallic-roughness, emissive, sky
|
#define MATERIAL_SAMPLERS 7 // Base, shadows, normal, occlusion, metallic-roughness, emissive, sky
|
||||||
|
|
@ -191,6 +193,14 @@ typedef struct ShadowS {
|
||||||
float depth[MAX_CASCADES]; // Each cascade's far plane in its own view
|
float depth[MAX_CASCADES]; // Each cascade's far plane in its own view
|
||||||
} ShadowT;
|
} ShadowT;
|
||||||
|
|
||||||
|
// A square of a large grid: its run of the index buffer and its own bounds, so it is culled alone.
|
||||||
|
typedef struct ChunkS {
|
||||||
|
uint32_t firstIndex;
|
||||||
|
uint32_t indexCount;
|
||||||
|
Vec3T boundsMin;
|
||||||
|
Vec3T boundsMax;
|
||||||
|
} ChunkT;
|
||||||
|
|
||||||
typedef struct MeshS {
|
typedef struct MeshS {
|
||||||
SDL_GPUBuffer *vertexBuffer;
|
SDL_GPUBuffer *vertexBuffer;
|
||||||
SDL_GPUBuffer *indexBuffer;
|
SDL_GPUBuffer *indexBuffer;
|
||||||
|
|
@ -212,6 +222,8 @@ typedef struct MeshS {
|
||||||
float sizeY;
|
float sizeY;
|
||||||
float sizeZ;
|
float sizeZ;
|
||||||
uint32_t version; // Stamped from the scene's mesh counter whenever the geometry changes (_cubeHash)
|
uint32_t version; // Stamped from the scene's mesh counter whenever the geometry changes (_cubeHash)
|
||||||
|
ChunkT *chunks; // A large grid's squares, a draw each; NULL draws the mesh whole
|
||||||
|
int32_t chunkCount;
|
||||||
bool skinned;
|
bool skinned;
|
||||||
bool used;
|
bool used;
|
||||||
} MeshT;
|
} MeshT;
|
||||||
|
|
@ -308,6 +320,7 @@ typedef struct DrawS {
|
||||||
Vec3T centre; // World bounding sphere, filled by _boundDraws
|
Vec3T centre; // World bounding sphere, filled by _boundDraws
|
||||||
float radius;
|
float radius;
|
||||||
int32_t skin; // Into the frame's skin matrices (_fillInstances), NO_HANDLE unskinned
|
int32_t skin; // Into the frame's skin matrices (_fillInstances), NO_HANDLE unskinned
|
||||||
|
int32_t chunk; // Into the mesh's chunks, NO_HANDLE for the whole mesh
|
||||||
} DrawT;
|
} DrawT;
|
||||||
|
|
||||||
// Matches SkinUniforms in scene.hlsl.
|
// Matches SkinUniforms in scene.hlsl.
|
||||||
|
|
@ -513,6 +526,10 @@ typedef struct SceneS {
|
||||||
int32_t sizedCount;
|
int32_t sizedCount;
|
||||||
int32_t sizedCapacity;
|
int32_t sizedCapacity;
|
||||||
int64_t textureBytes; // ... which is this
|
int64_t textureBytes; // ... which is this
|
||||||
|
SDL_GPUTexture **sharedTextures; // Textures more than one slot holds, with how many ...
|
||||||
|
int32_t *sharedHolds; // ... besides the first: a release drops one of these first
|
||||||
|
int32_t sharedCount;
|
||||||
|
int32_t sharedCapacity;
|
||||||
Ktx2FormatE compressedFormat;
|
Ktx2FormatE compressedFormat;
|
||||||
int32_t spriteNodeCount;
|
int32_t spriteNodeCount;
|
||||||
int32_t quadMesh; // The unit quad sprites and text draw on, NO_HANDLE until needed
|
int32_t quadMesh; // The unit quad sprites and text draw on, NO_HANDLE until needed
|
||||||
|
|
@ -640,6 +657,7 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderP
|
||||||
static void _drawParticles(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, const CameraFrameT *frame, const FragmentUniformsT *fragmentUniforms);
|
static void _drawParticles(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, const CameraFrameT *frame, const FragmentUniformsT *fragmentUniforms);
|
||||||
static void _drawPost(SDL_GPUCommandBuffer *commands, const CameraFrameT *frame);
|
static void _drawPost(SDL_GPUCommandBuffer *commands, const CameraFrameT *frame);
|
||||||
static void _drawSky(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, const CameraFrameT *frame);
|
static void _drawSky(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, const CameraFrameT *frame);
|
||||||
|
static int32_t _drawsOf(const NodeT *node);
|
||||||
static Vec3T _faceDirection(int32_t face, float s, float t);
|
static Vec3T _faceDirection(int32_t face, float s, float t);
|
||||||
static void _fillInstances(int32_t drawCount);
|
static void _fillInstances(int32_t drawCount);
|
||||||
static void _fillLights(FragmentUniformsT *uniforms);
|
static void _fillLights(FragmentUniformsT *uniforms);
|
||||||
|
|
@ -657,6 +675,7 @@ static int32_t _gridMesh(const float *heights, int32_t columns, int32_t
|
||||||
static uint16_t _half(float value);
|
static uint16_t _half(float value);
|
||||||
static bool _hasMorphs(const NodeT *node, const MeshT *mesh);
|
static bool _hasMorphs(const NodeT *node, const MeshT *mesh);
|
||||||
static SDL_GPUTextureFormat _hdrFormat(void);
|
static SDL_GPUTextureFormat _hdrFormat(void);
|
||||||
|
static void _holdTexture(SDL_GPUTexture *texture);
|
||||||
static bool _isSkinned(const NodeT *node, const MeshT *mesh);
|
static bool _isSkinned(const NodeT *node, const MeshT *mesh);
|
||||||
static void _lathe(SceneVertexT **vertices, int32_t *vertexCount, uint32_t **indices, int32_t *indexCount, float bottomRadius, float topRadius, float height, int32_t segments);
|
static void _lathe(SceneVertexT **vertices, int32_t *vertexCount, uint32_t **indices, int32_t *indexCount, float bottomRadius, float topRadius, float height, int32_t segments);
|
||||||
static float _linear(uint8_t value);
|
static float _linear(uint8_t value);
|
||||||
|
|
@ -665,6 +684,7 @@ static bool _mapIsColour(MaterialMapE map);
|
||||||
static void _matchMorphWeights(NodeT *node);
|
static void _matchMorphWeights(NodeT *node);
|
||||||
static void _materialDefaults(MaterialT *material);
|
static void _materialDefaults(MaterialT *material);
|
||||||
static void _materialPlace(MaterialT *material, MaterialMapE map, SDL_GPUTexture *texture, float strength);
|
static void _materialPlace(MaterialT *material, MaterialMapE map, SDL_GPUTexture *texture, float strength);
|
||||||
|
static SDL_GPUTexture **_materialSlot(MaterialT *material, MaterialMapE map);
|
||||||
static SDL_GPUTexture *_materialTexture(const MaterialT *material);
|
static SDL_GPUTexture *_materialTexture(const MaterialT *material);
|
||||||
static uint32_t _mipLevels(int32_t width, int32_t height);
|
static uint32_t _mipLevels(int32_t width, int32_t height);
|
||||||
static Mat4T _modelOf(const NodeT *node);
|
static Mat4T _modelOf(const NodeT *node);
|
||||||
|
|
@ -913,20 +933,22 @@ static void _attach(int32_t node, int32_t parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A world bounding sphere per draw, for culling and for fitting shadows.
|
// A world bounding sphere per draw, of its chunk when it is one, for culling and for fitting shadows.
|
||||||
static void _boundDraws(int32_t drawCount) {
|
static void _boundDraws(int32_t drawCount) {
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t c;
|
int32_t c;
|
||||||
|
|
||||||
for (x = 0; x < drawCount; x++) {
|
for (x = 0; x < drawCount; x++) {
|
||||||
NodeT *node = &_scene.nodes[_scene.draws[x].node];
|
NodeT *node = &_scene.nodes[_scene.draws[x].node];
|
||||||
MeshT *mesh = &_scene.meshes[node->mesh];
|
MeshT *mesh = &_scene.meshes[node->mesh];
|
||||||
Mat4T model = _modelOf(node);
|
const Vec3T *lo = (_scene.draws[x].chunk != NO_HANDLE) ? &mesh->chunks[_scene.draws[x].chunk].boundsMin : &mesh->boundsMin;
|
||||||
Vec3T drawMin = vec3(0.0f, 0.0f, 0.0f);
|
const Vec3T *hi = (_scene.draws[x].chunk != NO_HANDLE) ? &mesh->chunks[_scene.draws[x].chunk].boundsMax : &mesh->boundsMax;
|
||||||
Vec3T drawMax = vec3(0.0f, 0.0f, 0.0f);
|
Mat4T model = _modelOf(node);
|
||||||
|
Vec3T drawMin = vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
Vec3T drawMax = vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
for (c = 0; c < 8; c++) {
|
for (c = 0; c < 8; c++) {
|
||||||
Vec3T corner = vec3((c & 1) ? mesh->boundsMax.x : mesh->boundsMin.x, (c & 2) ? mesh->boundsMax.y : mesh->boundsMin.y, (c & 4) ? mesh->boundsMax.z : mesh->boundsMin.z);
|
Vec3T corner = vec3((c & 1) ? hi->x : lo->x, (c & 2) ? hi->y : lo->y, (c & 4) ? hi->z : lo->z);
|
||||||
|
|
||||||
corner = mat4TransformPoint(model, corner);
|
corner = mat4TransformPoint(model, corner);
|
||||||
if (c == 0) {
|
if (c == 0) {
|
||||||
|
|
@ -994,6 +1016,9 @@ static int32_t _compareOpaque(const void *a, const void *b) {
|
||||||
if (na->material != nb->material) {
|
if (na->material != nb->material) {
|
||||||
return (na->material < nb->material) ? -1 : 1;
|
return (na->material < nb->material) ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
if (((const DrawT *)a)->chunk != ((const DrawT *)b)->chunk) {
|
||||||
|
return (((const DrawT *)a)->chunk < ((const DrawT *)b)->chunk) ? -1 : 1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2024,7 +2049,13 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i
|
||||||
rgpuBindFragmentSamplers(pass, 0, samplerBindings, MATERIAL_SAMPLERS);
|
rgpuBindFragmentSamplers(pass, 0, samplerBindings, MATERIAL_SAMPLERS);
|
||||||
_scene.statBatches++;
|
_scene.statBatches++;
|
||||||
}
|
}
|
||||||
rgpuDrawIndexedPrimitives(pass, mesh->indexCount, (Uint32)(end - x), 0, 0, 0);
|
if (_scene.draws[index].chunk != NO_HANDLE) {
|
||||||
|
const ChunkT *chunk = &mesh->chunks[_scene.draws[index].chunk];
|
||||||
|
|
||||||
|
rgpuDrawIndexedPrimitives(pass, chunk->indexCount, (Uint32)(end - x), chunk->firstIndex, 0, 0);
|
||||||
|
} else {
|
||||||
|
rgpuDrawIndexedPrimitives(pass, mesh->indexCount, (Uint32)(end - x), 0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2164,6 +2195,15 @@ static void _drawSky(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, co
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// How many draws a node is this frame: none when nothing of it shows, a chunked mesh's chunks, or one.
|
||||||
|
static int32_t _drawsOf(const NodeT *node) {
|
||||||
|
if (!node->used || !node->worldVisible || !meshValid(node->mesh)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return SDL_max(_scene.meshes[node->mesh].chunkCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// The world direction through a cube face at s, t (-1 to 1, t down), in the usual cube map
|
// The world direction through a cube face at s, t (-1 to 1, t down), in the usual cube map
|
||||||
// convention (+X right, +Y up, +Z toward the viewer of a face looking down -Z).
|
// convention (+X right, +Y up, +Z toward the viewer of a face looking down -Z).
|
||||||
static Vec3T _faceDirection(int32_t face, float s, float t) {
|
static Vec3T _faceDirection(int32_t face, float s, float t) {
|
||||||
|
|
@ -2673,12 +2713,18 @@ static void _gatherParticles(Vec3T eye, Vec3T forward) {
|
||||||
static int32_t _gridMesh(const float *heights, int32_t columns, int32_t rows, float sizeX, float sizeY, float sizeZ, bool firstRowFar) {
|
static int32_t _gridMesh(const float *heights, int32_t columns, int32_t rows, float sizeX, float sizeY, float sizeZ, bool firstRowFar) {
|
||||||
SceneVertexT *vertices;
|
SceneVertexT *vertices;
|
||||||
uint32_t *indices;
|
uint32_t *indices;
|
||||||
|
ChunkT *chunks;
|
||||||
int32_t vertexCount = (columns + 1) * (rows + 1);
|
int32_t vertexCount = (columns + 1) * (rows + 1);
|
||||||
int32_t indexCount = columns * rows * 6;
|
int32_t indexCount = columns * rows * 6;
|
||||||
|
int32_t chunksX = (columns + CHUNK_CELLS - 1) / CHUNK_CELLS;
|
||||||
|
int32_t chunksY = (rows + CHUNK_CELLS - 1) / CHUNK_CELLS;
|
||||||
float zStart = firstRowFar ? -sizeZ / 2.0f : sizeZ / 2.0f;
|
float zStart = firstRowFar ? -sizeZ / 2.0f : sizeZ / 2.0f;
|
||||||
float zStep = firstRowFar ? sizeZ : -sizeZ;
|
float zStep = firstRowFar ? sizeZ : -sizeZ;
|
||||||
|
uint32_t cursor = 0;
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
|
int32_t cx;
|
||||||
|
int32_t cy;
|
||||||
int32_t mesh;
|
int32_t mesh;
|
||||||
|
|
||||||
if ((columns < 1) || (rows < 1)) {
|
if ((columns < 1) || (rows < 1)) {
|
||||||
|
|
@ -2686,7 +2732,8 @@ static int32_t _gridMesh(const float *heights, int32_t columns, int32_t rows, fl
|
||||||
}
|
}
|
||||||
vertices = SDL_calloc((size_t)vertexCount, sizeof(SceneVertexT));
|
vertices = SDL_calloc((size_t)vertexCount, sizeof(SceneVertexT));
|
||||||
indices = SDL_calloc((size_t)indexCount, sizeof(uint32_t));
|
indices = SDL_calloc((size_t)indexCount, sizeof(uint32_t));
|
||||||
if ((vertices == NULL) || (indices == NULL)) {
|
chunks = SDL_calloc((size_t)(chunksX * chunksY), sizeof(ChunkT));
|
||||||
|
if ((vertices == NULL) || (indices == NULL) || (chunks == NULL)) {
|
||||||
utilDie("Out of memory making a grid.");
|
utilDie("Out of memory making a grid.");
|
||||||
}
|
}
|
||||||
for (y = 0; y <= rows; y++) {
|
for (y = 0; y <= rows; y++) {
|
||||||
|
|
@ -2699,27 +2746,60 @@ static int32_t _gridMesh(const float *heights, int32_t columns, int32_t rows, fl
|
||||||
vertices[y * (columns + 1) + x] = _vertex(-sizeX / 2.0f + sizeX * u, h, z, 0.0f, 1.0f, 0.0f, u, 0.5f + z / sizeZ);
|
vertices[y * (columns + 1) + x] = _vertex(-sizeX / 2.0f + sizeX * u, h, z, 0.0f, 1.0f, 0.0f, u, 0.5f + z / sizeZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (y = 0; y < rows; y++) {
|
// The cells go in square by square, so each chunk is one run of the index buffer.
|
||||||
for (x = 0; x < columns; x++) {
|
for (cy = 0; cy < chunksY; cy++) {
|
||||||
uint32_t a = (uint32_t)(y * (columns + 1) + x);
|
for (cx = 0; cx < chunksX; cx++) {
|
||||||
uint32_t b = a + 1;
|
ChunkT *chunk = &chunks[cy * chunksX + cx];
|
||||||
uint32_t c = a + (uint32_t)(columns + 1);
|
int32_t x0 = cx * CHUNK_CELLS;
|
||||||
uint32_t d = c + 1;
|
int32_t y0 = cy * CHUNK_CELLS;
|
||||||
uint32_t *tri = &indices[(y * columns + x) * 6];
|
int32_t x1 = SDL_min(x0 + CHUNK_CELLS, columns);
|
||||||
|
int32_t y1 = SDL_min(y0 + CHUNK_CELLS, rows);
|
||||||
|
|
||||||
// Counter-clockwise seen from above: the winding flips with the row direction.
|
chunk->firstIndex = cursor;
|
||||||
tri[0] = a;
|
chunk->boundsMin = vec3(vertices[y0 * (columns + 1) + x0].position[0], vertices[y0 * (columns + 1) + x0].position[1], vertices[y0 * (columns + 1) + x0].position[2]);
|
||||||
tri[1] = firstRowFar ? d : b;
|
chunk->boundsMax = chunk->boundsMin;
|
||||||
tri[2] = firstRowFar ? b : d;
|
for (y = y0; y < y1; y++) {
|
||||||
tri[3] = a;
|
for (x = x0; x < x1; x++) {
|
||||||
tri[4] = firstRowFar ? c : d;
|
uint32_t a = (uint32_t)(y * (columns + 1) + x);
|
||||||
tri[5] = firstRowFar ? d : c;
|
uint32_t b = a + 1;
|
||||||
|
uint32_t c = a + (uint32_t)(columns + 1);
|
||||||
|
uint32_t d = c + 1;
|
||||||
|
uint32_t *tri = &indices[cursor];
|
||||||
|
|
||||||
|
// Counter-clockwise seen from above: the winding flips with the row direction.
|
||||||
|
tri[0] = a;
|
||||||
|
tri[1] = firstRowFar ? d : b;
|
||||||
|
tri[2] = firstRowFar ? b : d;
|
||||||
|
tri[3] = a;
|
||||||
|
tri[4] = firstRowFar ? c : d;
|
||||||
|
tri[5] = firstRowFar ? d : c;
|
||||||
|
cursor += 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The chunk's vertices run one past its last cell on each side.
|
||||||
|
for (y = y0; y <= y1; y++) {
|
||||||
|
for (x = x0; x <= x1; x++) {
|
||||||
|
const float *q = vertices[y * (columns + 1) + x].position;
|
||||||
|
Vec3T p = vec3(q[0], q[1], q[2]);
|
||||||
|
|
||||||
|
chunk->boundsMin = vec3(SDL_min(chunk->boundsMin.x, p.x), SDL_min(chunk->boundsMin.y, p.y), SDL_min(chunk->boundsMin.z, p.z));
|
||||||
|
chunk->boundsMax = vec3(SDL_max(chunk->boundsMax.x, p.x), SDL_max(chunk->boundsMax.y, p.y), SDL_max(chunk->boundsMax.z, p.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunk->indexCount = cursor - chunk->firstIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (heights != NULL) {
|
if (heights != NULL) {
|
||||||
sceneComputeNormals(vertices, vertexCount, indices, indexCount);
|
sceneComputeNormals(vertices, vertexCount, indices, indexCount);
|
||||||
}
|
}
|
||||||
mesh = _addMesh(vertices, vertexCount, indices, indexCount, false);
|
mesh = _addMesh(vertices, vertexCount, indices, indexCount, false);
|
||||||
|
// A grid of one chunk is drawn whole, as every other mesh is.
|
||||||
|
if ((mesh != NO_HANDLE) && (chunksX * chunksY > 1)) {
|
||||||
|
_scene.meshes[mesh].chunks = chunks;
|
||||||
|
_scene.meshes[mesh].chunkCount = chunksX * chunksY;
|
||||||
|
} else {
|
||||||
|
SDL_free(chunks);
|
||||||
|
}
|
||||||
SDL_free(vertices);
|
SDL_free(vertices);
|
||||||
SDL_free(indices);
|
SDL_free(indices);
|
||||||
return mesh;
|
return mesh;
|
||||||
|
|
@ -2773,6 +2853,31 @@ static SDL_GPUTextureFormat _hdrFormat(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// One more slot holds a texture, so it outlives the first release: the texture goes once the last
|
||||||
|
// holder lets go.
|
||||||
|
static void _holdTexture(SDL_GPUTexture *texture) {
|
||||||
|
int32_t x;
|
||||||
|
|
||||||
|
for (x = 0; x < _scene.sharedCount; x++) {
|
||||||
|
if (_scene.sharedTextures[x] == texture) {
|
||||||
|
_scene.sharedHolds[x]++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_scene.sharedCount == _scene.sharedCapacity) {
|
||||||
|
_scene.sharedCapacity += TEXTURE_SHARES_STEP;
|
||||||
|
_scene.sharedTextures = SDL_realloc(_scene.sharedTextures, sizeof(SDL_GPUTexture *) * (size_t)_scene.sharedCapacity);
|
||||||
|
_scene.sharedHolds = SDL_realloc(_scene.sharedHolds, sizeof(int32_t) * (size_t)_scene.sharedCapacity);
|
||||||
|
if ((_scene.sharedTextures == NULL) || (_scene.sharedHolds == NULL)) {
|
||||||
|
utilDie("Out of memory sharing a texture.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_scene.sharedTextures[_scene.sharedCount] = texture;
|
||||||
|
_scene.sharedHolds[_scene.sharedCount] = 1;
|
||||||
|
_scene.sharedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Whether a draw is posed by a skin this frame (which keeps it out of instanced batches).
|
// Whether a draw is posed by a skin this frame (which keeps it out of instanced batches).
|
||||||
static bool _isSkinned(const NodeT *node, const MeshT *mesh) {
|
static bool _isSkinned(const NodeT *node, const MeshT *mesh) {
|
||||||
return mesh->skinned && (node->skinCount > 0);
|
return mesh->skinned && (node->skinCount > 0);
|
||||||
|
|
@ -2910,26 +3015,12 @@ static void _materialDefaults(MaterialT *material) {
|
||||||
// was there, with the strength that goes with the map: the normal map's bump scale or the
|
// was there, with the strength that goes with the map: the normal map's bump scale or the
|
||||||
// occlusion map's blend. A base texture also drops any video feed or view the material showed.
|
// occlusion map's blend. A base texture also drops any video feed or view the material showed.
|
||||||
static void _materialPlace(MaterialT *material, MaterialMapE map, SDL_GPUTexture *texture, float strength) {
|
static void _materialPlace(MaterialT *material, MaterialMapE map, SDL_GPUTexture *texture, float strength) {
|
||||||
SDL_GPUTexture **slot;
|
SDL_GPUTexture **slot = _materialSlot(material, map);
|
||||||
|
|
||||||
switch (map) {
|
if (map == MAP_NORMAL) {
|
||||||
case MAP_NORMAL:
|
material->normalStrength = SDL_max(strength, 0.0f);
|
||||||
slot = &material->normalMap;
|
} else if (map == MAP_OCCLUSION) {
|
||||||
material->normalStrength = SDL_max(strength, 0.0f);
|
material->occlusionStrength = SDL_clamp(strength, 0.0f, 1.0f);
|
||||||
break;
|
|
||||||
case MAP_OCCLUSION:
|
|
||||||
slot = &material->occlusionMap;
|
|
||||||
material->occlusionStrength = SDL_clamp(strength, 0.0f, 1.0f);
|
|
||||||
break;
|
|
||||||
case MAP_METALLIC_ROUGHNESS:
|
|
||||||
slot = &material->metallicRoughnessMap;
|
|
||||||
break;
|
|
||||||
case MAP_EMISSIVE:
|
|
||||||
slot = &material->emissiveMap;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
slot = &material->texture;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (map == MAP_BASE) {
|
if (map == MAP_BASE) {
|
||||||
_releaseMaterialBase(material);
|
_releaseMaterialBase(material);
|
||||||
|
|
@ -2942,6 +3033,23 @@ static void _materialPlace(MaterialT *material, MaterialMapE map, SDL_GPUTexture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Where one of a material's maps is kept.
|
||||||
|
static SDL_GPUTexture **_materialSlot(MaterialT *material, MaterialMapE map) {
|
||||||
|
switch (map) {
|
||||||
|
case MAP_NORMAL:
|
||||||
|
return &material->normalMap;
|
||||||
|
case MAP_OCCLUSION:
|
||||||
|
return &material->occlusionMap;
|
||||||
|
case MAP_METALLIC_ROUGHNESS:
|
||||||
|
return &material->metallicRoughnessMap;
|
||||||
|
case MAP_EMISSIVE:
|
||||||
|
return &material->emissiveMap;
|
||||||
|
default:
|
||||||
|
return &material->texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// What the fragment shader samples for a material: its video feed, its rendered view, its image,
|
// What the fragment shader samples for a material: its video feed, its rendered view, its image,
|
||||||
// or NULL.
|
// or NULL.
|
||||||
static SDL_GPUTexture *_materialTexture(const MaterialT *material) {
|
static SDL_GPUTexture *_materialTexture(const MaterialT *material) {
|
||||||
|
|
@ -3230,6 +3338,19 @@ static void _releaseTexture(SDL_GPUTexture **texture) {
|
||||||
if (*texture == NULL) {
|
if (*texture == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// A shared texture loses a holder, and stays for the others.
|
||||||
|
for (x = 0; x < _scene.sharedCount; x++) {
|
||||||
|
if (_scene.sharedTextures[x] == *texture) {
|
||||||
|
_scene.sharedHolds[x]--;
|
||||||
|
if (_scene.sharedHolds[x] == 0) {
|
||||||
|
_scene.sharedCount--;
|
||||||
|
_scene.sharedTextures[x] = _scene.sharedTextures[_scene.sharedCount];
|
||||||
|
_scene.sharedHolds[x] = _scene.sharedHolds[_scene.sharedCount];
|
||||||
|
}
|
||||||
|
*texture = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (x = 0; x < _scene.sizedCount; x++) {
|
for (x = 0; x < _scene.sizedCount; x++) {
|
||||||
if (_scene.sizedTextures[x] == *texture) {
|
if (_scene.sizedTextures[x] == *texture) {
|
||||||
_scene.textureBytes -= (int64_t)_scene.sizedBytes[x];
|
_scene.textureBytes -= (int64_t)_scene.sizedBytes[x];
|
||||||
|
|
@ -3410,7 +3531,7 @@ static bool _sameBatch(int32_t a, int32_t b, bool shadowPass, const bool *skip)
|
||||||
const NodeT *nb = &_scene.nodes[_scene.draws[b].node];
|
const NodeT *nb = &_scene.nodes[_scene.draws[b].node];
|
||||||
const MeshT *mesh;
|
const MeshT *mesh;
|
||||||
|
|
||||||
if ((na->mesh != nb->mesh) || (na->material != nb->material) || ((skip != NULL) && skip[b])) {
|
if ((na->mesh != nb->mesh) || (na->material != nb->material) || (_scene.draws[a].chunk != _scene.draws[b].chunk) || ((skip != NULL) && skip[b])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mesh = &_scene.meshes[nb->mesh];
|
mesh = &_scene.meshes[nb->mesh];
|
||||||
|
|
@ -4054,6 +4175,26 @@ bool lightSetShadow(int32_t node, bool shadow) {
|
||||||
|
|
||||||
// ===== Materials =====
|
// ===== Materials =====
|
||||||
|
|
||||||
|
// One of another material's maps, the same texture rather than another upload of it, with this
|
||||||
|
// material's strength. False when the source has none there, or when the two maps read colour
|
||||||
|
// differently (the base and emissive maps are sRGB, the rest are data).
|
||||||
|
bool materialCopyMap(int32_t material, MaterialMapE map, int32_t from, MaterialMapE fromMap, float strength) {
|
||||||
|
SDL_GPUTexture *texture;
|
||||||
|
|
||||||
|
if (!materialValid(material) || !materialValid(from) || (_mapIsColour(map) != _mapIsColour(fromMap))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
texture = *_materialSlot(&_scene.materials[from], fromMap);
|
||||||
|
if (texture == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Held before it is placed, so copying a slot onto itself does not release it on the way.
|
||||||
|
_holdTexture(texture);
|
||||||
|
_materialPlace(&_scene.materials[material], map, texture, strength);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A sprite node's private material (its base texture is the sprite's) is the node's to delete.
|
// A sprite node's private material (its base texture is the sprite's) is the node's to delete.
|
||||||
bool materialDelete(int32_t material) {
|
bool materialDelete(int32_t material) {
|
||||||
int32_t x;
|
int32_t x;
|
||||||
|
|
@ -4391,6 +4532,7 @@ bool meshDelete(int32_t mesh) {
|
||||||
SDL_free(_scene.meshes[mesh].heights);
|
SDL_free(_scene.meshes[mesh].heights);
|
||||||
SDL_free(_scene.meshes[mesh].indices);
|
SDL_free(_scene.meshes[mesh].indices);
|
||||||
SDL_free(_scene.meshes[mesh].vertices);
|
SDL_free(_scene.meshes[mesh].vertices);
|
||||||
|
SDL_free(_scene.meshes[mesh].chunks);
|
||||||
if (_scene.meshes[mesh].transfer != NULL) {
|
if (_scene.meshes[mesh].transfer != NULL) {
|
||||||
rgpuReleaseTransferBuffer(_scene.device, _scene.meshes[mesh].transfer);
|
rgpuReleaseTransferBuffer(_scene.device, _scene.meshes[mesh].transfer);
|
||||||
}
|
}
|
||||||
|
|
@ -4650,6 +4792,10 @@ bool meshSetPositions(int32_t mesh, const float *positions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sceneComputeNormals(m->vertices, m->vertexCount, m->indices, (int32_t)m->indexCount);
|
sceneComputeNormals(m->vertices, m->vertexCount, m->indices, (int32_t)m->indexCount);
|
||||||
|
// Chunk bounds no longer hold for moved vertices, so the mesh is culled whole from now on.
|
||||||
|
SDL_free(m->chunks);
|
||||||
|
m->chunks = NULL;
|
||||||
|
m->chunkCount = 0;
|
||||||
_stampMesh(m);
|
_stampMesh(m);
|
||||||
if (_scene.device == NULL) {
|
if (_scene.device == NULL) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -5886,6 +6032,8 @@ void sceneQuit(void) {
|
||||||
SDL_free(_scene.spriteNodes);
|
SDL_free(_scene.spriteNodes);
|
||||||
SDL_free(_scene.sizedTextures);
|
SDL_free(_scene.sizedTextures);
|
||||||
SDL_free(_scene.sizedBytes);
|
SDL_free(_scene.sizedBytes);
|
||||||
|
SDL_free(_scene.sharedTextures);
|
||||||
|
SDL_free(_scene.sharedHolds);
|
||||||
memset(&_scene, 0, sizeof(_scene));
|
memset(&_scene, 0, sizeof(_scene));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5904,6 +6052,7 @@ SDL_Texture *sceneRender(void) {
|
||||||
bool *skip;
|
bool *skip;
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t drawCount;
|
int32_t drawCount;
|
||||||
|
int32_t drawTotal = 0;
|
||||||
int32_t opaqueCount = 0;
|
int32_t opaqueCount = 0;
|
||||||
int32_t blendedStart;
|
int32_t blendedStart;
|
||||||
int32_t slot;
|
int32_t slot;
|
||||||
|
|
@ -5953,31 +6102,44 @@ SDL_Texture *sceneRender(void) {
|
||||||
// Collect what to draw in one walk: opaque draws from the front of the array, sorted for
|
// Collect what to draw in one walk: opaque draws from the front of the array, sorted for
|
||||||
// batching, blended ones from the back, then moved up behind them; each camera orders those
|
// batching, blended ones from the back, then moved up behind them; each camera orders those
|
||||||
// back to front from its own eye as it renders (_orderBlended).
|
// back to front from its own eye as it renders (_orderBlended).
|
||||||
if (_scene.drawCapacity < _scene.nodeCount) {
|
// A chunked mesh is a draw a chunk, so the draws are counted before they are placed.
|
||||||
_scene.draws = SDL_realloc(_scene.draws, sizeof(DrawT) * (size_t)_scene.nodeCount);
|
for (x = 0; x < _scene.nodeCount; x++) {
|
||||||
|
drawTotal += _drawsOf(&_scene.nodes[x]);
|
||||||
|
}
|
||||||
|
if (_scene.drawCapacity < drawTotal) {
|
||||||
|
_scene.draws = SDL_realloc(_scene.draws, sizeof(DrawT) * (size_t)drawTotal);
|
||||||
if (_scene.draws == NULL) {
|
if (_scene.draws == NULL) {
|
||||||
utilDie("Out of memory collecting scene draws.");
|
utilDie("Out of memory collecting scene draws.");
|
||||||
}
|
}
|
||||||
_scene.drawCapacity = _scene.nodeCount;
|
_scene.drawCapacity = drawTotal;
|
||||||
}
|
}
|
||||||
blendedStart = _scene.nodeCount;
|
blendedStart = drawTotal;
|
||||||
for (x = 0; x < _scene.nodeCount; x++) {
|
for (x = 0; x < _scene.nodeCount; x++) {
|
||||||
|
int32_t count = _drawsOf(&_scene.nodes[x]);
|
||||||
|
int32_t c;
|
||||||
|
|
||||||
node = &_scene.nodes[x];
|
node = &_scene.nodes[x];
|
||||||
if (!node->used || !node->worldVisible || !meshValid(node->mesh)) {
|
if (count == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (node->worldFront) {
|
if (node->worldFront) {
|
||||||
frontCount++;
|
frontCount++;
|
||||||
}
|
}
|
||||||
if ((node->material != NO_HANDLE) && _scene.materials[node->material].blend) {
|
for (c = 0; c < count; c++) {
|
||||||
blendedStart--;
|
DrawT *draw;
|
||||||
_scene.draws[blendedStart].node = x;
|
|
||||||
} else {
|
if ((node->material != NO_HANDLE) && _scene.materials[node->material].blend) {
|
||||||
_scene.draws[opaqueCount].node = x;
|
blendedStart--;
|
||||||
opaqueCount++;
|
draw = &_scene.draws[blendedStart];
|
||||||
|
} else {
|
||||||
|
draw = &_scene.draws[opaqueCount];
|
||||||
|
opaqueCount++;
|
||||||
|
}
|
||||||
|
draw->node = x;
|
||||||
|
draw->chunk = (_scene.meshes[node->mesh].chunks != NULL) ? c : NO_HANDLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawCount = opaqueCount + (_scene.nodeCount - blendedStart);
|
drawCount = opaqueCount + (drawTotal - blendedStart);
|
||||||
_scene.opaqueCount = opaqueCount;
|
_scene.opaqueCount = opaqueCount;
|
||||||
qsort(_scene.draws, (size_t)opaqueCount, sizeof(DrawT), _compareOpaque);
|
qsort(_scene.draws, (size_t)opaqueCount, sizeof(DrawT), _compareOpaque);
|
||||||
if (drawCount > opaqueCount) {
|
if (drawCount > opaqueCount) {
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ bool lightSetIntensity(int32_t node, float intensity);
|
||||||
bool lightSetRange(int32_t node, float range);
|
bool lightSetRange(int32_t node, float range);
|
||||||
bool lightSetShadow(int32_t node, bool shadow);
|
bool lightSetShadow(int32_t node, bool shadow);
|
||||||
|
|
||||||
|
bool materialCopyMap(int32_t material, MaterialMapE map, int32_t from, MaterialMapE fromMap, float strength); // Another material's texture, shared rather than uploaded again
|
||||||
bool materialDelete(int32_t material);
|
bool materialDelete(int32_t material);
|
||||||
void materialForgetGui(int32_t gui); // Every material showing this GUI goes back to its texture
|
void materialForgetGui(int32_t gui); // Every material showing this GUI goes back to its texture
|
||||||
int32_t materialNew(void);
|
int32_t materialNew(void);
|
||||||
|
|
|
||||||
|
|
@ -398,7 +398,7 @@ typedef enum InputE {
|
||||||
INPUT_GRAB,
|
INPUT_GRAB,
|
||||||
// Added in Singe 3.00. A new switch goes on the end so the numbers a game already knows do not move.
|
// Added in Singe 3.00. A new switch goes on the end so the numbers a game already knows do not move.
|
||||||
INPUT_MOUSE_DISCONNECT,
|
INPUT_MOUSE_DISCONNECT,
|
||||||
INPUT_RECORD,
|
INPUT_RECORDING,
|
||||||
INPUT_COUNT
|
INPUT_COUNT
|
||||||
} InputE;
|
} InputE;
|
||||||
|
|
||||||
|
|
@ -3686,7 +3686,7 @@ static void _engineAction(InputE engine) {
|
||||||
_global.refreshDisplay = true;
|
_global.refreshDisplay = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case INPUT_RECORD:
|
case INPUT_RECORDING:
|
||||||
// Starts the next singeNNN.mp4, or stops the one being written.
|
// Starts the next singeNNN.mp4, or stops the one being written.
|
||||||
_global.recordRequest = recordIsRecording() ? RECORD_REQUEST_STOP : RECORD_REQUEST_START;
|
_global.recordRequest = recordIsRecording() ? RECORD_REQUEST_STOP : RECORD_REQUEST_START;
|
||||||
_global.refreshDisplay = true;
|
_global.refreshDisplay = true;
|
||||||
|
|
@ -3700,7 +3700,7 @@ static void _engineAction(InputE engine) {
|
||||||
|
|
||||||
// Which engine owned switch (pause, quit, screenshot, grab, record) a code is mapped to, or INPUT_COUNT.
|
// Which engine owned switch (pause, quit, screenshot, grab, record) a code is mapped to, or INPUT_COUNT.
|
||||||
static InputE _engineSwitch(int32_t scancode) {
|
static InputE _engineSwitch(int32_t scancode) {
|
||||||
static const InputE owned[] = { INPUT_PAUSE, INPUT_QUIT, INPUT_SCREENSHOT, INPUT_GRAB, INPUT_RECORD };
|
static const InputE owned[] = { INPUT_PAUSE, INPUT_QUIT, INPUT_SCREENSHOT, INPUT_GRAB, INPUT_RECORDING };
|
||||||
int32_t i = 0;
|
int32_t i = 0;
|
||||||
int32_t index = 0;
|
int32_t index = 0;
|
||||||
|
|
||||||
|
|
|
||||||
1
testScripts/Models/OneImage.gltf
Normal file
1
testScripts/Models/OneImage.gltf
Normal file
File diff suppressed because one or more lines are too long
1
testScripts/Models/SharedImage.gltf
Normal file
1
testScripts/Models/SharedImage.gltf
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
The Lua scripts used to verify the 3D scene and physics work, one per stage
|
The Lua scripts used to verify the 3D scene and physics work, one per stage
|
||||||
of PLAN.md sections 18 and 19, with everything they load: the Khronos
|
of PLAN.md sections 18 and 19, with everything they load: the Khronos
|
||||||
Duck, Fox, BoxAnimated, AnimatedMorphCube and Sponza sample models under Models/, Box.png and crate.png. The
|
Duck, Fox, BoxAnimated, AnimatedMorphCube and Sponza sample models under Models/, Box.png and crate.png, with
|
||||||
|
OneImage.gltf and SharedImage.gltf beside them, made for scene103 (one gradient triangle each), and ridges.png for scene104. The
|
||||||
disc and the font are the engine's own, addressed as
|
disc and the font are the engine's own, addressed as
|
||||||
Singe/menuBackground.mkv and Singe/FreeSansBold.ttf like any engine
|
Singe/menuBackground.mkv and Singe/FreeSansBold.ttf like any engine
|
||||||
asset. This directory is a complete Singe game directory. Run one from the singe directory with
|
asset. This directory is a complete Singe game directory. Run one from the singe directory with
|
||||||
|
|
@ -71,3 +72,5 @@ screenshot or two and quits by itself; the results are in screenshots/.
|
||||||
| scene99.singe | 52 | 50 | Formats: a lossless JPEG XL of crate.png (crate.jxl) drawn beside the PNG, which it must match, and the chiptune and tracker decoders played as music and as a sound (chip.nsf, tracker.xm, tracker.it, tracker.s3m); the run's log says what played |
|
| scene99.singe | 52 | 50 | Formats: a lossless JPEG XL of crate.png (crate.jxl) drawn beside the PNG, which it must match, and the chiptune and tracker decoders played as music and as a sound (chip.nsf, tracker.xm, tracker.it, tracker.s3m); the run's log says what played |
|
||||||
| scene101.singe | 53 | - | Calls no other scene used, over the disc, each checked against its Reference entry and printed as `CALLS ok` or `CALLS FAIL`: `collideRects`, `collidePointRect` and `collidePointPolygon` on known shapes (a concave L among them); `discGetState` through play, pause, `discSkipForward` (which keeps the pause), `discSkipToFrame` (which plays) and stop, with `discChangeSpeed`, `discSetFPS` and `discAudio`; a channel through `soundPause` and `soundResume` with `soundIsPlaying` at each step; `keyboardIsDown` and `keyboardGetModifiers` with nothing held, `mouseSetMode` both ways; `videoPause` holding a loaded video's frame and `videoSetVolume` in both forms; `spriteFlip` (the screenshot shows the arrows plain and mirrored); and `singePack` packing a game it wrote, its script read back through the container |
|
| scene101.singe | 53 | - | Calls no other scene used, over the disc, each checked against its Reference entry and printed as `CALLS ok` or `CALLS FAIL`: `collideRects`, `collidePointRect` and `collidePointPolygon` on known shapes (a concave L among them); `discGetState` through play, pause, `discSkipForward` (which keeps the pause), `discSkipToFrame` (which plays) and stop, with `discChangeSpeed`, `discSetFPS` and `discAudio`; a channel through `soundPause` and `soundResume` with `soundIsPlaying` at each step; `keyboardIsDown` and `keyboardGetModifiers` with nothing held, `mouseSetMode` both ways; `videoPause` holding a loaded video's frame and `videoSetVolume` in both forms; `spriteFlip` (the screenshot shows the arrows plain and mirrored); and `singePack` packing a game it wrote, its script read back through the container |
|
||||||
| scene102.singe | 54 | - | The 3D calls no other scene used, each checked and printed as `SCENE3D ok` or `SCENE3D FAIL`: `nodeSetName` found by `nodeFind`, `bodyApplyForce` lifting a 1 kg crate, `physicsSetEnabled` holding it in the air and letting it fall, `playerSetPosition`, `emitterDelete`, `navNearest` onto the floor and nothing far off, `navAgentStop` halting an agent short, `navAgentIsArrived` once it is sent on and arrives, `navAgentDelete`, and `ragdollDelete` freeing the Fox for a new ragdoll; no disc, -C 720x480 |
|
| scene102.singe | 54 | - | The 3D calls no other scene used, each checked and printed as `SCENE3D ok` or `SCENE3D FAIL`: `nodeSetName` found by `nodeFind`, `bodyApplyForce` lifting a 1 kg crate, `physicsSetEnabled` holding it in the air and letting it fall, `playerSetPosition`, `emitterDelete`, `navNearest` onto the floor and nothing far off, `navAgentStop` halting an agent short, `navAgentIsArrived` once it is sent on and arrives, `navAgentDelete`, and `ragdollDelete` freeing the Fox for a new ragdoll; no disc, -C 720x480 |
|
||||||
|
| scene103.singe | 55 | - | One image, many maps, checked and printed as `SHARED ok` or `SHARED FAIL`: `Models/OneImage.gltf` uses its picture once and sets the size of one upload; `Models/SharedImage.gltf` gives two materials the same picture for all five maps, which `sceneGetStats` has to show as two uploads (colour and data), not ten; `modelDelete` of each has to bring the texture total back to where it started; no disc, -C 720x480 |
|
||||||
|
| scene104.singe | 56 | - | Terrain in chunks, checked and printed as `CHUNKS ok` or `CHUNKS FAIL`: `meshHeightmap` of ridges.png (257 samples a side, made from a formula the scene repeats) is 16 draws in `sceneGetStats`, fewer of them in view with the camera in the middle; a twin built through `meshNew` is one draw; `terrainGetHeight` reads the greyscale samples exactly; screenshots chunked.png and whole.png should match; no disc, -C 720x480 |
|
||||||
|
|
|
||||||
|
|
@ -573,4 +573,24 @@ GAMES = {
|
||||||
DEVELOPER = "Test",
|
DEVELOPER = "Test",
|
||||||
PUBLISHER = "Test",
|
PUBLISHER = "Test",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
TITLE = "Models: One Image, Many Maps",
|
||||||
|
SCRIPT = "testScripts/scene103.singe",
|
||||||
|
DESCRIPTION = "A glTF whose two materials use one picture for all five maps holds two textures of it, one read as colour and one as data, rather than ten; sceneGetStats measures it against a model that uses the picture once, and modelDelete has to give every byte back.",
|
||||||
|
YEAR = 2026,
|
||||||
|
GENRE = "Test",
|
||||||
|
PLATFORM = "Singe",
|
||||||
|
DEVELOPER = "Test",
|
||||||
|
PUBLISHER = "Test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TITLE = "Terrain: Chunks",
|
||||||
|
SCRIPT = "testScripts/scene104.singe",
|
||||||
|
DESCRIPTION = "A 257 by 257 heightmap is drawn as 16 chunks, each culled alone: sceneGetStats counts them and how many are in view, a twin made through meshNew is drawn whole for comparison, and terrainGetHeight reads a greyscale PNG's samples exactly.",
|
||||||
|
YEAR = 2026,
|
||||||
|
GENRE = "Test",
|
||||||
|
PLATFORM = "Singe",
|
||||||
|
DEVELOPER = "Test",
|
||||||
|
PUBLISHER = "Test",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
testScripts/ridges.png
(Stored with Git LFS)
Normal file
BIN
testScripts/ridges.png
(Stored with Git LFS)
Normal file
Binary file not shown.
77
testScripts/scene103.singe
Normal file
77
testScripts/scene103.singe
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
-- One image used by several maps of a glTF is uploaded once for each way the maps read it, not once
|
||||||
|
-- a map: Models/OneImage.gltf uses its 256x256 picture once, as a base colour, which sets the size
|
||||||
|
-- of an upload; Models/SharedImage.gltf has two materials that each use the same picture for all
|
||||||
|
-- five maps, which is two uploads (the base and emissive maps read it as sRGB, the other three as
|
||||||
|
-- data), not ten. Deleting each model has to give every byte back, which proves the shared texture
|
||||||
|
-- outlives the first material to let go of it and goes with the last. Each check prints
|
||||||
|
-- "SHARED ok" or "SHARED FAIL". No disc: run with -C 720x480.
|
||||||
|
dofile("Singe/Framework.singe")
|
||||||
|
|
||||||
|
local font = fontLoad("Singe/FreeSansBold.ttf", 14)
|
||||||
|
local camera = nodeNew()
|
||||||
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
||||||
|
local frames = 0
|
||||||
|
local failures = 0
|
||||||
|
local before = 0
|
||||||
|
local single = 0
|
||||||
|
local model = nil
|
||||||
|
local LAST = 12
|
||||||
|
|
||||||
|
|
||||||
|
local function check(name, ok, detail)
|
||||||
|
if ok then
|
||||||
|
debugPrint("SHARED ok " .. name)
|
||||||
|
else
|
||||||
|
failures = failures + 1
|
||||||
|
debugPrint("SHARED FAIL " .. name .. (detail and (" (" .. detail .. ")") or ""))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Kilobytes of texture the scene holds now.
|
||||||
|
local function textureKb()
|
||||||
|
local _, _, _, kb = sceneGetStats()
|
||||||
|
|
||||||
|
return kb
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
fontSelect(font)
|
||||||
|
overlaySetResolution(720, 480)
|
||||||
|
sceneEnable(true)
|
||||||
|
cameraSet(camera)
|
||||||
|
nodeSetPosition(camera, 0.5, 0.5, 2)
|
||||||
|
nodeLookAt(camera, 0.5, 0.5, 0)
|
||||||
|
nodeLookAt(sun, -1, -2, -1)
|
||||||
|
|
||||||
|
|
||||||
|
function onOverlayUpdate()
|
||||||
|
frames = frames + 1
|
||||||
|
overlayClear()
|
||||||
|
fontPrint(8, 6, string.format("frame %d textures %d KB failures %d", frames, textureKb(), failures))
|
||||||
|
if frames == 2 then
|
||||||
|
before = textureKb()
|
||||||
|
model = modelLoad("testScripts/Models/OneImage.gltf")
|
||||||
|
modelInstance(model)
|
||||||
|
elseif frames == 4 then
|
||||||
|
single = textureKb() - before
|
||||||
|
check("one upload costs something", single > 0, single .. " KB")
|
||||||
|
modelDelete(model)
|
||||||
|
elseif frames == 6 then
|
||||||
|
check("modelDelete gives the one upload back", textureKb() == before, textureKb() .. " KB against " .. before)
|
||||||
|
model = modelLoad("testScripts/Models/SharedImage.gltf")
|
||||||
|
modelInstance(model)
|
||||||
|
elseif frames == 8 then
|
||||||
|
local shared = textureKb() - before
|
||||||
|
|
||||||
|
check("ten maps of one image are two uploads", shared == 2 * single, shared .. " KB against " .. (2 * single))
|
||||||
|
singeScreenshot("shared")
|
||||||
|
modelDelete(model)
|
||||||
|
elseif frames == 10 then
|
||||||
|
check("modelDelete gives the shared uploads back", textureKb() == before, textureKb() .. " KB against " .. before)
|
||||||
|
elseif frames == LAST then
|
||||||
|
debugPrint(string.format("SHARED RESULT failures=%d", failures))
|
||||||
|
singeQuit()
|
||||||
|
end
|
||||||
|
return OVERLAY_UPDATED
|
||||||
|
end
|
||||||
108
testScripts/scene104.singe
Normal file
108
testScripts/scene104.singe
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
-- A large heightmap is drawn in chunks, each culled alone, and looks as it did drawn whole.
|
||||||
|
-- testScripts/ridges.png is 257 samples a side, so meshHeightmap makes 256 by 256 cells in 16
|
||||||
|
-- chunks of 64; a twin built cell for cell through meshNew from the same formula is one mesh
|
||||||
|
-- with no chunks. sceneGetStats has to count 16 draws for the chunked one, fewer of them inside
|
||||||
|
-- the view (the camera stands in the middle, so what is behind it is not drawn), and one for the
|
||||||
|
-- twin. terrainGetHeight has to read the samples exactly, which a greyscale PNG did not until
|
||||||
|
-- the loader's grey ramp was fixed. The screenshots chunked and whole should match: the
|
||||||
|
-- triangles are the same and only the order the normals are summed in differs. Each check prints
|
||||||
|
-- "CHUNKS ok" or "CHUNKS FAIL". No disc: run with -C 720x480.
|
||||||
|
dofile("Singe/Framework.singe")
|
||||||
|
|
||||||
|
local font = fontLoad("Singe/FreeSansBold.ttf", 14)
|
||||||
|
local ground = materialNew()
|
||||||
|
local camera = nodeNew()
|
||||||
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
||||||
|
local chunked = nodeNew()
|
||||||
|
local whole = nodeNew()
|
||||||
|
local frames = 0
|
||||||
|
local failures = 0
|
||||||
|
local SAMPLES = 257
|
||||||
|
local SIZE = 256 -- Across, so every vertex lands on a whole number
|
||||||
|
local HEIGHT = 16
|
||||||
|
local LAST = 10
|
||||||
|
|
||||||
|
|
||||||
|
local function check(name, ok, detail)
|
||||||
|
if ok then
|
||||||
|
debugPrint("CHUNKS ok " .. name)
|
||||||
|
else
|
||||||
|
failures = failures + 1
|
||||||
|
debugPrint("CHUNKS FAIL " .. name .. (detail and (" (" .. detail .. ")") or ""))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- The sample at column x, row y, as the PNG holds it: the same formula made ridges.png.
|
||||||
|
local function sample(x, y)
|
||||||
|
return math.floor((0.5 + 0.25 * math.sin(x * 0.05) + 0.25 * math.cos(y * 0.07)) * 255 + 0.5) / 255
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- The grid meshHeightmap makes, a vertex per sample with row 0 at -Z, wound to face up.
|
||||||
|
local function twinMesh()
|
||||||
|
local positions = {}
|
||||||
|
local indices = {}
|
||||||
|
local cells = SAMPLES - 1
|
||||||
|
|
||||||
|
for y = 0, cells do
|
||||||
|
for x = 0, cells do
|
||||||
|
positions[#positions + 1] = -SIZE / 2 + x
|
||||||
|
positions[#positions + 1] = sample(x, y) * HEIGHT
|
||||||
|
positions[#positions + 1] = -SIZE / 2 + y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for y = 0, cells - 1 do
|
||||||
|
for x = 0, cells - 1 do
|
||||||
|
local a = y * SAMPLES + x + 1
|
||||||
|
local b = a + 1
|
||||||
|
local c = a + SAMPLES
|
||||||
|
local d = c + 1
|
||||||
|
|
||||||
|
for _, index in ipairs({ a, d, b, a, c, d }) do
|
||||||
|
indices[#indices + 1] = index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return meshNew(positions, nil, nil, indices)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
fontSelect(font)
|
||||||
|
overlaySetResolution(720, 480)
|
||||||
|
sceneEnable(true)
|
||||||
|
cameraSet(camera)
|
||||||
|
nodeSetPosition(camera, 0, 30, 0)
|
||||||
|
nodeLookAt(camera, 60, 6, 80)
|
||||||
|
nodeLookAt(sun, -1, -2, -0.5)
|
||||||
|
materialSetColor(ground, 110, 160, 90)
|
||||||
|
nodeSetMesh(chunked, meshHeightmap("testScripts/ridges.png", SIZE, HEIGHT, SIZE), ground)
|
||||||
|
nodeSetMesh(whole, twinMesh(), ground)
|
||||||
|
nodeSetVisible(whole, false)
|
||||||
|
do
|
||||||
|
local got = terrainGetHeight(chunked, -SIZE / 2 + 10, -SIZE / 2 + 20)
|
||||||
|
|
||||||
|
check("terrainGetHeight reads the samples", (got ~= nil) and (math.abs(got - sample(10, 20) * HEIGHT) < 0.001), tostring(got) .. " against " .. (sample(10, 20) * HEIGHT))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function onOverlayUpdate()
|
||||||
|
local total, drawn = sceneGetStats()
|
||||||
|
|
||||||
|
frames = frames + 1
|
||||||
|
overlayClear()
|
||||||
|
if frames == 3 then
|
||||||
|
check("16 chunk draws", total == 16, tostring(total))
|
||||||
|
check("chunks out of view are culled", (drawn > 0) and (drawn < total), drawn .. " of " .. total)
|
||||||
|
singeScreenshot("chunked")
|
||||||
|
nodeSetVisible(chunked, false)
|
||||||
|
nodeSetVisible(whole, true)
|
||||||
|
elseif frames == 6 then
|
||||||
|
check("the twin is one draw", total == 1, tostring(total))
|
||||||
|
singeScreenshot("whole")
|
||||||
|
elseif frames == LAST then
|
||||||
|
debugPrint(string.format("CHUNKS RESULT failures=%d", failures))
|
||||||
|
singeQuit()
|
||||||
|
end
|
||||||
|
return OVERLAY_UPDATED
|
||||||
|
end
|
||||||
8
thirdparty/SDL3_image/src/IMG_libpng.c
vendored
8
thirdparty/SDL3_image/src/IMG_libpng.c
vendored
|
|
@ -479,10 +479,12 @@ static bool LIBPNG_LoadPNG_IO_Internal(SDL_IOStream *src, struct png_load_vars *
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The ramp runs from 0 to 255 inclusive (Singe: it divided by ncolors rather than
|
||||||
|
ncolors - 1, so every 8-bit grey read one step dark and a 1-bit white came out as 127) */
|
||||||
for (int i = 0; i < palette->ncolors; i++) {
|
for (int i = 0; i < palette->ncolors; i++) {
|
||||||
palette->colors[i].r = (i * 255) / palette->ncolors;
|
palette->colors[i].r = (i * 255) / (palette->ncolors - 1);
|
||||||
palette->colors[i].g = (i * 255) / palette->ncolors;
|
palette->colors[i].g = (i * 255) / (palette->ncolors - 1);
|
||||||
palette->colors[i].b = (i * 255) / palette->ncolors;
|
palette->colors[i].b = (i * 255) / (palette->ncolors - 1);
|
||||||
palette->colors[i].a = 255;
|
palette->colors[i].a = 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue