chg: bring back project generators `MAKE proj` (premake5)

chg: bring back most demos/ `MAKE demos`
fix: ui: fixed crash when no cooked fonts were available (ui_notify)
fix: html5: added pthreads support (@zpl-zak)
fix: html5: allowed coi requests in localhost served contents (@zpl-zak)
chg: html5: instantiate web server only if content is not being served
chg: bring back demos/html5/
chg: bring back tools/editor/
chg: bring back some audio tunes
chg: update docs
main
r-lyeh 2023-08-04 21:39:36 +02:00
parent 3b67efc1f5
commit 54aa82aecf
108 changed files with 28724 additions and 273 deletions

121
MAKE.bat
View File

@ -15,9 +15,9 @@ if [ "$1" = "tidy" ]; then
rm 0?-* 2> /dev/null rm 0?-* 2> /dev/null
rm fwk.o 2> /dev/null rm fwk.o 2> /dev/null
rm .art*.zip 2> /dev/null rm .art*.zip 2> /dev/null
rm demos/lua/.art*.zip 2> /dev/null rm engine/bind/.art*.zip 2> /dev/null
rm demos/html5/.art*.zip 2> /dev/null rm demos/html5/.art*.zip 2> /dev/null
rm demos/lua/libfwk* 2> /dev/null rm engine/bind/libfwk* 2> /dev/null
rm fwk_*.* 2> /dev/null rm fwk_*.* 2> /dev/null
rm 3rd_*.* 2> /dev/null rm 3rd_*.* 2> /dev/null
rm libfwk* 2> /dev/null rm libfwk* 2> /dev/null
@ -63,7 +63,7 @@ while [ $# -ge 1 ]; do
echo sh MAKE.bat [tidy] echo sh MAKE.bat [tidy]
echo sh MAKE.bat [split,join] echo sh MAKE.bat [split,join]
echo sh MAKE.bat [cook] echo sh MAKE.bat [cook]
echo sh MAKE.bat [sln] echo sh MAKE.bat [proj]
exit exit
fi fi
if [ "$1" = "dll" ]; then if [ "$1" = "dll" ]; then
@ -93,7 +93,7 @@ while [ $# -ge 1 ]; do
if [ "$1" = "tcc" ]; then if [ "$1" = "tcc" ]; then
export cc="tcc -D__STDC_NO_VLA__" export cc="tcc -D__STDC_NO_VLA__"
fi fi
if [ "$1" = "sln" ]; then if [ "$1" = "proj" ]; then
if [ "$(uname)" != "Darwin" ]; then if [ "$(uname)" != "Darwin" ]; then
chmod +x tools/premake5.linux chmod +x tools/premake5.linux
tools/premake5.linux gmake tools/premake5.linux gmake
@ -147,14 +147,14 @@ if [ "$(uname)" != "Darwin" ]; then
chmod +x tools/xlsx2ini.linux chmod +x tools/xlsx2ini.linux
chmod +x tools/premake5.linux chmod +x tools/premake5.linux
chmod +x tools/ninja.linux chmod +x tools/ninja.linux
chmod +x demos/lua/luajit.linux chmod +x engine/bind/luajit.linux
echo build=$build, type=$dll, cc=$cc, args=$args echo build=$build, type=$dll, cc=$cc, args=$args
# framework (as dynamic library) # framework (as dynamic library)
if [ "$dll" = "dll" ]; then if [ "$dll" = "dll" ]; then
echo libfwk.so && $cc -o libfwk.so engine/fwk.c -shared -fPIC -w -lX11 -lm -ldl -lpthread $flags $args echo libfwk.so && $cc -o libfwk.so engine/fwk.c -shared -fPIC -w -lX11 -lm -ldl -lpthread $flags $args
cp libfwk.so demos/lua/ cp libfwk.so engine/bind/
export import="libfwk.so -Wl,-rpath,./" export import="libfwk.so -Wl,-rpath,./"
else else
# framework (static) # framework (static)
@ -167,15 +167,14 @@ if [ "$(uname)" != "Darwin" ]; then
# demos # demos
echo hello && $cc -o hello hello.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $args & echo hello && $cc -o hello hello.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $args &
# echo 00-ui && $cc -o 00-ui demos/00-ui.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 00-ui && $cc -o 00-ui demos/00-ui.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args &
# echo 01-sprite && $cc -o 01-sprite demos/01-sprite.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 01-sprite && $cc -o 01-sprite demos/01-sprite.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args &
# echo 02-ddraw && $cc -o 02-ddraw demos/02-ddraw.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 02-ddraw && $cc -o 02-ddraw demos/02-ddraw.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args &
# echo 03-anims && $cc -o 03-anims demos/03-anims.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 03-anims && $cc -o 03-anims demos/03-anims.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args &
# echo 04-actor && $cc -o 04-actor demos/04-actor.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 04-actor && $cc -o 04-actor demos/04-actor.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args &
# echo 04-controller && $cc -o 04-controller demos/04-controller.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 05-scene && $cc -o 05-scene demos/05-scene.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args &
# echo 05-scene && $cc -o 05-scene demos/05-scene.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 06-controller && $cc -o 06-controller demos/06-controller.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args &
# echo 06-pbr && $cc -o 06-pbr demos/06-pbr.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args & echo 07-network && $cc -o 07-network demos/07-network.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args
# echo 07-network && $cc -o 07-network demos/07-network.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args
fi fi
if [ "$(uname)" = "Darwin" ]; then if [ "$(uname)" = "Darwin" ]; then
@ -202,14 +201,14 @@ if [ "$(uname)" = "Darwin" ]; then
chmod +x tools/xlsx2ini.osx chmod +x tools/xlsx2ini.osx
chmod +x tools/premake5.osx chmod +x tools/premake5.osx
chmod +x tools/ninja.osx chmod +x tools/ninja.osx
chmod +x demos/lua/luajit.osx chmod +x engine/bind/luajit.osx
echo build=$build, type=$dll, cc=$cc, args=$args echo build=$build, type=$dll, cc=$cc, args=$args
# framework (as dynamic library) # framework (as dynamic library)
if [ "$dll" = "dll" ]; then if [ "$dll" = "dll" ]; then
echo libfwk && cc -ObjC -dynamiclib -o libfwk.dylib engine/fwk.c -framework cocoa -framework iokit -framework audiotoolbox -w $flags $args echo libfwk && cc -ObjC -dynamiclib -o libfwk.dylib engine/fwk.c -framework cocoa -framework iokit -framework audiotoolbox -w $flags $args
cp libfwk.dylib demos/lua cp libfwk.dylib engine/bind
export import=libfwk.dylib export import=libfwk.dylib
else else
# framework # framework
@ -222,15 +221,14 @@ if [ "$(uname)" = "Darwin" ]; then
# demos # demos
echo hello && cc -o hello -ObjC hello.c -w -Iengine/ $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo hello && cc -o hello -ObjC hello.c -w -Iengine/ $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 00-ui && cc -o 00-ui demos/00-ui.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 00-ui && cc -o 00-ui demos/00-ui.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 01-sprite && cc -o 01-sprite demos/01-sprite.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 01-sprite && cc -o 01-sprite demos/01-sprite.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 02-ddraw && cc -o 02-ddraw demos/02-ddraw.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 02-ddraw && cc -o 02-ddraw demos/02-ddraw.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 03-anims && cc -o 03-anims demos/03-anims.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 03-anims && cc -o 03-anims demos/03-anims.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 04-actor && cc -o 04-actor demos/04-actor.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 04-actor && cc -o 04-actor demos/04-actor.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 04-controller && cc -o 04-controller demos/04-controller.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 05-scene && cc -o 05-scene demos/05-scene.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 05-scene && cc -o 05-scene demos/05-scene.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 06-controller && cc -o 06-controller demos/06-controller.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox &
# echo 06-pbr && cc -o 06-pbr demos/06-pbr.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox & echo 07-network && cc -o 07-network demos/07-network.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox
# echo 07-network && cc -o 07-network demos/07-network.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox
fi fi
exit exit
@ -249,17 +247,18 @@ if "%1"=="-h" goto showhelp
if "%1"=="help" ( if "%1"=="help" (
:showhelp :showhelp
echo %0 ; compile everything: `make dll dev` alias echo %0 ; compile everything: `make dll dev` alias
echo %0 [help] ; show this screen echo %0 [all] ; build everything
echo %0 [docs] ; generate tools/docs/docs.html file echo %0 [bind] ; generate lua bindings
echo %0 [cook] ; cook .zipfiles with tools/cook.ini cookbook echo %0 [cook] ; cook .zipfiles with tools/cook.ini cookbook
echo %0 [docs] ; generate tools/docs/docs.html file
echo %0 [help] ; show this screen
echo %0 [proj] ; generate a xcode/gmake/ninja/visual studio solution
echo %0 [sync] ; sync repo to latest echo %0 [sync] ; sync repo to latest
echo %0 [tidy] ; clean up temp files echo %0 [tidy] ; clean up temp files
echo %0 [bindings] ; generate demos/lua bindings
echo %0 [checkmem] ; check untracked allocators in FWK echo %0 [checkmem] ; check untracked allocators in FWK
echo %0 [split^|join] ; engine/fwk* ^>split^> engine/split/* or engine/split/* ^>join^> engine/fwk* echo %0 [split^|join] ; engine/fwk* ^>split^> engine/split/* or engine/split/* ^>join^> engine/fwk*
echo %0 [amalgamation] ; combine engine/fwk* into a single-header file echo %0 [amalgamation] ; combine engine/fwk* into a single-header file
echo %0 [sln] ; generate a xcode/gmake/ninja/visual studio solution echo %0 [cl^|tcc^|cc^|gcc^|clang^|clang-cl] [dbg^|dev^|rel] [static^|dll] [nofwk^|nodemos^|editor] [vis] [-- args]
echo %0 [cl^|tcc^|cc^|gcc^|clang^|clang-cl] [dbg^|dev^|rel] [static^|dll] [nofwk^|nodemos^|noeditor] [vis] [-- args]
echo cl \ echo cl \
echo tcc ^| echo tcc ^|
echo cc ^| select compiler. must be accessible in PATH echo cc ^| select compiler. must be accessible in PATH
@ -272,8 +271,8 @@ if "%1"=="help" (
echo static \ link fwk as static library echo static \ link fwk as static library
echo dll / link fwk as dynamic library (dll^) (default^) echo dll / link fwk as dynamic library (dll^) (default^)
echo nofwk \ do not compile framework echo nofwk \ do not compile framework
echo nodemos ^| do not compile demos echo demos ^| do compile demos
echo noeditor / do not compile editor echo editor / do compile editor
echo vis ^> visualize invokation cmdline. echo vis ^> visualize invokation cmdline.
echo args ^> after `--` separator is found, pass all remaining arguments to compiler as-is echo args ^> after `--` separator is found, pass all remaining arguments to compiler as-is
echo. echo.
@ -298,10 +297,10 @@ if "%1"=="cook" (
exit /b exit /b
) )
rem generate bindings rem generate bindings
if "%1"=="bindings" ( if "%1"=="bind" (
rem luajit rem luajit
tools\luajit tools\luajit_make_bindings.lua > fwk.lua tools\luajit tools\luajit_make_bindings.lua > fwk.lua
move /y fwk.lua demos\lua move /y fwk.lua engine\bind
exit /b exit /b
) )
@ -349,7 +348,7 @@ rem generate prior files to a github release
if "%1"=="github" ( if "%1"=="github" (
rem call make.bat dll rem call make.bat dll
call make.bat docs call make.bat docs
call make.bat bindings call make.bat bind
call make.bat amalgamation call make.bat amalgamation
call make.bat split call make.bat split
@ -391,7 +390,7 @@ rem tidy environment
if "%1"=="tidy" ( if "%1"=="tidy" (
move /y ??-*.png demos > nul 2> nul move /y ??-*.png demos > nul 2> nul
move /y ??-*.c demos > nul 2> nul move /y ??-*.c demos > nul 2> nul
del demos\lua\fwk.dll > nul 2> nul del engine\bind\fwk.dll > nul 2> nul
del .temp*.* > nul 2> nul del .temp*.* > nul 2> nul
del *.zip > nul 2> nul del *.zip > nul 2> nul
del *.mem > nul 2> nul del *.mem > nul 2> nul
@ -427,10 +426,11 @@ set build=dev
set args=-Iengine set args=-Iengine
set other= set other=
set fwk=yes set fwk=yes
set demos=yes set hello=yes
set editor=yes set demos=no
set editor=no
set vis=no set vis=no
set sln=no set proj=no
set rc=0 set rc=0
:parse_args :parse_args
@ -454,7 +454,10 @@ set rc=0
if "%1"=="nofwk" set "fwk=no" && goto loop if "%1"=="nofwk" set "fwk=no" && goto loop
if "%1"=="nodemos" set "demos=no" && goto loop if "%1"=="nodemos" set "demos=no" && goto loop
if "%1"=="demos" set "demos=yes" && set "hello=no" && goto loop
if "%1"=="noeditor" set "editor=no" && goto loop if "%1"=="noeditor" set "editor=no" && goto loop
if "%1"=="editor" set "editor=yes" && set "hello=no"&& goto loop
if "%1"=="all" set "fwk=yes" && set "demos=yes" && set "editor=yes" && set "hello=yes" && goto loop
if "%1"=="tcc" set "cc=%1" && goto loop if "%1"=="tcc" set "cc=%1" && goto loop
if "%1"=="cl" set "cc=%1" && goto loop if "%1"=="cl" set "cc=%1" && goto loop
@ -464,7 +467,7 @@ set rc=0
if "%1"=="clang" set "cc=%1" && goto loop if "%1"=="clang" set "cc=%1" && goto loop
if "%1"=="clang-cl" set "cc=%1" && goto loop if "%1"=="clang-cl" set "cc=%1" && goto loop
if "%1"=="sln" set "sln=yes" && goto loop if "%1"=="proj" set "proj=yes" && goto loop
if not "%1"=="" set "other=!other! %1" && set "editor=no" && set "demos=no" if not "%1"=="" set "other=!other! %1" && set "editor=no" && set "demos=no"
@ -503,9 +506,10 @@ if "!cc!"=="" (
) )
rem solution. @todo: lin/osx rem solution. @todo: lin/osx
if "!sln!"=="yes" if not "%vs%"=="" pushd tools && premake5 vs20%vs% & popd if "!proj!"=="yes" if not "%vs%"=="00" pushd tools && premake5 vs20%vs% & popd
if "!sln!"=="yes" pushd tools && premake5 ninja & popd if "!proj!"=="yes" if "%vs%"=="00" pushd tools && premake5 vs2013 & popd
if "!sln!"=="yes" pushd tools && premake5 gmake & popd & exit /b if "!proj!"=="yes" pushd tools && premake5 ninja & popd
if "!proj!"=="yes" pushd tools && premake5 gmake & popd & exit /b
rem --- pipeline rem --- pipeline
rem cl tools/ass2iqe.c /Fetools/ass2iqe.exe /nologo /openmp /O2 /Oy /MT /DNDEBUG assimp.lib rem cl tools/ass2iqe.c /Fetools/ass2iqe.exe /nologo /openmp /O2 /Oy /MT /DNDEBUG assimp.lib
@ -645,30 +649,33 @@ if not "!other!"=="" (
rem framework rem framework
if "!fwk!"=="yes" ( if "!fwk!"=="yes" (
if "!vis!"=="yes" echo !cc! engine\fwk.c !export! !args! ^&^& if "!dll!"=="dll" copy /y fwk.dll demos\lua ^> nul if "!vis!"=="yes" echo !cc! engine\fwk.c !export! !args! ^&^& if "!dll!"=="dll" copy /y fwk.dll engine\bind ^> nul
!echo! fwk && !cc! engine\fwk.c !export! !args! && if "!dll!"=="dll" copy /y fwk.dll demos\lua > nul || set rc=1 !echo! fwk && !cc! engine\fwk.c !export! !args! && if "!dll!"=="dll" copy /y fwk.dll engine\bind > nul || set rc=1
) )
rem editor rem editor
if "!editor!"=="yes" ( if "!editor!"=="yes" (
set edit=-DCOOK_ON_DEMAND -DUI_LESSER_SPACING -DUI_ICONS_SMALL set edit=-DCOOK_ON_DEMAND -DUI_LESSER_SPACING -DUI_ICONS_SMALL
if "!vis!"=="yes" echo !cc! !o! editor.exe tools\editor\editor.c !edit! !import! !args! if "!vis!"=="yes" echo !cc! !o! editor.exe tools\editor\editor.c !edit! !import! !args!
rem !echo! editor && !cc! !o! editor.exe tools\editor\editor.c !edit! !import! !args! || set rc=1 !echo! editor && !cc! !o! editor.exe tools\editor\editor.c !edit! !import! !args! || set rc=1
rem !echo! editor2 && !cc! !o! editor2.exe tools\editor\editor2.c !edit! !args! || set rc=1 !echo! editor2 && !cc! !o! editor2.exe tools\editor\editor2.c !edit! !args! || set rc=1
) )
rem demos rem demos
if "!demos!"=="yes" ( if "!demos!"=="yes" (
!echo! 00-ui && !cc! !o! 00-ui.exe demos\00-ui.c !import! !args! || set rc=1
!echo! 01-sprite && !cc! !o! 01-sprite.exe demos\01-sprite.c !import! !args! || set rc=1
!echo! 02-ddraw && !cc! !o! 02-ddraw.exe demos\02-ddraw.c !import! !args! || set rc=1
!echo! 03-anims && !cc! !o! 03-anims.exe demos\03-anims.c !import! !args! || set rc=1
!echo! 04-actor && !cc! !o! 04-actor.exe demos\04-actor.c !import! !args! || set rc=1
!echo! 05-scene && !cc! !o! 05-scene.exe demos\05-scene.c !import! !args! || set rc=1
!echo! 06-controller && !cc! !o! 06-controller.exe demos\06-controller.c !import! !args! || set rc=1
!echo! 07-network && !cc! !o! 07-network.exe demos\07-network.c !import! !args! || set rc=1
)
rem hello
if "!hello!"=="yes" (
!echo! hello && !cc! !o! hello.exe hello.c !args! || set rc=1 !echo! hello && !cc! !o! hello.exe hello.c !args! || set rc=1
rem !echo! 00-ui && !cc! !o! 00-ui.exe demos\00-ui.c !import! !args! || set rc=1
rem !echo! 01-sprite && !cc! !o! 01-sprite.exe demos\01-sprite.c !import! !args! || set rc=1
rem !echo! 02-ddraw && !cc! !o! 02-ddraw.exe demos\02-ddraw.c !import! !args! || set rc=1
rem !echo! 03-anims && !cc! !o! 03-anims.exe demos\03-anims.c !import! !args! || set rc=1
rem !echo! 04-actor && !cc! !o! 04-actor.exe demos\04-actor.c !import! !args! || set rc=1
rem !echo! 04-controller && !cc! !o! 04-controller.exe demos\04-controller.c !import! !args! || set rc=1
rem !echo! 05-scene && !cc! !o! 05-scene.exe demos\05-scene.c !import! !args! || set rc=1
rem !echo! 06-pbr && !cc! !o! 06-pbr.exe demos\06-pbr.c !import! !args! || set rc=1
rem !echo! 07-network && !cc! !o! 07-network.exe demos\07-network.c !import! !args! || set rc=1
) )
rem user-defined apps rem user-defined apps

214
README.md
View File

@ -1,6 +1,6 @@
<h1 align="center"><a href="https://bit.ly/-f-w-k-">F·W·K</a></h1> <h1 align="center"><a href="https://bit.ly/fwk2023">F·W·K</a></h1>
<p align="center"> <p align="center">
3D game framework in C.<br/> 3D game engine/framework in C, with Luajit and Python bindings now.<br/>
</p> </p>
<p align="center"> <p align="center">
@ -8,16 +8,12 @@
</p> </p>
## Goals ## Goals
- [x] ~~C++~~. C. - [x] ~~Full featured~~, ~~Fast~~, ~~Modern C++~~. Small, Naive, Simple C.
- [x] ~~Fast~~. Naive. - [x] ~~Rich build system~~, ~~Royaltie fee~~. Single file, Freely unlicensed.
- [x] ~~Modern~~. Simple.
- [x] ~~Full featured~~. Small.
- [x] ~~Rich build system~~. Single file.
- [x] ~~Royaltie fee~~. Free and unlicensed.
## Features ᕦ(ᐛ)ᕤ ## Features ᕦ(ᐛ)ᕤ
- [x] Pipeline: configurable and integrated [asset pipeline](tools/cook.ini). - [x] Pipeline: configurable and integrated [asset pipeline](tools/cook.ini).
- [x] Embedded: single-file header, all dependencies included. - [x] Embedded: [single-file header](engine/joint/fwk.h), all dependencies included.
- [x] Compiler: MSVC, MINGW64, TCC, GCC, clang, clang-cl and emscripten. - [x] Compiler: MSVC, MINGW64, TCC, GCC, clang, clang-cl and emscripten.
- [x] Linkage: Both static linkage and dynamic .dll/.so/.dylib support. - [x] Linkage: Both static linkage and dynamic .dll/.so/.dylib support.
- [x] Platform: Windows, Linux and OSX. Partial HTML5/Web support. - [x] Platform: Windows, Linux and OSX. Partial HTML5/Web support.
@ -52,25 +48,36 @@
- [x] Scene handling. - [x] Scene handling.
- [x] Profiler, stats and leaks finder. - [x] Profiler, stats and leaks finder.
- [x] [Editor (wip)](https://user-images.githubusercontent.com/35402248/174457347-f787a6a2-aac8-404c-a5da-f44310c3d432.mp4). - [x] [Editor (wip)](https://user-images.githubusercontent.com/35402248/174457347-f787a6a2-aac8-404c-a5da-f44310c3d432.mp4).
- [x] [Documentation (wip)](https://bit.ly/-f-w-k-). - [x] [Documentation (wip)](https://bit.ly/fwk2023).
## Roadmap ᕕ(ᐛ)ᕗ (in order of arrival; ✱: partial support) ## Roadmap ᕕ(ᐛ)ᕗ (in order of arrival; ✱: partial support)
- [ ] AI pass: actors, waypoints, pathfinding, behavior trees (h/fsm,goap), and navmesh generation. - [ ] AI pass: actors, waypoints, pathfinding, behavior trees (h/fsm,goap), and navmesh generation.
- [ ] Render pass: reverse-Z, automatic LODs, impostors, decals.
- [ ] Materials: (colors✱, textures✱, matcaps✱, videos✱, shadertoys✱). Shadertoys as post-fx✱. <!--materials as postfx, as they have an update() method -->
- [ ] Lighting: Hard/soft shadow mapping (VSM,CCSM). Baked lightmaps. Refl probes. Integrated PBR.
- [ ] Network/VM pass: Entity/component/systems and worlds. <!-- W/ECS, gameobj, serialization:load/save/merge, diff/patch ;; dead reckoning, interpolation, extrapolation, bandwidth budgets --> - [ ] Network/VM pass: Entity/component/systems and worlds. <!-- W/ECS, gameobj, serialization:load/save/merge, diff/patch ;; dead reckoning, interpolation, extrapolation, bandwidth budgets -->
- [ ] Core pass: struct serialization.
- [ ] Message pipeline and replication. <!-- manual/replication channels, node sharding/clustering. --> - [ ] Message pipeline and replication. <!-- manual/replication channels, node sharding/clustering. -->
- [ ] Digital signals, message buffering and event polling. - [ ] Digital signals, message buffering and event polling.
- [ ] World streaming and level loading. - [ ] World streaming and level loading.
- [ ] Scenegraphs and spatial partioning. BVH, PVS, occluders, frustum culling. - [ ] Scenegraphs and spatial partioning. BVH, PVS, occluders, frustum culling.
- [ ] Server/client architecture. Hybrid P2P. - [ ] Server/client architecture. Hybrid P2P.
- [ ] NAT traversal. Socketless API, message API and pub/sub wrappers (enet/websocket). - [ ] NAT traversal. Socketless API, message API and pub/sub wrappers (enet/websocket).
- [ ] Editor pass = netbased + offline rendering + virtual input.
- [ ] Basic: Gizmos✱, scene tree, property editor✱, load/save✱, undo/redo✱, copy/paste, on/off (vis,tick,ddraw,log), vcs.
- [ ] Scenenode: node singleton display, node console, node labels, node outlines✱.<!-- node == gameobj ? -->
- [ ] Debug: toggles on/off (billboards✱, materials, un/lit, cast shadows, wireframe, skybox✱/mie✱, fog/atmosphere, collide✱, physics).
- [ ] Level: volumes, triggers, platforms, level streaming.
- [ ] Sub-editor: timeline and data tracks, node graphs. <!-- worthy: will be reused into materials, animgraphs and blueprints -->
- [ ] Sub-editor: Procedural content, brushes, noise and CSG.
- [ ] Sub-editor: blendshapes, additive anims, head/foot/hand IKs.
- [ ] Script pass: DLL✱ (module->plugin/sys), Lua✱, Luajit✱, Teal✱ and TypeScript.
- [ ] Render pass: reverse-Z, automatic LODs, impostors, decals.
- [ ] Materials: (colors✱, textures✱, matcaps✱, videos✱, shadertoys✱). Shadertoys as post-fx✱. <!--materials as postfx, as they have an update() method -->
- [ ] Lighting: Hard/soft shadow mapping (VSM,CCSM). Baked lightmaps. Refl probes. Integrated PBR.
- [ ] Tools pass - [ ] Tools pass
- [ ] Extend shaders + bindings. Per-platform✱, per-type✱, per-asset options. GIF, PKM. - [ ] Extend shaders + bindings. Per-platform✱, per-type✱, per-asset options. GIF, PKM.
- [ ] Extend atlas (sprite/lightmaps). Fit packing (sprites). - [ ] Extend atlas (sprite/lightmaps). Fit packing (sprites).
- [ ] Extend bindings and messaging: parse C headers during cooking stage. <!-- msgs,docs,refl,meta,lua -- (*.c, *.h) as .proto/.pbc maybe, free reflection+automatic bindings --> - [ ] Extend bindings and messaging: parse C headers during cooking stage. <!-- msgs,docs,refl,meta,lua -- (*.c, *.h) as .proto/.pbc maybe, free reflection+automatic bindings -->
- [ ] API pass - [ ] API pass
- [ ] Extend math: quat2, bezier, catmull.
- [ ] Discuss API and freeze it. - [ ] Discuss API and freeze it.
- [ ] Document everything. - [ ] Document everything.
@ -183,17 +190,6 @@ int main() {
} }
``` ```
```C
#include "fwk.h" // Minimal HTML5 sample
void render(void *arg) {
if( !input(KEY_ESC) ) puts("hello FWK from HTML5!");
}
int main() {
window_create(75.0, 0); // 75% size, no extra flags
window_loop(render, NULL); // game loop
}
```
```lua ```lua
local fwk = require("fwk") -- Minimal Lua sample local fwk = require("fwk") -- Minimal Lua sample
fwk.window_create(75.0,0) -- 75% size, no extra flags fwk.window_create(75.0,0) -- 75% size, no extra flags
@ -203,6 +199,12 @@ end
``` ```
## Quickstart ## Quickstart
- Double-click `MAKE.bat` (Win) or `sh MAKE.bat` (Linux/OSX) to quick start.
- `MAKE.bat all` (Win) or `sh MAKE.bat all` (Linux/OSX) to build everything.
- `MAKE.bat proj` (Win) or `sh MAKE.bat proj` (Linux/OSX) to generate solutions/makefiles.
- `MAKE.bat help` (Win) or `sh MAKE.bat help` (Linux/OSX) for a bunch of options.
- `MAKE.bat hello.c` (Win) or `sh MAKE.bat hello.c` (Linux/OSX) to build a single executable.
- Alternatively,
```bat ```bat
echo win/vc && cl hello.c echo win/vc && cl hello.c
echo win/clang-cl && clang-cl hello.c echo win/clang-cl && clang-cl hello.c
@ -215,94 +217,108 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
``` ```
## Cook ## Cook
- Most asset types need to be cooked before being used in your application. Some other assets like `.png` do not. - Assets need to be cooked before being consumed in your application. The [tools/](tools/) folder contains all the related binaries to perform any asset processing plus the [cookbook](tools/cook.ini) to do so.
- Cooked assets will be written into .zipfiles close to your executable, and mounted before entering game loop. - Your game will cook all your assets as long as the [`tools/`](tools/) folder is next to your executable. Alternatively, cook them all just by invoking supplied [`tools/cook` standalone binary](tools/).
- Cooked .zipfiles and your executable are the only required assets when releasing your game. - In both cases, assets will be cooked and packed into .zipfiles next to your executable, then mounted before entering game loop. These .zipfiles plus your executable are the only required files when releasing your game.
- Cook manually your assets by invoking supplied [`tools/cook` standalone binary](tools/).
- Cook automatically your assets by just playing your game: a runtime cook is already embedded into your binary.
- In order to achieve this, ensure the [`tools/` folder](tools/) is close to your executable.
- This folder contains all the related binaries to perform any asset conversion plus the [cookbook](tools/cook.ini) to do so.
## Extra tips ## Extra tips
- Any ico/png file named after the executable name will be automatically used as app icon. - Any ico/png file named after the executable name will be automatically used as app icon.
- Similar to the ico/png case above, the cooked .zipfiles can be named after the main executable as well. - Similar to the ico/png case above, the cooked .zipfiles can be named after the main executable as well.
- Dropped files into game window will be imported & saved into [`import/`](art/engine/import) folder. - Dropped files into game window will be imported & saved into [`import/`](engine/art/import/) folder.
- Update the gamepad controller database by upgrading the [`gamecontrollerdb.txt`](art/engine/input) file. - Update the gamepad controller database by upgrading the [`gamecontrollerdb.txt`](engine/art/input/) file.
- Depending on your IDE, you might need to browse to [`split/`](split/) sources when debugging FWK. - Depending on your IDE, you might need to browse to [`engine/split/`](engine/split/) sources when debugging FWK.
- Cook assets on demand, as opposed to cook all existing assets on depot, by using `--cook-on-demand` flag. - Cook assets on demand, as opposed to cook all existing assets on depot, by using `--cook-on-demand` flag.
- Linux/OSX users can optionally install wine and use the Windows tools instead (by using `--cook-wine` flag). - Linux/OSX users can optionally install wine and use the Windows tools instead (by using `--cook-wine` flag).
- Disable automatic cooking by using `--cook-jobs=0` flag (not recommended). - Disable automatic cooking by using `--cook-jobs=0` flag (not recommended).
- Generate a project solution by dropping `split/fwk.h, fwk.c and fwk` files into it. - Generate a project solution by dropping `engine/fwk.h, fwk.c and fwk` files into it.
- Auto-generated Luajit and Python bindings can be found in the [`engine/bind/`](engine/bind/) folder.
<!-- - On windows + vc, you can use `make bindings` or `make docs` to generate everything prior to a release -->
<!-- - Note: Windows: Assimp.dll may need [this package installed](https://www.microsoft.com/en-us/download/confirmation.aspx?id=30679).--> <!-- - Note: Windows: Assimp.dll may need [this package installed](https://www.microsoft.com/en-us/download/confirmation.aspx?id=30679).-->
## Credits (Artwork + demos) ## Credits
- [Nanofactory](https://sketchfab.com/3d-models/kgirls01-d2f946f58a8040ae993cda70c97b302c), for kgirls01 3D model (CC BY-NC-ND 4.0). **Artwork**
- [RottingPixels](https://opengameart.org/content/2d-castle-platformer-tileset-16x16), for castle-tileset (CC0). [Dean Evans, Raijin](https://youtu.be/RRvYkrrpMKo?t=147 "for the Map song (c)"),
- [wwwtyro](https://github.com/wwwtyro/glsl-atmosphere), for nicest rayleigh/mie scattering shader around (CC0). [FMS_Cat](https://gist.github.com/FMS-Cat/a1ccea3ce866c34706084e3526204f4f "for nicest VHS/VCR shader around (MIT)"),
[Goblin165cm](https://sketchfab.com/3d-models/halloween-little-witch-ccc023590bfb4789af9322864e42d1ab "for witch 3D model (CC BY 4.0)"),
[Nuulbee](https://sketchfab.com/3d-models/kgirls01-d2f946f58a8040ae993cda70c97b302c "for kgirls01 3D model (CC BY-NC-ND 4.0)"),
[Quaternius](https://www.patreon.com/quaternius "for the lovely 3D robots (CC0)"),
[Rotting Pixels](https://opengameart.org/content/2d-castle-platformer-tileset-16x16 "for castle-tileset (CC0)"),
[Tom Lewandowski](https://QuestStudios.com "for his MIDI recordings (c)"),
[Rye Terrell](https://github.com/wwwtyro/glsl-atmosphere "for nicest rayleigh/mie scattering shader around (CC0)"),
**Tools**
[Aaron Barany](https://github.com/akb825/Cuttlefish "for cuttlefish (APACHE2)"),
[Arseny Kapoulkine](https://github.com/zeux/pugixml/ "for pugixml (MIT)"),
[Assimp authors](https://github.com/assimp/assimp "for assimp (BSD3)"),
[Bernhard Schelling](https://github.com/schellingb/TinySoundFont "for tml.h (Zlib) and tsf.h (MIT)"),
[Christian Collins](http://www.schristiancollins.com "for GeneralUser GS soundfont (PD)"),
[FFMPEG authors](https://www.ffmpeg.org/ "for ffmpeg (LGPL21)"),
[Imagination](https://developer.imaginationtech.com/pvrtextool/ "for pvrtextoolcli (ITL)"),
[Krzysztof Gabis](https://github.com/kgabis/ape "for split.py/join.py (MIT)"),
[Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo "for iqm.cpp (PD)"),
[Martín Lucas Golini](https://github.com/SpartanJ/eepp/commit/8552941da19380d7a629c4da80a976aec5d39e5c "for emscripten-fs.html (CC0)"),
[Mattias Gustavsson](https://github.com/mattiasgustavsson/libs "for mid.h (PD)"),
[Michael Schmoock](http://github.com/willsteel/lcpp "for lcpp (MIT)"),
[Morgan McGuire](https://casual-effects.com/markdeep/ "for markdeep (BSD2)"),
[Olivier Lapicque, Konstanty Bialkowski](https://github.com/Konstanty/libmodplug "for libmodplug (PD)"),
[Polyglot Team](https://docs.google.com/spreadsheets/d/17f0dQawb-s_Fd7DHgmVvJoEGDMH_yoSd8EYigrb0zmM/edit "for polyglot gamedev (CC0)"),
[Tildearrow](https://github.com/tildearrow/furnace/ "for Furnace (GPL2)"),
[Tomas Pettersson](http://www.drpetter.se/ "for sfxr (PD)"),
[Tor Andersson](https://github.com/ccxvii/asstools "for assiqe.c (BSD)"),
**Runtime**
[Andreas Mantler](https://github.com/ands "for their math library (PD)"),
[Barerose](https://github.com/barerose "for swrap (CC0) and math library (CC0)"),
[Camilla Löwy](https://github.com/elmindreda "for glfw3 (Zlib)"),
[Dave Rand](https://tools.ietf.org/html/rfc1978 "for ppp (PD)"),
[David Herberth](https://github.com/dav1dde/ "for glad generated code (PD)"),
[David Reid](https://github.com/mackron "for miniaudio (PD)"),
[Dominic Szablewski](https://github.com/phoboslab/pl_mpeg "for pl_mpeg (MIT)"),
[Dominik Madarász](https://github.com/zaklaus "for json5 parser (PD)"),
[Eduard Suica](https://github.com/eduardsui/tlse "for tlse (PD)"),
[Evan Wallace](https://github.com/evanw "for their math library (CC0)"),
[Gargaj+cce/Peisik](https://github.com/gargaj/foxotron "for Foxotron/PBR shaders (UNLICENSE)"),
[Guilherme Lampert](https://github.com/glampert "for their math library (PD)"),
[Guillaume Vareille](http://tinyfiledialogs.sourceforge.net "for tinyfiledialogs (ZLIB)"),
[Haruhiko Okumura](https://oku.edu.mie-u.ac.jp/~okumura/compression/ "for lzss (PD)"),
[Igor Pavlov](https://www.7-zip.org/ "for LZMA (PD)"),
[Ilya Muravyov](https://github.com/encode84 "for bcm, balz, crush, ulz, lz4x (PD)"),
[Jon Olick](https://www.jonolick.com/ "for jo_mp1 and jo_mpeg (PD)"),
[Joonas Pihlajamaa](https://github.com/jokkebk/JUnzip "for JUnzip library (PD)"),
[Juliette Focault](https://github.com/juliettef/IconFontCppHeaders/blob/main/IconsMaterialDesign.h "for the generated MD header (ZLIB)"),
[Kristoffer Grönlund](https://github.com/krig "for their math library (CC0)"),
[Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo "for IQM spec & player (PD)"),
[Lee Salzman, V.Hrytsenko, D.Madarász](https://github.com/zpl-c/enet/ "for enet (MIT)"),
[Libtomcrypt](https://github.com/libtom/libtomcrypt "for libtomcrypt (Unlicense)"),
[Lua authors](https://www.lua.org/ "for Lua language (MIT)"),
[Mattias Gustavsson](https://github.com/mattiasgustavsson/libs "for thread.h and https.h (PD)"),
[Micha Mettke](https://github.com/vurtun "for their math library (PD)"),
[Micha Mettke, Chris Willcocks, Dmitry Hrabrov](https://github.com/vurtun/nuklear "for nuklear (PD)"),
[Michael Galetzka](https://github.com/Cultrarius/Swarmz "for swarmz (UNLICENSE)"),
[Morten Vassvik](https://github.com/vassvik/mv_easy_font "for mv_easy_font (Unlicense)"),
[Mārtiņš Možeiko](https://gist.github.com/mmozeiko/68f0a8459ef2f98bcd879158011cc275 "for A* pathfinding (PD)"),
[Omar Cornut, vaiorabbit](https://github.com/ocornut/imgui/pull/3627 "for tables of unicode ranges (MIT-0)"),
[Rabia Alhaffar](https://github.com/Rabios/ice_libs "for ice_batt.h (PD)"),
[Rich Geldreich](https://github.com/richgel999/miniz "for miniz (PD)"),
[Ross Williams](http://ross.net/compression/lzrw3a.html "for lzrw3a (PD)"),
[Samuli Raivio](https://github.com/bqqbarbhg/bq_websocket "for bq_websocket (PD)"),
[Sean Barrett](https://github.com/nothings "for stb_image, stb_image_write, stb_sprintf, stb_truetype and stb_vorbis (PD)"),
[Sebastian Steinhauer](https://github.com/kieselsteini "for sts_mixer (PD)"),
[Stan Melax, Cloud Wu](https://web.archive.org/web/20031204035320/http://www.melax.com/polychop/gdmag.pdf "for polychop C algorithm (PD)"),
[Stefan Gustavson](https://github.com/stegu/perlin-noise "for simplex noise (PD)"),
[Sterling Orsten](https://github.com/sgorsten "for their math library (UNLICENSE)"),
[Tor Andersson](https://github.com/ccxvii/minilibs "for xml.c (PD)"),
[Wolfgang Draxinger](https://github.com/datenwolf "for their math library (WTFPL2)"),
## Credits (Tools) <!--
- [Aaron Barany](https://github.com/akb825/Cuttlefish), for cuttlefish (APACHE2). - [DavidLam](https://en.wikipedia.org/wiki/Tokamak_(software) "for tokamak physics engine (ZLIB)")
- [Arseny Kapoulkine](https://github.com/zeux/pugixml/), for pugixml (MIT). - [ID Software, David St-Louis](https://github.com/Daivuk/PureDOOM "for PureDOOM (Doom License)")
- [Assimp authors](https://github.com/assimp/assimp), for assimp (BSD3). - [Miloslav Číž](https://codeberg.org/drummyfish/Anarch "for Anarch (CC0)")
- [Bernhard Schelling](https://github.com/schellingb/TinySoundFont), for tml.h (Zlib) and tsf.h (MIT). - [Rxi](https://github.com/rxi/autobatch "for lovely sprites & cats demo (MIT)")
- [ffmpeg authors](https://www.ffmpeg.org/), for ffmpeg (LGPL21). -->
- [Imagination](https://developer.imaginationtech.com/pvrtextool/), for pvrtextoolcli (ITL).
- [Krzysztof Gabis](https://github.com/kgabis/ape), for split.py/join.py (MIT).
- [Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo), for iqm.cpp (PD).
- [Mattias Gustavsson](https://github.com/mattiasgustavsson/libs), for mid.h (PD).
- [Michael Schmoock](http://github.com/willsteel/lcpp), for lcpp (MIT).
- [Olivier Lapicque, Konstanty Bialkowski](https://github.com/Konstanty/libmodplug), for libmodplug (PD).
- [Polyglot Team](https://docs.google.com/spreadsheets/d/17f0dQawb-s_Fd7DHgmVvJoEGDMH_yoSd8EYigrb0zmM/edit), for polyglot gamedev (CC0).
- [Tildearrow](https://github.com/tildearrow/furnace/), for Furnace (GPL2).
- [Tomas Pettersson](http://www.drpetter.se/), for sfxr (PD).
- [Tor Andersson](https://github.com/ccxvii/asstools), for assiqe.c (BSD).
## Credits (Runtime)
- [Barerose](https://github.com/barerose), for swrap (CC0).
- [Camilla Löwy](https://github.com/elmindreda), for glfw3 (Zlib).
- [Dave Rand](https://tools.ietf.org/html/rfc1978) for ppp (PD).
- [David Herberth](https://github.com/dav1dde/), for glad generated code (PD).
- [David Reid](https://github.com/mackron), for miniaudio (PD).
- [Dominic Szablewski](https://github.com/phoboslab/pl_mpeg), for pl_mpeg (MIT).
- [Dominik Madarász](https://github.com/zaklaus), for json5 parser (PD).
- [Eduard Suica](https://github.com/eduardsui/tlse), for tlse (PD).
- [Gargaj+cce/Peisik](https://github.com/gargaj/foxotron), for Foxotron/PBR shaders (UNLICENSE).
- [Guillaume Vareille](http://tinyfiledialogs.sourceforge.net), for tinyfiledialogs (ZLIB).
- [Haruhiko Okumura](https://oku.edu.mie-u.ac.jp/~okumura/compression/) for lzss (PD).
- [Igor Pavlov](https://www.7-zip.org/) for LZMA (PD).
- [Ilya Muravyov](https://github.com/encode84) for bcm, balz, crush, ulz, lz4x (PD).
- [Jon Olick](https://www.jonolick.com/), for jo_mp1 and jo_mpeg (PD).
- [Joonas Pihlajamaa](https://github.com/jokkebk/JUnzip), for JUnzip library (PD).
- [Juliette Focault](https://github.com/juliettef/IconFontCppHeaders/blob/main/IconsMaterialDesign.h), for the generated MD header (ZLIB).
- [Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo), for IQM spec & player (PD).
- [Lee Salzman, V.Hrytsenko, D.Madarász](https://github.com/zpl-c/enet/), for enet (MIT).
- [Libtomcrypt](https://github.com/libtom/libtomcrypt), for libtomcrypt (Unlicense).
- [Lua authors](https://www.lua.org/), for Lua language (MIT).
- [Mārtiņš Možeiko](https://gist.github.com/mmozeiko/68f0a8459ef2f98bcd879158011cc275), for A* pathfinding (PD).
- [Mattias Gustavsson](https://github.com/mattiasgustavsson/libs), for thread.h and https.h (PD).
- [Micha Mettke, Chris Willcocks, Dmitry Hrabrov](https://github.com/vurtun/nuklear), for nuklear (PD).
- [Michael Galetzka](https://github.com/Cultrarius/Swarmz), for swarmz (UNLICENSE).
- [Omar Cornut, vaiorabbit](https://github.com/ocornut/imgui/pull/3627), for tables of unicode ranges (MIT-0).
- [Rabia Alhaffar](https://github.com/Rabios/ice_libs), for ice_batt.h (PD).
- [Rich Geldreich](https://github.com/richgel999/miniz), for miniz (PD).
- [Ross Williams](http://ross.net/compression/lzrw3a.html) for lzrw3a (PD).
- [Samuli Raivio](https://github.com/bqqbarbhg/bq_websocket), for bq_websocket (PD).
- [Sean Barrett](https://github.com/nothings), for stb_image, stb_image_write, stb_sprintf, stb_truetype and stb_vorbis (PD).
- [Sebastian Steinhauer](https://github.com/kieselsteini), for sts_mixer (PD).
- [Stan Melax, Cloud Wu](https://web.archive.org/web/20031204035320/http://www.melax.com/polychop/gdmag.pdf), for polychop C algorithm (PD).
- [Stefan Gustavson](https://github.com/stegu/perlin-noise), for simplex noise (PD).
- [Tor Andersson](https://github.com/ccxvii/minilibs), for xml.c (PD).
- [Vassvik](https://github.com/vassvik/mv_easy_font), for mv_easy_font (Unlicense).
- Special thanks to [@ands](https://github.com/ands), [@barerose](https://github.com/barerose), [@datenwolf](https://github.com/datenwolf), [@evanw](https://github.com/evanw), [@glampert](https://github.com/glampert), [@krig](https://github.com/krig), [@sgorsten](https://github.com/sgorsten) and [@vurtun](https://github.com/vurtun) for their math libraries (PD,CC0,WTFPL2,CC0,PD,CC0,Unlicense,PD).
## Unlicense ## Unlicense
This software is released into the [public domain](https://unlicense.org/). Also dual-licensed as [0-BSD](https://opensource.org/licenses/0BSD) or [MIT (No Attribution)](https://github.com/aws/mit-0) for those countries where public domain is a concern (sigh). Any contribution to this repository is implicitly subjected to the same release conditions aforementioned. This software is released into the [public domain](https://unlicense.org/). Also dual-licensed as [0-BSD](https://opensource.org/licenses/0BSD) or [MIT (No Attribution)](https://github.com/aws/mit-0) for those countries where public domain is a concern (sigh). Any contribution to this repository is implicitly subjected to the same release conditions aforementioned.
## Links ## Links
<p> Still looking for alternatives? [amulet](https://github.com/ianmaclarty/amulet), [aroma](https://github.com/leafo/aroma/), [astera](https://github.com/tek256/astera), [blendelf](https://github.com/jesterKing/BlendELF), [bullordengine](https://github.com/MarilynDafa/Bulllord-Engine), [candle](https://github.com/EvilPudding/candle), [cave](https://github.com/kieselsteini/cave), [chickpea](https://github.com/ivansafrin/chickpea), [corange](https://github.com/orangeduck/Corange), [cute](https://github.com/RandyGaul/cute_framework), [dos-like](https://github.com/mattiasgustavsson/dos-like), [ejoy2d](https://github.com/ejoy/ejoy2d), [exengine](https://github.com/exezin/exengine), [gunslinger](https://github.com/MrFrenik/gunslinger), [hate](https://github.com/excessive/hate), [island](https://github.com/island-org/island), [juno](https://github.com/rxi/juno), [l](https://github.com/Lyatus/L), [lgf](https://github.com/Planimeter/lgf), [limbus](https://github.com/redien/limbus), [love](https://github.com/love2d/love/), [lovr](https://github.com/bjornbytes/lovr), [mini3d](https://github.com/mini3d/mini3d), [mintaro](https://github.com/mackron/mintaro), [mio](https://github.com/ccxvii/mio), [olive.c](https://github.com/tsoding/olive.c), [opensource](https://github.com/w23/OpenSource), [ouzel](https://github.com/elnormous/ouzel/), [pez](https://github.com/prideout/pez), [pixie](https://github.com/mattiasgustavsson/pixie), [punity](https://github.com/martincohen/Punity), [r96](https://github.com/badlogic/r96), [ricotech](https://github.com/dbechrd/RicoTech), [rizz](https://github.com/septag/rizz), [tigr](https://github.com/erkkah/tigr), [yourgamelib](https://github.com/duddel/yourgamelib)
<a href="https://github.com/r-lyeh/FWK/issues"><img alt="Issues" src="https://img.shields.io/github/issues-raw/r-lyeh/FWK.svg"/></a>
<a href="https://discord.gg/vu6Vt9d"><img alt="Discord" src="https://img.shields.io/discord/270565488365535232?color=5865F2&label=chat&logo=discord&logoColor=white"/></a><br/>
Still looking for alternatives? <a href="https://github.com/r-lyeh/FWK/issues"><img alt="Issues" src="https://img.shields.io/github/issues-raw/r-lyeh/FWK.svg"/></a> <a href="https://discord.gg/vu6Vt9d"><img alt="Discord" src="https://img.shields.io/discord/270565488365535232?color=5865F2&label=chat&logo=discord&logoColor=white"/></a>
[amulet](https://github.com/ianmaclarty/amulet), [aroma](https://github.com/leafo/aroma/), [astera](https://github.com/tek256/astera), [blendelf](https://github.com/jesterKing/BlendELF), [bullordengine](https://github.com/MarilynDafa/Bulllord-Engine), [candle](https://github.com/EvilPudding/candle), [cave](https://github.com/kieselsteini/cave), [chickpea](https://github.com/ivansafrin/chickpea), [corange](https://github.com/orangeduck/Corange), [cute](https://github.com/RandyGaul/cute_framework), [dos-like](https://github.com/mattiasgustavsson/dos-like), [ejoy2d](https://github.com/ejoy/ejoy2d), [exengine](https://github.com/exezin/exengine), [gunslinger](https://github.com/MrFrenik/gunslinger), [hate](https://github.com/excessive/hate), [island](https://github.com/island-org/island), [juno](https://github.com/rxi/juno), [l](https://github.com/Lyatus/L), [lgf](https://github.com/Planimeter/lgf), [limbus](https://github.com/redien/limbus), [love](https://github.com/love2d/love/), [lovr](https://github.com/bjornbytes/lovr), [mini3d](https://github.com/mini3d/mini3d), [mintaro](https://github.com/mackron/mintaro), [mio](https://github.com/ccxvii/mio), [olive.c](https://github.com/tsoding/olive.c), [opensource](https://github.com/w23/OpenSource), [ouzel](https://github.com/elnormous/ouzel/), [pez](https://github.com/prideout/pez), [pixie](https://github.com/mattiasgustavsson/pixie), [punity](https://github.com/martincohen/Punity), [r96](https://github.com/badlogic/r96), [ricotech](https://github.com/dbechrd/RicoTech), [rizz](https://github.com/septag/rizz), [tigr](https://github.com/erkkah/tigr), [yourgamelib](https://github.com/duddel/yourgamelib)
</p>

278
demos/00-demo.c 100644
View File

@ -0,0 +1,278 @@
// framework demo
// - rlyeh, public domain
#include "fwk.h"
int main() {
// options
bool do_about = 0;
float do_scale = 0.10f;
bool do_debugdraw = 0;
float do_gamepad_deadzone = 0.15f;
vec2 do_gamepad_polarity = vec2(+1,+1);
vec2 do_gamepad_sensitivity = vec2(0.1f,0.1f);
vec2 do_mouse_polarity = vec2(+1,-1);
vec2 do_mouse_sensitivity = vec2(0.2f,0.2f);
bool do_billboard_x = 0, do_billboard_y = 0, do_billboard_z = 0;
// window (80% sized, MSAA x4 flag)
window_create(80, WINDOW_MSAA4);
window_title(__FILE__);
// load all fx files
for(const char **list = file_list("./", "fx**.fs"); *list; list++) {
fx_load(*list);
}
// load skybox
skybox_t sky = skybox(flag("--mie") ? 0 : "cubemaps/stardust", 0); // --mie for rayleigh/mie scattering
// load static scene
model_t sponza;
int do_sponza = flag("--sponza");
if( do_sponza ) {
sponza = model("sponza.obj", 0); // MODEL_NO_TEXTURES);
translation44(sponza.pivot, 0,-1,0);
rotate44(sponza.pivot, -90,1,0,0);
scale44(sponza.pivot, 10,10,10);
}
model_t shaderball;
int do_shaderball = flag("--shaderball");
if( do_shaderball ) {
shaderball = model("shaderball.glb", 0);
translation44(shaderball.pivot, 0,0,-10);
rotate44(shaderball.pivot, -90,1,0,0);
scale44(shaderball.pivot, 0.02,0.02,0.02);
}
// animated models loading
int model_flags = flag("--matcaps") ? MODEL_MATCAPS : 0;
model_t girl = model("kgirl/kgirls01.fbx", model_flags);
model_t alien = model("alien/alien_helmet.fbx", model_flags); rotation44(alien.pivot, -90,1,0,0);
model_t george = model("robots/george.fbx", model_flags);
model_t leela = model("robots/leela.fbx", model_flags);
model_t mike = model("robots/mike.fbx", model_flags);
model_t stan = model("robots/stan.fbx", model_flags);
model_t robots[4] = { george, leela, mike, stan };
for( int i = 0; i < countof(robots); ++i ) {
rotation44(robots[i].pivot, -90,1,0,0);
}
if( flag("--matcaps") ) {
// patch models to use matcaps
model_set_texture(george, texture("matcaps/3B6E10_E3F2C3_88AC2E_99CE51-256px", 0)); // green
model_set_texture(leela, texture("matcaps/39433A_65866E_86BF8B_BFF8D8-256px", 0));
model_set_texture(mike, texture("matcaps/394641_B1A67E_75BEBE_7D7256-256px.png", 0));
model_set_texture(stan, texture("matcaps/test_steel", 0));
model_set_texture(girl, texture("matcaps/material3", 0));
model_set_texture(alien, texture("matcaps/material3", 0));
if( flag("--shaderball") )
model_set_texture(shaderball, texture("matcaps/normals", 0));
}
// camera
camera_t cam = camera();
cam.speed = 0.2f;
// audio (both clips & streams)
audio_t SFX1 = audio_clip( "coin.wav" );
audio_t SFX2 = audio_clip( "pew.sfxr" );
audio_t BGM1 = audio_stream( "waterworld-map.fur"); // wrath_of_the_djinn.xm" );
audio_t BGM2 = audio_stream( "larry.mid" );
audio_t BGM3 = audio_stream( "monkey1.mid" ), BGM = BGM1;
audio_play(SFX1, 0);
audio_play(BGM, 0);
// demo loop
while (window_swap())
{
// input
if( input_down(KEY_ESC) ) break;
if( input_down(KEY_F5) ) window_reload();
if( input_down(KEY_W) && input_held(KEY_LCTRL) ) break;
if( input_down(KEY_F11) ) window_fullscreen( window_has_fullscreen() ^ 1 );
if( input_down(KEY_X) ) window_screenshot(__FILE__ ".png");
if( input_down(KEY_Z) ) window_record(__FILE__ ".mp4");
// vec2 filtered_lpad = input_filter_deadzone(input2(GAMEPAD_LPAD), do_gamepad_deadzone + 1e-3);
// vec2 filtered_rpad = input_filter_deadzone(input2(GAMEPAD_RPAD), do_gamepad_deadzone + 1e-3);
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdecq.x,wasdecq.y,wasdecq.z);
camera_fps(&cam, mouse.x,mouse.y);
window_cursor( !active );
// apply post-fxs from here
fx_begin();
// queue debug drawcalls
profile("Debugdraw") {
ddraw_grid(0);
ddraw_color(YELLOW);
ddraw_text(vec3(+1,+1,-1), 0.04f, va("(%f,%f,%f)", cam.position.x,cam.position.y,cam.position.z));
if(do_debugdraw) ddraw_demo(); // showcase many debugdraw shapes
ddraw_color(YELLOW);
ddraw_flush();
}
// draw skybox
profile("Skybox") {
skybox_render(&sky, cam.proj, cam.view);
}
profile("Skeletal update") if(!window_has_pause()) {
float delta = window_delta() * 30; // 30fps anim
// animate girl & alien
girl.curframe = model_animate(girl, girl.curframe + delta);
alien.curframe = model_animate(alien, alien.curframe + delta);
// animate robots
for(int i = 0; i < countof(robots); ++i) {
robots[i].curframe = model_animate(robots[i], robots[i].curframe + delta);
}
}
profile("Skeletal render") {
static vec3 p = {-10,0,-10}, r = {0,0,0}, s = {2,2,2};
gizmo(&p, &r, &s);
mat44 M; rotationq44(M, eulerq(r)); scale44(M, s.x,s.y,s.z); relocate44(M, p.x,p.y,p.z);
model_render(girl, cam.proj, cam.view, M, 0);
aabb box = model_aabb(girl, M);
ddraw_color(YELLOW);
ddraw_aabb(box.min, box.max);
}
profile("Skeletal render") {
static vec3 p = {+10,0,-10}, r = {0,-90,0}, s = {1,1,1};
//gizmo(&p, &r, &s);
mat44 M; rotationq44(M, eulerq(r)); scale44(M, s.x,s.y,s.z); relocate44(M, p.x,p.y,p.z);
model_render(alien, cam.proj, cam.view, M, 0);
aabb box = model_aabb(alien, M); // @fixme: neg Y
ddraw_color(YELLOW);
//ddraw_aabb(box.min, box.max);
}
profile("Skeletal render") for(int i = 0; i < countof(robots); ++i) {
float scale = 0.50;
mat44 M; copy44(M, robots[i].pivot); translate44(M, i*3,0,0); scale44(M, scale,scale,scale);
model_render(robots[i], cam.proj, cam.view, M, 0);
}
if(do_sponza) profile("Sponza") {
float scale = 1.00;
mat44 M; copy44(M, sponza.pivot); translate44(M, 0,0,0); scale44(M, scale,scale,scale);
model_render(sponza, cam.proj, cam.view, M, 0);
}
if(do_shaderball) profile("Shaderball") {
float scale = 1.00;
mat44 M; copy44(M, shaderball.pivot); translate44(M, 0,0,0); scale44(M, scale,scale,scale);
model_render(shaderball, cam.proj, cam.view, M, 0);
}
// post-fxs end here
fx_end();
// font demo
do_once font_scales(FONT_FACE1, 48, 24, 18, 12, 9, 6);
font_print(va(FONT_RIGHT FONT_BOTTOM FONT_H4 "%5.2f FPS", window_fps()));
// queue ui
if( ui_panel("App", 0)) {
if(ui_bool("Show debugdraw demo", &do_debugdraw)) {}
if(ui_separator()) {}
if(ui_slider("Gamepad deadzone", &do_gamepad_deadzone)) {}
if(ui_float2("Gamepad polarity", do_gamepad_polarity.v2)) {}
if(ui_float2("Gamepad sensitivity", do_gamepad_sensitivity.v2)) {}
if(ui_separator()) {}
if(ui_float2("Mouse polarity", do_mouse_polarity.v2)) {}
if(ui_float2("Mouse sensitivity", do_mouse_sensitivity.v2)) {}
if(ui_separator()) {}
if(ui_button("About...")) { do_about = 1; audio_play(SFX1, 0); }
if(ui_dialog("About", __FILE__ "\n" __DATE__ "\n" "Public Domain.", 0, &do_about)) {}
ui_panel_end();
}
if( ui_panel("Camera", 0)) {
if( ui_float("Speed", &cam.speed) ) {}
if( ui_float3("Position", cam.position.v3) ) {}
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
}
ui_panel_end();
}
if( ui_panel("Audio", 0)) {
static float bgm = 1, sfx = 1, master = 1;
if( ui_slider2("BGM", &bgm, va("%.2f", bgm))) audio_volume_stream(bgm);
if( ui_slider2("SFX", &sfx, va("%.2f", sfx))) audio_volume_clip(sfx);
if( ui_slider2("Master", &master, va("%.2f", master))) audio_volume_master(master);
if( ui_label2_toolbar("BGM: Waterworld Map" /*Wrath of the Djinn"*/, ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = BGM1, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("BGM: Leisure Suit Larry", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = BGM2, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("BGM: Monkey Island", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = BGM3, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("SFX: Coin", ICON_MD_VOLUME_UP)) audio_play(SFX1, 0);
if( ui_label2_toolbar("SFX: Pew", ICON_MD_VOLUME_UP)) audio_play(SFX2, 0);
ui_panel_end();
}
input_demo(); // show some keyboard/mouse/gamepad UI tabs
ui_demo(1); // show all UI widgets in a tab
}
// data tests (json5)
const char json5[] =
" /* json5 */ // comment\n"
" abc: 42.67, def: true, integer:0x100 \n"
" huge: 2.2239333e5, \n"
" hello: 'world /*comment in string*/ //again', \n"
" children : { a: 1, b: 2, c: 3 },\n"
" array: [+1,2,-3,4,5], \n"
" invalids : [ nan, NaN, -nan, -NaN, inf, Infinity, -inf, -Infinity ],";
if( json_push(json5) ) {
assert( json_float("/abc") == 42.67 );
assert( json_int("/def") == 1 );
assert( json_int("/integer") == 0x100 );
assert( json_float("/huge") > 2.22e5 );
assert( strlen(json_string("/hello")) == 35 );
assert( json_int("/children/a") == 1 );
assert( json_int("/children.b") == 2 );
assert( json_int("/children[c]") == 3 );
assert( json_int("/array[%d]", 2) == -3 );
assert( json_count("/invalids") == 8 );
assert( isnan(json_float("/invalids[0]")) );
assert( !json_find("/non_existing") );
assert( PRINTF("json5 tests OK\n") );
json_pop();
}
// data tests (xml)
const char *xml = vfs_read("test1.xml");
if( xml_push(xml) ) {
puts( xml );
puts( xml_string("/person/firstName/$") );
puts( xml_string("/person/lastName/$") );
puts( xml_string("/person/address/@type") );
xml_pop();
}
// network test (https)
array(char) webfile = download("https://www.google.com/");
printf("Network test: %d bytes downloaded from google.com\n", array_count(webfile));
// script test (lua)
script_run( "-- Bye.lua\nio.write(\"script test: Bye world!, from \", _VERSION, \"\\n\")" );
}

7
demos/00-hello.c 100644
View File

@ -0,0 +1,7 @@
#include "fwk.h" // Minimal C sample
int main() {
window_create(75.0, 0); // 75% size, no extra flags
while( window_swap() && !input(KEY_ESC) ) { // game loop
puts("hello FWK from C!");
}
}

86
demos/00-ui.c 100644
View File

@ -0,0 +1,86 @@
// hello ui: config, window, system, ui, video
// - rlyeh, public domain.
//
// Compile with:
// `make demos\00-ui.c` (windows)
// `sh MAKE.bat demos/00-ui.c` (linux, osx)
#include "fwk.h"
int main() {
float app_volume = 1.00f;
unsigned app_size = flag("--fullscreen") ? 100 : 75;
unsigned app_flags = flag("--msaa") ? WINDOW_MSAA4 : 0;
unsigned app_target_fps = optioni("--fps", 60); // --fps=30, --fps=45, etc. defaults to 60.
// window api (fullscreen or 75% sized, optional MSAA flags)
window_create(app_size, app_flags);
window_title(__FILE__);
window_fps_lock(app_target_fps);
// load video
video_t *v = video( "bjork-all-is-full-of-love.mp4", VIDEO_RGB );
// app loop
while( window_swap() ) {
// input controls
if( input(KEY_ESC) ) break;
// profile api
texture_t *textures;
profile( "Video decoder" ) {
// video api: decode frame and get associated textures (audio is sent to audiomixer automatically)
textures = video_decode( v );
// fullscreen video
// if(video_is_rgb(v)) fullscreen_quad_rgb( textures[0], 1.3f );
// else fullscreen_quad_ycbcr( textures, 1.3f );
}
// create menubar on top
int choice1 = ui_menu("File;Shell;Exit");
int choice2 = ui_menu("Help;About");
if( choice1 == 1 ) system(ifdef(win32, "start \"\" cmd", ifdef(osx, "open sh", "xdg-open sh")));
if( choice1 == 2 ) exit(0);
// showcase a few ui widgets
ui_demo(0);
// create ui panel
if( ui_panel("myPanel", PANEL_OPEN) ) {
// Print some numbers
ui_section("Stats");
ui_label2("FPS", va("%5.2f", window_fps()));
ui_separator();
// add some buttons
ui_section("Buttons");
if( ui_button("Screenshot") ) window_screenshot(__FILE__ ".png"), ui_notify(0,ICON_MD_WARNING "Screenshot");
if( ui_button("Record Video") ) window_record(__FILE__ ".mp4"), ui_notify(0,ICON_MD_WARNING "Recoding video");
if( ui_button("Toggle fullscreen") ) window_fullscreen( !window_has_fullscreen() );
ui_separator();
// some more video controls
ui_section("Video");
if( ui_button("Rewind") ) video_seek(v, video_position(v) - 3);
if( ui_button("Pause") ) video_pause(v, video_is_paused(v) ^ 1);
if( ui_button("Forward") ) video_seek(v, video_position(v) + 3);
if( ui_slider2("Volume", &app_volume, va("%.2f", app_volume))) audio_volume_master(app_volume);
// end of panel. must be enclosed within same if() branch.
ui_panel_end();
}
// create window
static int open = 1;
if( ui_window("myWindow", &open) ) {
// present decoded texture in a widget, without any label (NULL)
ui_texture( NULL, textures[0] );
// end of window. must be enclosed within same if() branch.
ui_window_end();
}
}
}
// this demo supersedes following old sources:
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-hello.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-video.c

167
demos/01-sprite.c 100644
View File

@ -0,0 +1,167 @@
// sprite demo: window, audio, camera, font, tiled, render, fx, spritesheet, input, ui. @todo: finish spine
// - rlyeh, public domain.
//
// Compile with:
// `make demos\01-sprite.c` (windows)
// `sh MAKE.bat demos/01-sprite.c` (linux, osx)
#include "fwk.h"
void demo_kids(vec3 offs) {
// init
static texture_t kids; do_once kids = texture( "spriteSheetExample.png", TEXTURE_LINEAR );
static vec3 pos[2] = {{490,362},{442,362}}, vel[2] = {0};
static int row[2] = {0,3}, frame[2] = {0};
static int inputs[2][4] = {{KEY_W,KEY_A,KEY_S,KEY_D},{KEY_UP,KEY_LEFT,KEY_DOWN,KEY_RIGHT}};
// move
for( int i = 0; i < countof(pos); ++i ) {
vel[i].x = input(inputs[i][3]) - input(inputs[i][1]);
vel[i].y = input(inputs[i][2]) - input(inputs[i][0]);
pos[i].x = fmod(pos[i].x + vel[i].x, window_width() + 128);
pos[i].y = fmod(pos[i].y + vel[i].y, window_height() + 128);
frame[i] += vel[i].x || vel[i].y;
}
// render
for( int i = 0; i < countof(pos); ++i ) {
int col = frame[i] / 10, num_frame = row[i] * 4 + col % 4; // 4x4 tilesheet
float position[3] = {pos[i].x,pos[i].y,pos[i].y}, offset[2]={0,0}, scale[2]={0.5,0.5};
float spritesheet[3]={num_frame,4,4};
sprite_sheet(kids,
spritesheet, // num_frame in a 4x4 spritesheet
position, 0, // position(x,y,depth:sort-by-Y), angle
offset, scale, // offset(x,y), scale(x,y)
false, WHITE, false // is_additive, tint color, resolution-independent
);
}
}
void demo_hud() {
// draw pixel-art hud, 16x16 ui element, scaled and positioned in resolution-independant way
static texture_t inputs; do_once inputs = texture( "prompts_tilemap_34x24_16x16x1.png", TEXTURE_LINEAR );
float spritesheet[3] = {17,34,24}, offset[2] = {0, - 2*absf(sin(window_time()*5))}; // sprite cell and animation
float scale[2] = {3, 3}, tile_w = 16 * scale[0], tile_h = 16 * scale[1]; // scaling
float position[3] = {window_width() - tile_w, window_height() - tile_h, window_height() }; // position in screen-coordinates (x,y,z-index)
sprite_sheet(inputs, spritesheet, position, 0/*deg*/, offset, scale, false, WHITE, 1);
}
int main() {
// window (80% sized, MSAA x4 flag)
window_create(80.0, WINDOW_MSAA4);
window_title(__FILE__);
// tiled map
tiled_t tmx = tiled(vfs_read("castle.tmx"));
// tmx.parallax = true;
// spine model
//spine_t *spn = spine("goblins.json", "goblins.atlas", 0);
// camera 2d
camera_t cam = camera();
cam.position = vec3(window_width()/2, window_height()/2, 3); // at(CX,CY) zoom(x3)
camera_enable(&cam);
// audio (both clips & streams)
audio_t clip1 = audio_clip( "coin.wav" );
audio_t clip2 = audio_clip( "pew.sfxr" );
audio_t stream1 = audio_stream( "larry.mid" );
audio_t stream2 = audio_stream( "monkey1.mid" );
audio_t BGM = stream1;
audio_play(BGM, 0);
// font config: faces (6 max) and colors (10 max)
#define FONT_CJK FONT_FACE3
#define FONT_YELLOW FONT_COLOR2
#define FONT_LIME FONT_COLOR3
font_face(FONT_CJK, "mplus-1p-medium.ttf", 48.f, FONT_JP|FONT_2048); // CJK|FONT_2048|FONT_OVERSAMPLE_Y);
font_color(FONT_YELLOW, RGB4(255,255,0,255));
font_color(FONT_LIME, RGB4(128,255,0,255));
// fx: load all post fx files in all subdirs. enable a few filters by default
fx_load("fx**.fs");
fx_enable(fx_find("fxCRT2.fs"), 1);
fx_enable(fx_find("fxGrain.fs"), 1);
fx_enable(fx_find("fxContrast.fs"), 1);
fx_enable(fx_find("fxVignette.fs"), 1);
// demo loop
while (window_swap() && !input_down(KEY_ESC)) {
// handle input
if( input_down(KEY_F5) ) window_reload();
if( input_down(KEY_F11) ) window_fullscreen( !window_has_fullscreen() );
// camera panning (x,y) & zooming (z)
if( !ui_hover() && !ui_active() ) {
if( input(MOUSE_L) ) cam.position.x += input_diff(MOUSE_X);
if( input(MOUSE_L) ) cam.position.y += input_diff(MOUSE_Y);
cam.position.z += input_diff(MOUSE_W) * 0.1;
}
// apply post-fxs from here
fx_begin();
profile("Rendering") {
vec3 center = add3(cam.position,vec3(-window_width()/1,-window_height()/2,0));
// render tiled map
tiled_render(tmx, center);
//
demo_kids(vec3(0,0,1));
demo_hud();
// render spine model
// spine_animate(spn, !window_has_pause() * window_delta());
// spine_render(spn, vec3(cam.position.x, cam.position.y, 1), true );
// sprite_flush();
}
// subtitle sample
font_print(
FONT_BOTTOM FONT_CENTER
FONT_CJK FONT_H1
FONT_YELLOW "私はガラスを食べられます。" FONT_LIME "それは私を傷つけません。\n"
);
// post-fxs end here
fx_end();
// ui
if( ui_panel("Audio", 0)) {
static float bgm = 1, sfx = 1, master = 1;
if( ui_slider2("BGM", &bgm, va("%.2f", bgm))) audio_volume_stream(bgm);
if( ui_slider2("SFX", &sfx, va("%.2f", sfx))) audio_volume_clip(sfx);
if( ui_slider2("Master", &master, va("%.2f", master))) audio_volume_master(master);
if( ui_label2_toolbar("BGM: Leisure Suit Larry", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = stream1, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("BGM: Monkey Island", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = stream2, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("SFX: Coin", ICON_MD_VOLUME_UP)) audio_play(clip1, 0);
if( ui_label2_toolbar("SFX: Pew", ICON_MD_VOLUME_UP)) audio_play(clip2, 0);
ui_panel_end();
}
if( ui_panel("Tiled", 0)) {
ui_float("Zoom in", &cam.position.z);
tiled_ui(&tmx);
ui_panel_end();
}
/*if( ui_panel("Spine", 0)) {
spine_ui(spn);
ui_panel_end();
}*/
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
}
ui_panel_end();
}
}
}
// this demo supersedes following old sources:
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-audio.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-font.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-spine.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-sprite.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-tiled.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-tilemap.c

119
demos/02-ddraw.c 100644
View File

@ -0,0 +1,119 @@
// ddraw demo: fps camera, renderdd, collide, math, ui, fx
// - rlyeh, public domain.
//
// Compile with:
// `make demos\02-ddraw.c` (windows)
// `sh MAKE.bat demos/02-ddraw.c` (linux, osx)
#include "fwk.h"
int main() {
bool do_colliders_demo = 1;
bool do_debugdraw_demo = 0;
// 75% size, MSAAx2
window_create(75.0, WINDOW_MSAA2);
window_title(__FILE__);
// camera that points to origin
camera_t cam = camera();
// load skybox folder (no flags)
skybox_t sky = skybox("cubemaps/stardust", 0);
// load all postfx files in all subdirs
fx_load("fx**.fs");
// main loop
while( window_swap() ) {
// input handler
if (input_down(KEY_F11) ) window_fullscreen( window_has_fullscreen()^1 );
if (input_down(KEY_ESC) ) break;
// fps camera
profile("FPS camera") {
if( input(GAMEPAD_CONNECTED) ) {
vec2 filtered_lpad = input_filter_deadzone(input2(GAMEPAD_LPAD), 0.15f/*do_gamepad_deadzone*/ + 1e-3 );
vec2 filtered_rpad = input_filter_deadzone(input2(GAMEPAD_RPAD), 0.15f/*do_gamepad_deadzone*/ + 1e-3 );
vec2 mouse = scale2(vec2(filtered_rpad.x, filtered_rpad.y), 1.0f);
vec3 wasdec = scale3(vec3(filtered_lpad.x, input(GAMEPAD_LT) - input(GAMEPAD_RT), filtered_lpad.y), 1.0f);
camera_move(&cam, wasdec.x,wasdec.y,wasdec.z);
camera_fps(&cam, mouse.x,mouse.y);
window_cursor( true );
} else {
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdecq.x,wasdecq.y,wasdecq.z);
camera_fps(&cam, mouse.x,mouse.y);
window_cursor( !active );
}
}
fx_begin();
// draw skybox
skybox_render(&sky, cam.proj, cam.view);
// world
ddraw_grid(0);
// boids
static swarm_t sw;
profile("boids") {
do_once sw = swarm();
do_once array_push(sw.steering_targets, vec3(0,0,0));
do_once for(int i = 0; i < 100; ++i)
array_push(sw.boids, boid(scale3(rnd3(),10), rnd3())); // pos,vel
// move
sw.steering_targets[0] = cam.position;
swarm_update(&sw, window_delta());
// draw
for (int j = 0, end = array_count(sw.boids); j < end; ++j) {
vec3 dir = norm3(sub3(sw.boids[j].position, sw.boids[j].prev_position));
ddraw_boid(sw.boids[j].position, dir);
}
}
// showcase many debugdraw shapes
if( do_debugdraw_demo ) {
ddraw_demo();
}
// showcase many colliding tests
if( do_colliders_demo ) {
collide_demo();
}
fx_end();
// ui
if( ui_panel("App", 0) ) {
ui_bool("Collide demo", &do_colliders_demo);
ui_bool("DebugDraw demo", &do_debugdraw_demo);
ui_panel_end();
}
if( ui_panel("Swarm", 0) ) {
ui_swarm(&sw);
ui_panel_end();
}
if( ui_panel("Camera", 0)) {
if( ui_float("Speed", &cam.speed) ) {}
if( ui_float3("Position", cam.position.v3) ) {}
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
}
ui_panel_end();
}
}
}
// this demo supersedes following old sources:
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-collide.c

65
demos/02-frustum.c 100644
View File

@ -0,0 +1,65 @@
#include "fwk.h"
void ddraw_camera(camera_t *c) {
vec3 center = c->position;
// ddraw_prism(add3(center,vec3(2,0,0)), 0.5, 1, vec3(-1,0,0), 4); // center,radius,height,normal,segments
// ddraw_box(center, vec3(2,2,1)); // center,extents
mat33 r; rotationq33(r, eulerq(vec3(-c->yaw,-c->pitch,0)));
ddraw_cube33(center, vec3(2,2,2), r);
ddraw_circle(add3(center,vec3(+1,1,0)), vec3(0,0,1), 0.8); // pos,normal,radius
ddraw_circle(add3(center,vec3(-1,1,0)), vec3(0,0,1), 0.8); // pos,normal,radius
mat44 projview; multiply44x2(projview, c->proj, c->view);
ddraw_frustum(projview);
}
int main() {
window_create(0.75, 0);
camera_t cam = camera();
camera_t cam2 = camera();
int spin = 1;
while( window_swap() ) {
if(input_down(KEY_SPACE)) spin^=1;
// spin 2nd camera
double t = window_time(), c = cos(t), s = sin(t);
if(spin)
camera_teleport(&cam2, vec3(c * 100, 100, s * 100));
camera_lookat(&cam2, vec3(0,0,0));
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdecq.x,wasdecq.y,wasdecq.z);
camera_fps(&cam, mouse.x,mouse.y);
window_cursor( !active );
mat44 projview; multiply44x2(projview, cam2.proj, cam2.view);
frustum f = frustum_build(projview);
ddraw_ground(0);
ddraw_camera(&cam2);
int drawn = 0, total = 0;
for(int z = -300; z < 300; z += 5) {
for(int x = -300; x < 300; x += 5) {
vec3 min = vec3(x, 0, z);
vec3 max = add3(min, vec3(2.5,2.5,2.5));
if( frustum_test_aabb(f, aabb(min, max)) ) {
ddraw_aabb( min, max );
++drawn;
}
++total;
}
}
font_print(va(FONT_RIGHT "%d/%d cubes drawn", drawn, total));
}
}

186
demos/03-anims.c 100644
View File

@ -0,0 +1,186 @@
// anims demo: input, mesh, anim, render, fx, ui, instancing
// - rlyeh, public domain.
//
// Compile with:
// `make demos\03-anims.c` (windows)
// `sh MAKE.bat demos/03-anims.c` (linux, osx)
//
// @todo: instanced poses, instanced render_bones
// @todo: ik, modify bone transform (bone space)
#include "fwk.h"
typedef struct anims_t {
int inuse; // animation number in use
float speed; // x1.00
array(anim_t) anims; // [begin,end,flags] frames of every animation in set
array(mat44) M; // instanced transforms
} anims_t;
anims_t animations(const char *pathfile, int flags) {
anims_t a = {0};
char *anim_file = vfs_read(pathfile);
for each_substring(anim_file, "\r\n", anim) {
int from, to;
char anim_name[128] = {0};
if( sscanf(anim, "%*s %d-%d %127[^\r\n]", &from, &to, anim_name) != 3) continue;
array_push(a.anims, !!strstri(anim_name, "loop") ? loop(from, to, 0, 0) : clip(from, to, 0, 0)); // [from,to,flags]
array_back(a.anims)->name = strswap(strswap(strswap(STRDUP(anim_name), "Loop", ""), "loop", ""), "()", "");
}
array_resize(a.M, 32*32);
for(int z = 0, i = 0; z < 32; ++z) {
for(int x = 0; x < 32; ++x, ++i) {
vec3 p = vec3(-x*3,0,-z*3);
vec3 r = vec3(0,0,0);
vec3 s = vec3(2,2,2);
compose44(a.M[i], p, eulerq(r), s);
}
}
a.speed = 1.0;
return a;
}
int main() {
bool do_showaabb = 0;
bool do_showbones = 0;
bool do_showmodel = 1;
bool do_showgizmo = 1;
// 75% sized, MSAAx2
window_create(75, WINDOW_MSAA2);
window_title(__FILE__);
camera_t cam = camera();
skybox_t sky = skybox("cubemaps/stardust", 0);
model_t mdl = model("kgirls01.fbx", 0);
anims_t a = animations("kgirl/animlist.txt", 0);
// load all postfx files in all subdirs
fx_load("fx**.fs");
while( window_swap() && !input(KEY_ESC) ) {
// fps camera
if( input(GAMEPAD_CONNECTED) ) {
vec2 filtered_lpad = input_filter_deadzone(input2(GAMEPAD_LPAD), 0.15f/*do_gamepad_deadzone*/ + 1e-3 );
vec2 filtered_rpad = input_filter_deadzone(input2(GAMEPAD_RPAD), 0.15f/*do_gamepad_deadzone*/ + 1e-3 );
vec2 mouse = scale2(vec2(filtered_rpad.x, filtered_rpad.y), 1.0f);
vec3 wasdec = scale3(vec3(filtered_lpad.x, input(GAMEPAD_LT) - input(GAMEPAD_RT), filtered_lpad.y), 1.0f);
camera_move(&cam, wasdec.x,wasdec.y,wasdec.z);
camera_fps(&cam, mouse.x,mouse.y);
window_cursor( true );
} else {
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdecq.x,wasdecq.y,wasdecq.z);
camera_fps(&cam, mouse.x,mouse.y);
window_cursor( !active );
}
// skeletal update
static bool is_dragging_slider = 0;
vec2i anim = vec2i( a.anims[ a.inuse ].from, a.anims[ a.inuse ].to );
profile("Skeletal update") {
float delta = window_delta() * 30 * a.speed * !is_dragging_slider; // 30fps anim timer
if(!window_has_pause()) mdl.curframe = model_animate_clip(mdl, mdl.curframe + delta, anim.min, anim.max, a.anims[a.inuse].flags & ANIM_LOOP );
}
// render
fx_begin();
// Skybox
profile("Skybox") {
skybox_render(&sky, cam.proj, cam.view);
}
// ground
ddraw_ground(0);
ddraw_flush();
// characters
static int NUM_INSTANCES = 1;
profile("Skeletal render") {
if( do_showmodel ) model_render_instanced(mdl, cam.proj, cam.view, a.M, 0, NUM_INSTANCES);
if( do_showbones ) model_render_skeleton(mdl, a.M[0]);
if( do_showaabb ) {
aabb box = model_aabb(mdl, a.M[0]);
ddraw_aabb(box.min, box.max);
}
if( do_showgizmo ) {
static vec3 p = {0,0,0}, r = {0,0,0}, s = {2,2,2};
gizmo(&p, &r, &s);
compose44(a.M[0], p, eulerq(r), s);
}
}
fx_end();
if( ui_panel("Animation", 0) ) {
if( ui_bool("Show aabb", &do_showaabb) );
if( ui_bool("Show bones", &do_showbones) );
if( ui_bool("Show models", &do_showmodel) );
if( ui_bool("Show gizmo", &do_showgizmo) );
ui_separator();
if( ui_int("Instances", &NUM_INSTANCES)) NUM_INSTANCES = clampi(NUM_INSTANCES, 1, array_count(a.M));
ui_separator();
ui_label(va("Anim %s [%d.. %.2f ..%d]", a.anims[ a.inuse ].name, anim.min, mdl.curframe, anim.max ));
// normalize curframe into [0..1] range
is_dragging_slider = 0;
float range = (mdl.curframe - anim.min) / ((anim.max - anim.min) + !(anim.max - anim.min));
if( ui_slider2("Frame", &range, va("%.2f/%d %02d%%", mdl.curframe - anim.min, anim.max - anim.min, (int)(range * 100.f))) ) {
mdl.curframe = range * (anim.max - anim.min) + anim.min;
is_dragging_slider = 1;
}
ui_slider2("Speed", &a.speed, va("x%.2f", a.speed));
ui_separator();
for( int i = 0; i < array_count(a.anims); ++i ) {
bool selected = a.inuse == i;
float progress = selected ? (mdl.curframe - anim.min) * 100.f / (anim.max - anim.min) : 0.f;
const char *caption = va("%s%s%s %.2f%%", selected ? "*":"", a.anims[i].name, a.anims[i].flags & ANIM_LOOP ? " (Loop)":"", progress);
int choice = ui_label2_toolbar(caption, va("%s %s %s", ICON_MD_REPLAY_CIRCLE_FILLED, a.inuse == i && a.speed <= 0 ? ICON_MD_NOT_STARTED : ICON_MD_PAUSE_CIRCLE, ICON_MD_PLAY_CIRCLE) );
if( choice == 1 ) { // play/restart
if( mdl.curframe >= anim.max ) mdl.curframe = anim.min; // restart animation
a.speed = 1.0f;
a.inuse = i;
}
if( choice == 2 ) { // pause/advance-frame
if(a.speed <= 0) mdl.curframe = (int)mdl.curframe + 1;
a.speed = 0.0f;
a.inuse = i;
}
if( choice == 3 ) { // loop on/off
if( a.anims[i].flags & ANIM_LOOP )
a.anims[ i ].flags &= ~ANIM_LOOP;
else
a.anims[ i ].flags |= ANIM_LOOP;
a.inuse = i;
}
}
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, b);
}
ui_panel_end();
}
}
}
// @todo: controller demo: root motion on/off
// @todo: controller demo: anim controller.lua
// this demo supersedes following old sources:
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-anims.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-instanced.c

150
demos/04-actor.c 100644
View File

@ -0,0 +1,150 @@
// actor controller demo: anims, anim blending, input, math
// - rlyeh, public domain.
//
// Compile with:
// `make demos\04-actor.c` (windows)
// `sh MAKE.bat demos/04-actor.c` (linux, osx)
#include "fwk.h"
int main() {
// create window (75% sized, MSAAx4)
window_create(75, WINDOW_MSAA4);
window_title(__FILE__);
// set up our players
struct player_t {
const char *name;
model_t mdl;
anim_t idle, run; // anim clips
float keys[4], scale; // up,down,left,right
vec2 inertia; // [forward,yaw]
vec3 pivot, speed; // [pitch,yaw,roll] [turn speed,forward speed,anim speed fps]
vec3 pos, dir, pad; // [position] [facing dir] [gamepad accumulator]
bool notified;
float brain[4]; // AI
} player[3] = {
{ "PLAYER-1", model("kgirls01.fbx", 0), loop(0,60,0.25,0), loop(66,85,0.25,0), // idle anim [0..60], run anim [66..85]
{KEY_UP,KEY_DOWN,KEY_LEFT,KEY_RIGHT}, 2, {0.90,0.80}, {-100}, {3, 0.30, 30}, {0}, {1} },
{ "PLAYER-2", model("george.fbx", 0), loop(0,100,0.25,0), loop(372,396,0.25,0), // idle anim [0..100], run anim [372..396]
{KEY_I,KEY_K,KEY_J,KEY_L}, 1, {0.95,0.90}, {-90,-90}, {1.75, 0.25, 24}, {-5}, {1} },
{ "PLAYER-3", model("alien.fbx", 0), loop(110,208,0.25,0), loop(360,380,0.25,0), // idle anim [110..208], run anim [360..380]
{KEY_W,KEY_S,KEY_A,KEY_D}, 0.85, {0.85,0.75}, {-90,-90}, {3.5, 0.35, 60}, {5}, {1} }
};
// camera that points to origin, skybox, and a background tune
camera_t cam = camera();
skybox_t sky = skybox("cubemaps/stardust", 0);
audio_play( audio_stream("waterworld-map.fur"), 0 );
// game loop
while( window_swap() ) {
// world: skybox, position markers and ground grid
skybox_render(&sky, cam.proj, cam.view);
for( int i = 0; i < countof(player); ++i )
ddraw_position_dir(player[i].pos, player[i].dir, 1.0f);
ddraw_grid(0);
ddraw_flush();
// move and render players
for( int i = 0; i < countof(player); ++i ) {
struct player_t *p = &player[i];
// capture inputs
p->brain[0] = input(p->keys[0]);
p->brain[1] = input(p->keys[1]);
p->brain[2] = input(p->keys[2]);
p->brain[3] = input(p->keys[3]);
// setup waypoints for PLAYER-1
static array(vec3) points;
if( input_down(MOUSE_L) && !ui_hover() ) {
vec3 pt = editor_pick(input(MOUSE_X), input(MOUSE_Y));
hit *h = ray_hit_plane(ray(cam.position, pt), plane(vec3(0,0,0),vec3(0,1,0)));
if(h) array_push(points, h->p);
}
// ddraw waypoints
ddraw_color(YELLOW);
for( int i = 1; i < array_count(points); ++i) ddraw_line(points[i-1],points[i]);
for( int i = 0; i < array_count(points); ++i) ddraw_circle(points[i], vec3(0,1,0), 1); // prism(points[i], 1, 0, vec3(0,1,0), 4);
ddraw_color(RED);
for( int i = 0; i < array_count(points); ++i) ddraw_point(points[i]);
ddraw_color(WHITE);
// move thru waypoints (PLAYER-1 only)
if( i == 0 && array_count(points) ) {
struct player_t *p = &player[i];
vec3 dst = points[0];
vec3 vector1 = norm3(vec3(p->dir.x,0,p->dir.z));
vec3 vector2 = norm3(sub3(dst,p->pos));
float angle = atan2(vector2.z, vector2.x) - atan2(vector1.z, vector1.x);
angle *= 180 / C_PI;
// range [0, 360)
// if (angle < 0) { angle += 2 * 180; }
// range (-180, 180]
if (angle > 180) { angle -= 2 * 180; }
else if (angle <= -180) { angle += 2 * 180; }
float dist = len3(sub3(p->pos, dst));
if(dist < 1) {
// goal
array_pop_front(points);
}
else {
if( dist < 10 && abs(angle) > 10 ) {
// spin only
p->brain[ angle < 0 ? 2 : 3 ] = 1;
}
else {
// spin
p->brain[ angle < 0 ? 2 : 3 ] = 1;
// move forward
p->brain[ 0 ] = 1;
}
}
}
// accumulate movement
float yaw = p->brain[2] - p->brain[3];
float fwd = p->brain[0] - p->brain[1]; if(fwd<0) fwd = 0;
p->pad.x = p->pad.x * p->inertia.y + yaw * (1-p->inertia.y);
p->pad.y = p->pad.y * p->inertia.x + fwd * (1-p->inertia.x);
// rotate yaw dir, then apply into position
p->dir = rotatey3(p->dir, p->speed.x * p->pad.x);
p->pos = add3(p->pos, scale3(p->dir, p->speed.y * p->pad.y));
// animate clips and blend
anim_t *primary = fwd ? &p->run : &p->idle;
anim_t *secondary = fwd ? &p->idle : &p->run;
model_animate_blends(p->mdl, primary, secondary, window_delta() * p->speed.z);
// render model. transforms on top of initial pivot and scale
mat44 M; compose44(M, p->pos, eulerq(add3(p->pivot,vec3(atan2(p->dir.z,p->dir.x)*180/C_PI,0,0))),vec3(p->scale,p->scale,p->scale));
model_render(p->mdl, cam.proj, cam.view, M, 0);
// ui
if( yaw||fwd ) if( !p->notified ) p->notified = 1, ui_notify(0, va(ICON_MD_GAMEPAD " %s joined the game.", p->name));
ddraw_text(p->pos, 0.01, va("%s: %6.3f", fwd?"run":"idle", (fwd ? p->run : p->idle).timer ));
}
// look at the players that are moving; center of their triangle otherwise.
float A = len3(player[0].pad); if(A<0.01) A=0;
float B = len3(player[1].pad); if(B<0.01) B=0;
float C = len3(player[2].pad); if(C<0.01) C=0;
float weight = A + B + C;
if( weight ) A /= weight, B /= weight, C /= weight; else A = B = C = 0.33333;
vec3 target = add3(add3(scale3(player[0].pos,A), scale3(player[1].pos,B)), scale3(player[2].pos,C));
// smooth target before sending to camera
static vec3 smooth; camera_lookat(&cam, smooth = mix3(target,smooth,!weight ? 0.98 : 0.95));
// ui
if( ui_panel("Controls", 0) ) {
ui_label2("Girl", ICON_MD_MOUSE " Set Waypoint");
ui_label2("Girl", ICON_MD_GAMEPAD " CURSOR keys");
ui_label2("Alien", ICON_MD_GAMEPAD " W,A,S,D keys");
ui_label2("Robot", ICON_MD_GAMEPAD " I,J,K,L keys");
ui_panel_end();
}
}
}

249
demos/04-lod.c 100644

File diff suppressed because one or more lines are too long

230
demos/05-scene.c 100644
View File

@ -0,0 +1,230 @@
// scene demo
// - rlyeh, public domain
#include "fwk.h"
int main() {
// options
bool do_twosided = 1;
bool do_wireframe = 0;
bool do_billboard_x = 0, do_billboard_y = 0, do_billboard_z = 0;
// window (80% sized, MSAA x4 flag)
window_create(80, WINDOW_MSAA4);
window_title(__FILE__);
// load all postfx files in all subdirs
fx_load("fx**.fs");
// scene loading
#define SCENE(...) #__VA_ARGS__
const char *my_scene = SCENE([
{
skybox: 'cubemaps/stardust/',
},
{
position:[-5.0,-2.0,2.0],
rotation: [90.0,0.0,180.0],
scale:0.20,
//anchor/pivot:[],
// vertex:'p3 t2',
mesh:'models/witch/witch.obj',
texture:'models/witch/witch_diffuse.tga.png',
// swapzy:true,
flipuv:false,
},
{
position:[-5.0,-2.0,2.0],
rotation: [90.0,0.0,180.0],
scale:2.20,
//anchor/pivot:[],
// vertex:'p3 t2',
mesh:'models/witch/witch_object.obj',
texture:'models/witch/witch_object_diffuse.tga.png',
// swapzy:true,
flipuv:false,
},
]);
int num_spawned = scene_merge(my_scene);
object_t *obj1 = scene_index(0);
object_t *obj2 = scene_index(1);
// manual spawn & loading
model_t m1 = model("kgirl/kgirls01.fbx", 0); //MODEL_NO_ANIMS);
texture_t t1 = texture("kgirl/g01_texture.png", TEXTURE_RGB);
object_t* obj3 = scene_spawn();
object_model(obj3, m1);
object_diffuse(obj3, t1);
object_scale(obj3, vec3(3,3,3));
object_move(obj3, vec3(-10,0,-10));
object_pivot(obj3, vec3(-90+180,180,0));
// camera
camera_t cam = camera();
cam.speed = 0.2f;
// demo loop
while (window_swap())
{
// input
if( input_down(KEY_ESC) ) break;
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
window_cursor( !active );
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdecq.x,wasdecq.y,wasdecq.z);
camera_fps(&cam, mouse.x,mouse.y);
// queue model scale bounces
float t = fmod(window_time(), 0.3) / 0.3;
float s = 0.01f * ease_ping_pong(t, ease_in_cubic,ease_out_cubic);
object_scale(obj1, vec3(0.20f - s,0.20f + s,0.20f - s));
object_scale(obj2, vec3(0.20f - s,0.20f + s,0.20f - s));
// queue model billboard
object_billboard(obj1, (do_billboard_x << 2)|(do_billboard_y << 1)|(do_billboard_z << 0));
object_billboard(obj2, (do_billboard_x << 2)|(do_billboard_y << 1)|(do_billboard_z << 0));
// queue model rotation
//object_rotate(obj3, vec3(0,1*window_time() * 20,0));
// flush render scene (background objects: skybox)
profile("Scene background") {
scene_render(SCENE_BACKGROUND);
}
// queue debug drawcalls
profile("Debugdraw") {
ddraw_ground(0);
ddraw_color(YELLOW);
ddraw_text(vec3(+1,+1,-1), 0.04f, va("(%f,%f,%f)", cam.position.x,cam.position.y,cam.position.z));
ddraw_color(YELLOW);
ddraw_flush();
}
// apply post-fxs from here
fx_begin();
// render scene (foreground objects) with post-effects
profile("Scene foreground") {
int scene_flags = 0;
scene_flags |= do_wireframe ? SCENE_WIREFRAME : 0;
scene_flags |= do_twosided ? 0 : SCENE_CULLFACE;
scene_render(SCENE_FOREGROUND | scene_flags);
}
profile("Skeletal update") if(!window_has_pause()) {
float delta = window_delta() * 30 ; // 30fps anim
m1.curframe = model_animate(m1, m1.curframe + delta);
ddraw_text(vec3(-10,5,-10), 0.05, va("Frame: %.1f", m1.curframe));
}
// post-fxs end here
fx_end();
// queue ui
if( ui_panel("Camera", 0)) {
if( ui_float("Speed", &cam.speed) ) {}
if( ui_float3("Position", &cam.position.x) ) {}
ui_panel_end();
}
if( ui_panel("Scene", 0)) {
if(ui_toggle("Billboard X", &do_billboard_x)) {}
if(ui_toggle("Billboard Y", &do_billboard_y)) {}
if(ui_toggle("Billboard Z", &do_billboard_z)) {}
if(ui_separator()) {}
if(ui_bool("Wireframe", &do_wireframe)) {}
if(ui_bool("Two sided", &do_twosided)) {}
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
}
ui_panel_end();
}
}
}
#if 0
// ----------------------------------------------------------------------------
// material demo
// - rlyeh, public domain
//
// @todo: object_print(obj, "");
// create camera
camera_t cam = camera();
// load video, RGB texture, no audio
video_t *v = video( "bjork-all-is-full-of-love.mp4", VIDEO_RGB | VIDEO_NO_AUDIO ); video_seek(v, 30);
// load texture
texture_t t1 = texture("kgirl/g01_texture.png", TEXTURE_RGB);
texture_t t2 = texture("matcaps/material3", 0);
// load model
model_t m1 = model("suzanne.obj", MODEL_NO_ANIMATIONS);
model_t m2 = model("suzanne.obj", MODEL_NO_ANIMATIONS|MODEL_MATCAPS);
// spawn object1 (diffuse)
object_t* obj1 = scene_spawn();
object_model(obj1, m1);
object_diffuse(obj1, t1);
object_scale(obj1, vec3(3,3,3));
object_move(obj1, vec3(-10+5*0,0,-10));
object_pivot(obj1, vec3(0,90,0));
// spawn object2 (matcap)
object_t* obj2 = scene_spawn();
object_model(obj2, m2);
object_diffuse(obj2, t2);
object_scale(obj2, vec3(3,3,3));
object_move(obj2, vec3(-10+5*2,0,-10));
object_pivot(obj2, vec3(0,90,0));
// spawn object2 (video)
object_t* obj3 = scene_spawn();
object_model(obj3, m1);
object_diffuse(obj3, video_textures(v)[0]);
object_scale(obj3, vec3(3,3,3));
object_move(obj3, vec3(-10+5*1,0,-10));
object_pivot(obj3, vec3(0,90,0));
// @todo: add shadertoy material
static model_t cube; do_once cube = model("cube.obj", 0);
static shadertoy_t s; do_once s = shadertoy("shadertoys/4ttGWM.fs", 256);
model_set_texture(cube, shadertoy_render(&s, window_delta())->tx);
model_render(cube, cam.proj, cam.view, cube.pivot, 0);
while(window_swap() && !input(KEY_ESC)) {
// draw environment
viewport_color( RGB3(22,22,32) );
ddraw_grid(0);
ddraw_flush();
// update video
video_decode( v );
// draw scene
scene_render(SCENE_FOREGROUND);
}
// load static scene
// model_t sponza = model("sponza.obj", MODEL_MATCAPS);
// model_set_texture(sponza, texture("matcaps/normals", 0));
// translation44(sponza.pivot, 0,-1,0);
// rotate44(sponza.pivot, -90,1,0,0);
// scale44(sponza.pivot, 10,10,10);
// model_render(sponza, cam.proj, cam.view, sponza.pivot, 0);
// this demo supersedes following old sources:
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-material.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-shadertoy.c
#endif

View File

@ -0,0 +1,288 @@
// full controller demo: anims, input, collide; @todo: gamepad, input opts, easing on hits, notify on gamepad connect
// - rlyeh, public domain.
//
// Compile with:
// `make demos\04-controller.c` (windows)
// `sh MAKE.bat demos/04-controller.c` (linux, osx)
#include "fwk.h"
int main() {
// 75% window, MSAAx2 flag
window_create(75, WINDOW_MSAA2);
// fx: load all post fx files in all subdirs
fx_load("fx**.fs");
// create a camera
camera_t cam = camera();
camera_enable(&cam);
// config 3d model #1
model_t witch = model("witch/witch.obj", 0);
model_set_texture(witch, texture("witch/witch_diffuse.tga.png", 0));
mat44 witch_pivot; vec3 witch_p = {-5,0,-5}, witch_r={-180,180,0}, witch_s={0.1,-0.1,0.1};
// config 3d model #2
model_t girl = model("kgirl/kgirls01.fbx", 0);
mat44 girl_pivot; vec3 girl_p = {0,0,0}, girl_r = {270,0,0}, girl_s = {2,2,2};
// skybox
skybox_t sky = skybox("cubemaps/stardust", 0);
// BGM
audio_play( audio_stream("waterworld-map.fur"), 0 );
// editor loop
while( window_swap() ) {
// game camera
profile("Game.Camera") {
camera_t *cam = camera_get_active();
static vec3 source;
do_once source = cam->position;
vec3 target = add3(girl_p, vec3(0,10,0));
target = add3(target, scale3(norm3(sub3(source, target)), 10.0));
source = mix3(source, target, 1-0.99f);
camera_teleport(cam, source);
camera_lookat(cam, vec3(girl_p.x,0,girl_p.z));
}
// render begin (postfx)
fx_begin();
// skybox
skybox_render(&sky, cam.proj, cam.view);
// world
ddraw_grid(0);
ddraw_flush();
// models
compose44(girl.pivot, girl_p, eulerq(girl_r), girl_s);
model_render(girl, cam.proj, cam.view, girl.pivot, 0);
compose44(witch.pivot, witch_p, eulerq(witch_r), witch_s);
model_render(witch, cam.proj, cam.view, witch.pivot, 0);
// render end (postfx)
fx_end();
// input controllers
double GAME_JUMP_DOWN = input_down(KEY_Z);
double GAME_FIRE_DOWN = input_down(KEY_X);
double GAME_JUMP = input(KEY_Z);
double GAME_FIRE = input(KEY_X);
double GAME_LEFT = input(KEY_J);
double GAME_RIGHT = input(KEY_L);
double GAME_UP = input(KEY_I);
double GAME_DOWN = input(KEY_K);
double GAME_AXISX = input(KEY_L) - input(KEY_J);
double GAME_AXISY = input(KEY_I) - input(KEY_K);
if( !input_anykey() ) {
if( input(GAMEPAD_CONNECTED) ) {
vec2 filtered_lpad = input_filter_deadzone(input2(GAMEPAD_LPAD), 0.15f /*15% deadzone*/);
GAME_JUMP_DOWN = input_down(GAMEPAD_A);
GAME_FIRE_DOWN = input_down(GAMEPAD_B) || input_down(GAMEPAD_X) || input_down(GAMEPAD_Y);
GAME_JUMP = input(GAMEPAD_A);
GAME_FIRE = input(GAMEPAD_B) || input(GAMEPAD_X) || input(GAMEPAD_Y);
GAME_AXISX = filtered_lpad.x;
GAME_AXISY = filtered_lpad.y;
}
}
// animation controllers
profile("Game.Animate scene") if( !window_has_pause() ) {
float delta = window_delta() * 30; // 30fps anim
// animate girl
girl.curframe = model_animate(girl, girl.curframe + delta);
// jump controller: jump duration=1.5s, jump height=6 units, anims (expo->circ)
float jump_delta = 1.0;
static double jump_timer = 0, jump_ss = 1.5, jump_h = 6;
if( GAME_JUMP_DOWN ) if( jump_timer == 0 ) jump_timer = time_ss();
jump_delta = clampf(time_ss() - jump_timer, 0, jump_ss) * (1.0/jump_ss);
if( jump_delta >= 1 ) { jump_timer = 0; }
float y = ease_ping_pong( jump_delta, ease_out_expo, ease_out_circ);
girl_p.y = y * jump_h;
// punch controller
float punch_delta = 1;
if( jump_delta >= 1 ) {
static vec3 origin;
static double punch_timer = 0, punch_ss = 0.5;
if( GAME_FIRE_DOWN ) if( punch_timer == 0 ) punch_timer = time_ss(), origin = girl_p;
punch_delta = clampf(time_ss() - punch_timer, 0, punch_ss) * (1.0/punch_ss);
if( punch_delta >= 1 ) { punch_timer = 0; }
else {
float x = ease_out_expo( punch_delta );
vec3 fwd = rotate3q(vec3(0,0,1), eulerq(vec3(girl_r.x - 170,girl_r.y,girl_r.z)));
vec3 mix = mix3(girl_p, add3(origin,scale3(fwd,x*2)), x);
girl_p.x = mix.x, girl_p.z = mix.z;
}
}
int modern_controller = 1;
int running = 0;
// girl controller
// locomotion vars
float speed = 0.2f * delta;
float yaw_boost = GAME_AXISY > 0 ? 1.0 : 1.75;
if(punch_delta < 1) yaw_boost = 0.0; // if firing...
else if(punch_delta <= 0.1) yaw_boost = 4.0; // unless initial punch chaining, extra yaw
// old fashioned locomotion controller (boat controller)
if(!modern_controller) {
running = GAME_AXISY > 0;
girl_r.x -= 170;
quat q = eulerq(girl_r); // += custom.pivot
vec3 rgt = rotate3q(vec3(1,0,0), q);
vec3 up = rotate3q(vec3(0,1,0), q);
vec3 fwd = rotate3q(vec3(0,0,1), q);
vec3 dir = scale3(fwd, speed * GAME_AXISY * (GAME_AXISY > 0 ? 2.0 : 0.5));
girl_r.x += speed * 20.0 * yaw_boost * GAME_AXISX; // yaw
girl_p = add3(girl_p, dir);
girl_r.x += 170;
}
// modern locomotion controller (mario 3d)
if(modern_controller) {
running = GAME_AXISX != 0 || GAME_AXISY != 0;
camera_t *cam = camera_get_active();
vec3 fwd = sub3(girl_p, cam->position); fwd.y = 0; fwd = norm3(fwd);
vec3 rgt = norm3(cross3(fwd, vec3(0,1,0)));
// target
vec3 dir = add3(
scale3(fwd, GAME_AXISY),
scale3(rgt, GAME_AXISX)
); dir.y = 0; dir = norm3(dir);
// smoothing
static vec3 olddir; do_once olddir = dir;
dir = mix3(dir, olddir, 1 - (yaw_boost / 4.0) * 0.85);
olddir = dir;
// vis
// ddraw_arrow(girl_p, add3(girl_p,scale3(dir,10)));
// apply direction
girl_p = add3(girl_p, scale3(dir, speed * 2));
// apply rotation
{
girl_r.x -= 170;
quat q = eulerq(girl_r);
vec3 fwdg = rotate3q(vec3(0,0,1), q);
girl_r.x += 170;
//float cosAngle = dot3(dir,fwdg);
//float angle = acos(cosAngle) * TO_DEG;
float angle = TO_DEG * ( atan2(fwdg.z, fwdg.x) - atan2(dir.z, dir.x));
if( !isnan(angle) ) {
girl_r.x -= angle;
while(girl_r.x> 180) girl_r.x-=360;
while(girl_r.x<-180) girl_r.x+=360;
}
}
}
// anim loops
if( jump_delta < 1 ) { // jump/kick anim
#if 0
girl.curframe = clampf(girl.curframe, 184, 202);
if( girl.curframe > 202-4 && GAME_FIRE_DOWN ) girl.curframe = 184+4;
#else
#define loopf(frame, min, max) (frame < min ? min : frame > max ? min + frame - max : frame)
if(girl.curframe >= 203)
girl.curframe = loopf(girl.curframe, 203, 220);
else
girl.curframe = clampf(girl.curframe, 184, 202);
if( girl.curframe > 202-4 && girl.curframe < 208 && GAME_FIRE_DOWN ) girl.curframe = 203;
#endif
}
else if( punch_delta < 1 ) { // punch anim
girl.curframe = clampf(girl.curframe, 90, 101);
if( girl.curframe > 101-6 && GAME_FIRE_DOWN ) girl.curframe = 101-6;
}
else if( running ) {
// loop running anim
if( girl.curframe < 65 ) girl.curframe = 65;
if( girl.curframe > 85 ) girl.curframe = 65;
}
else { // loop idle anim
if( girl.curframe > 59 ) girl.curframe = 0;
}
}
// Game collisions
profile("Game.collisions") {
bool punching = girl.curframe >= 90 && girl.curframe < 101;
bool air_kicking = girl.curframe >= 184 && girl.curframe < 202;
bool jump_kicking = girl.curframe >= 203 && girl.curframe < 220;
bool attacking = punching || air_kicking || jump_kicking;
if( attacking ) {
aabb boxg = model_aabb(girl, girl_pivot);
aabb boxw = model_aabb(witch, witch_pivot);
#if 0 // halve aabb. ok
{
vec3 diag = sub3(boxg.max, boxg.min);
vec3 halve = scale3(diag, 0.25);
vec3 center = scale3(add3(boxg.min, boxg.max), 0.5);
boxg.min = sub3(center, halve);
boxg.max = add3(center, halve);
}
#endif
hit* h = aabb_hit_aabb(boxg, boxw);
if( h && GAME_FIRE ) {
vec3 dir = norm3(sub3(witch_p, girl_p));
witch_p = add3(witch_p, mul3(dir,vec3(1,0,1)));
}
}
}
// ui
if( ui_panel("Input", 0) ) { // @todo: showcase input binding
ui_section("Controllers");
ui_label("Gamepad #1");
ui_label("Keys I/J/K/L + Z/X");
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, b);
}
ui_panel_end();
}
}
}
// vec2 do_gamepad_polarity = vec2(+1,+1);
// vec2 do_gamepad_sensitivity = vec2(0.1f,0.1f);
// vec2 do_mouse_polarity = vec2(+1,-1);
// vec2 do_mouse_sensitivity = vec2(0.2f,0.2f);
// float do_gamepad_deadzone = 0.15f;//
//
// if(ui_separator()) {}
// if(ui_slider("Gamepad deadzone", &do_gamepad_deadzone)) {}
// if(ui_float2("Gamepad polarity", do_gamepad_polarity.v2)) {}
// if(ui_float2("Gamepad sensitivity", do_gamepad_sensitivity.v2)) {}
// if(ui_separator()) {}
// if(ui_float2("Mouse polarity", do_mouse_polarity.v2)) {}
// if(ui_float2("Mouse sensitivity", do_mouse_sensitivity.v2)) {}//

88
demos/07-network.c 100644
View File

@ -0,0 +1,88 @@
// network demo: sockets, downloads, script, @todo: ecs, vm, obj, teal
// - rlyeh, public domain.
//
// Compile with:
// `make demos\07-network.c` (windows)
// `sh MAKE.bat demos/07-network.c` (linux, osx)
#include "fwk.h"
volatile int client_socket = -1;
volatile int server_socket = -1;
int server_echo(int cli) {
ui_notify("Server: client connected", va("Address %s:%s", tcp_host(cli), tcp_port(cli)));
char buf[128];
int len = tcp_recv(cli, buf, 128);
if( len > 0 ) tcp_send(cli, buf, len); // echo
tcp_close(cli);
return 1;
}
int server_thread(void *userdata) {
server_socket = tcp_bind("0.0.0.0", "8080", 10); // port "0" for auto
for(;;) {
tcp_peek(server_socket, server_echo);
sleep_ms(100);
}
}
void client_message(const char *msg) {
client_socket = tcp_open("127.0.0.1", "8080");
if( client_socket >= 0 ) {
tcp_send(client_socket, msg, strlen(msg));
char buf[128];
int rlen = tcp_recv(client_socket, buf, 128);
if(rlen > 0) ui_notify("Client", va("Received '%.*s' from server", rlen, buf));
// tcp_close(client_socket);
}
}
int main() {
window_create(0.85, 0);
window_title(__FILE__);
while(window_swap()) {
static int ui_open; ui_open = 1;
if( ui_window("Network", &ui_open) ) {
if( ui_button("Server: listen") ) { do_once thread(server_thread, NULL); }
ui_label(server_socket >= 0 ? "Listening at 8080 port" : "Not ready");
ui_separator();
if( ui_button("Client: send message") ) client_message("Hi");
ui_label(client_socket >= 0 ? va("Connected to server. My address is %s:%s", tcp_host(client_socket), tcp_port(client_socket)) : "Not connected");
ui_separator();
if( ui_button("Download test (https)")) {
array(char) webfile = download("https://www.google.com/"); // @leak
ui_notify("Download test", va("%d bytes downloaded from google.com", array_count(webfile)));
}
ui_separator();
if( ui_button("Script test")) {
script_run( "ui_notify(\"Script test\", \"Hello from \" .. _VERSION)");
script_run( "-- Bye.lua\nio.write(\"script test: Bye world!, from \", _VERSION, \"\\n\")" );
}
ui_window_end();
}
}
}
// this demo supersedes following old sources:
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-demo.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-script.c
// https://github.com/r-lyeh/FWK/blob/45e34d7890b2b8fe1f4994f4b76e496280d83cb6/demos/00-socket.c
#if 0 // teal
script_run("local tl=require(\"tl\")\ntl.loader()");
script_run("addsub = require(\"s2\"); print (addsub.add(10, 20))");
s2.tl:
local function add(a: number, b: number): number
return a + b
end
local s = add(1,2)
print(s)
#endif

39
demos/99-audio.c 100644
View File

@ -0,0 +1,39 @@
// audio demo
// - rlyeh, public domain
#include "fwk.h"
int main() {
// window (80% sized, MSAA x4 flag)
window_create(80, WINDOW_MSAA4 | WINDOW_SQUARE);
// audio (both streams & clips)
audio_t stream1 = audio_stream( "wrath_of_the_djinn.xm" );
audio_t stream2 = audio_stream( "larry.mid" );
audio_t stream3 = audio_stream( "monkey1.mid" );
audio_t stream4 = audio_stream( "waterworld-map.fur" );
audio_t BGM = stream1;
audio_play(BGM, 0);
audio_t SFX1 = audio_clip( "coin.wav" );
audio_t SFX2 = audio_clip( "pew.sfxr" );
audio_play(SFX1, 0);
// demo loop
while (window_swap() && !input_down(KEY_ESC)) {
static int open = 1;
if( ui_window("Audio", &open)) {
static float bgm = 1, sfx = 1, master = 1;
if( ui_slider2("BGM", &bgm, va("%.2f", bgm))) audio_volume_stream(bgm);
if( ui_slider2("SFX", &sfx, va("%.2f", sfx))) audio_volume_clip(sfx);
if( ui_slider2("Master", &master, va("%.2f", master))) audio_volume_master(master);
if( ui_label2_toolbar("BGM: Wrath of the Djinn", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = stream1, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("BGM: Leisure Suit Larry", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = stream2, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("BGM: Monkey Island", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = stream3, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("BGM: Waterworld Map", ICON_MD_VOLUME_UP)) audio_stop(BGM), audio_play(BGM = stream4, AUDIO_SINGLE_INSTANCE);
if( ui_label2_toolbar("SFX: Coin", ICON_MD_VOLUME_UP)) audio_play(SFX1, 0);
if( ui_label2_toolbar("SFX: Pew", ICON_MD_VOLUME_UP)) audio_play(SFX2, 0);
ui_window_end();
}
}
}

93
demos/99-cubemap.c 100644
View File

@ -0,0 +1,93 @@
#include "fwk.h"
int SKY_DIR = 0;
const char *SKY_DIRS[] = {
"cubemaps/bridge3/",
"cubemaps/colors/",
"cubemaps/colors2/",
"cubemaps/mountain/",
"cubemaps/room/",
"cubemaps/stardust/",
"hdr/MonValley_G_DirtRoad_1k.hdr",
"hdr/Factory_Catwalk_1k.hdr",
"hdr/Shiodome_Stairs_1k.hdr",
};
int OBJ_MDL = 0;
const char *OBJ_MDLS[] = {
"meshes/sphere.obj",
"meshes/suzanne.obj",
"meshes/gazebo.obj",
};
int main(int argc, char** argv) {
window_create(85, WINDOW_MSAA8);
camera_t cam = camera();
skybox_t sky = {0};
model_t mdl = {0};
bool initialized = 0;
bool must_reload = 0;
while( window_swap()) {
// reloading
if( must_reload ) {
must_reload = 0;
skybox_destroy(&sky);
model_destroy(mdl);
initialized = 0;
}
if( !initialized ) {
initialized = 1;
sky = skybox(SKY_DIRS[SKY_DIR], 0);
mdl = model(OBJ_MDLS[OBJ_MDL], 0);
rotation44(mdl.pivot, 0, 1,0,0); // @fixme: -90,1,0,0 -> should we rotate SHMs as well? compensate rotation in shader?
}
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
window_cursor( !active );
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdec = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-input(KEY_C),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdec.x,wasdec.y,wasdec.z);
camera_fps(&cam, mouse.x,mouse.y);
// render
mat44 mvp; multiply44x2(mvp, cam.proj, cam.view);
{
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
//glDisable(GL_CULL_FACE);
// mesh
glDepthMask(GL_TRUE);
glUseProgram(mdl.program);
glUniform3fv(glGetUniformLocation(mdl.program, "u_coefficients_sh"), 9, &sky.cubemap.sh[0].x);
glUniform1i(glGetUniformLocation(mdl.program, "u_textured"), false);
model_render(mdl, cam.proj, cam.view, mdl.pivot, 0);
// sky
skybox_render(&sky, cam.proj, cam.view);
}
if( ui_panel("Scene", 0)) {
if( ui_list("Skybox", SKY_DIRS, countof(SKY_DIRS), &SKY_DIR) ) {
must_reload = 1;
}
if( ui_list("Model", OBJ_MDLS, countof(OBJ_MDLS), &OBJ_MDL) ) {
must_reload = 1;
}
ui_separator();
for (int i = 0; i < 9; i++) {
vec3 remap = scale3(add3(sky.cubemap.sh[i], vec3(1,1,1)), 127.5f); // -1..+1 -> 0..255
ui_color3(va("SH Coefficient [%d]", i), &remap.x);
sky.cubemap.sh[i] = sub3(scale3(remap, 1/127.5f), vec3(1,1,1));
}
ui_panel_end();
}
}
}

57
demos/99-easing.c 100644
View File

@ -0,0 +1,57 @@
#include "fwk.h"
struct {
float (*ease)(float);
const char *name;
} easings[] = {
{ease_linear, "ease_linear"},
{ease_out_sine, "ease_out_sine"},
{ease_out_quad, "ease_out_quad"},
{ease_out_cubic, "ease_out_cubic"},
{ease_out_quart, "ease_out_quart"},
{ease_out_quint, "ease_out_quint"},
{ease_out_expo, "ease_out_expo"},
{ease_out_circ, "ease_out_circ"},
{ease_out_back, "ease_out_back"},
{ease_out_elastic, "ease_out_elastic"},
{ease_out_bounce, "ease_out_bounce"},
{ease_in_sine, "ease_in_sine"},
{ease_in_quad, "ease_in_quad"},
{ease_in_cubic, "ease_in_cubic"},
{ease_in_quart, "ease_in_quart"},
{ease_in_quint, "ease_in_quint"},
{ease_in_expo, "ease_in_expo"},
{ease_in_circ, "ease_in_circ"},
{ease_in_back, "ease_in_back"},
{ease_in_elastic, "ease_in_elastic"},
{ease_in_bounce, "ease_in_bounce"},
{ease_inout_sine, "ease_inout_sine"},
{ease_inout_quad, "ease_inout_quad"},
{ease_inout_cubic, "ease_inout_cubic"},
{ease_inout_quart, "ease_inout_quart"},
{ease_inout_quint, "ease_inout_quint"},
{ease_inout_expo, "ease_inout_expo"},
{ease_inout_circ, "ease_inout_circ"},
{ease_inout_back, "ease_inout_back"},
{ease_inout_elastic, "ease_inout_elastic"},
{ease_inout_bounce, "ease_inout_bounce"},
{ease_inout_perlin, "ease_inout_perlin"},
};
int main() {
window_create(0.75, WINDOW_SQUARE);
while(window_swap()) {
static double timer = 0; timer = fmod(timer+window_delta(), 2); // loops every 2s
static int open = 1;
if( ui_window("ease", &open) ) {
float linear_delta = timer / 2.f; // delta is [0..1]
for( int i = 0; i < countof(easings); ++i) {
float nonlinear_delta = easings[i].ease(linear_delta);
// visualize
ui_slider( easings[i].name, &nonlinear_delta );
}
ui_window_end();
}
}
}

111
demos/99-font.c 100644
View File

@ -0,0 +1,111 @@
#include "fwk.h"
int main() {
window_create(0.75, WINDOW_MSAA8);
// style: our aliases
#define FONT_REGULAR FONT_FACE1
#define FONT_ITALIC FONT_FACE2
#define FONT_BOLD FONT_FACE3
#define FONT_JAPANESE FONT_FACE4
#define FONT_MONOSPACE FONT_FACE5
#define FONT_GRAY FONT_COLOR2
#define FONT_ORANGE FONT_COLOR3
#define FONT_LIME FONT_COLOR4
#define FONT_GREEN FONT_COLOR5
#define FONT_CYAN FONT_COLOR6
#define FONT_LARGEST FONT_H1
#define FONT_LARGE FONT_H2
#define FONT_MEDIUM FONT_H3
#define FONT_NORMAL FONT_H4
#define FONT_SMALL FONT_H5
#define FONT_TINY FONT_H6
// style: atlas size, unicode ranges and font faces (up to 6 faces)
font_face(FONT_REGULAR, "Carlito-Regular.ttf", 48.f, FONT_EU|FONT_AR|FONT_RU|FONT_2048);
font_face(FONT_ITALIC, "Carlito-Italic.ttf", 48.f, FONT_EU|FONT_AR|FONT_RU|FONT_2048);
font_face(FONT_BOLD, "Carlito-Bold.ttf", 48.f, FONT_EU|FONT_AR|FONT_RU|FONT_2048);
font_face(FONT_JAPANESE, "mplus-1p-medium.ttf", 48.f, FONT_JP|FONT_2048); // CJK|FONT_2048|FONT_OVERSAMPLE_Y);
font_face(FONT_MONOSPACE, "Inconsolata-Regular.ttf", 24.f, FONT_EU|FONT_512);
// style: colors (up to 10 colors)
font_color(FONT_GRAY, RGB4(100,100,100,255));
font_color(FONT_ORANGE, RGB4(255,192,0,255));
font_color(FONT_LIME, RGB4(192,255,0,255));
font_color(FONT_GREEN, RGB4(0,255,192,255));
font_color(FONT_CYAN, RGB4(0,192,255,255));
// prepare color highlighting for following code snippet
const char *source =
FONT_MONOSPACE FONT_LARGEST
"int main(int argc, char **argv) {\n"
" for( int i = 0; i < 10; ++i)\n"
" puts(\"hello world\");\n"
" return 0;\n"
"}\n";
const void *colors = font_colorize(source, "void,int,char", "if,else,for,do,while,return,switch,case,break,default,");
// demo loop
while( window_swap() && !input(KEY_ESC) ) {
ddraw_grid(0);
// initial spacing
font_goto(0, 50);
// print a code snippet with syntax highlighting
font_highlight(source, colors);
// print a few strings with markup codes
font_print(
FONT_REGULAR
FONT_LARGEST FONT_GRAY "The quick "
FONT_LARGE FONT_LIME "brown "
FONT_MEDIUM FONT_GRAY "fox "
FONT_NORMAL "jumps over "
FONT_SMALL "the lazy "
FONT_TINY "dog.\n");
font_print(
FONT_REGULAR FONT_LARGE FONT_CYAN
"Now is the time for all " FONT_ITALIC "good men " FONT_REGULAR "to come to the aid of " FONT_BOLD "the party.\n");
font_print(
FONT_ITALIC FONT_LARGE FONT_GREEN
"Ég get etið gler án þess að meiða mig!\n");
font_print(
FONT_BOLD FONT_LARGE FONT_ORANGE
"Эх, чужак! Общий съём цен шляп (юфть)—вдрызг!.\n");
font_print(
FONT_JAPANESE
"私はガラスを食べられます。それは私を傷つけません。\n");
font_print( "This text ");
font_print( "should display concatenated, ");
font_print( "as there are no linefeeds.\n" );
// i18n: pangrams.txt file, line browser
static int counter = 0;
static array(char*) lines; do_once lines = strsplit( vfs_read("pangrams.txt"), "\r\n" );
counter += input_down(KEY_RIGHT)-input_down(KEY_LEFT);
counter += counter < 0 ? array_count(lines) : 0;
font_print( va("<< %s >>\n", lines[counter % array_count(lines)]) );
// this does not work yet. you cant chain alignments yet...
//font_print(FONT_TOP "Top" FONT_MIDDLE "Middle" FONT_BASELINE "Baseline" FONT_BOTTOM "Bottom\n");
//font_print(FONT_LEFT "Left" FONT_CENTER "Center" FONT_RIGHT "Right\n");
// ... alignment must be the first tag in a string for now. this is a temporary hack.
font_print(FONT_LEFT "left");
font_print(FONT_CENTER "center");
font_print(FONT_RIGHT "right");
font_print(FONT_TOP FONT_CENTER "top\n");
font_print(FONT_MIDDLE FONT_RIGHT "middle\n");
font_print(FONT_BASELINE FONT_RIGHT "baseline\n");
font_print(FONT_BOTTOM FONT_CENTER "bottom\n");
}
}

View File

@ -0,0 +1,62 @@
// instanced models demo
// - rlyeh, public domain.
#include "fwk.h"
int main() {
window_create(75, WINDOW_MSAA2);
window_title(__FILE__);
camera_t cam = camera();
skybox_t sky = skybox("cubemaps/stardust", 0);
model_t girl = model("kgirls01.fbx", 0);
while( window_swap() ) {
if(input(KEY_F5)) window_reload();
if(input(KEY_ESC)) break;
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
window_cursor( !active );
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdec = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-input(KEY_C),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdec.x,wasdec.y,wasdec.z);
camera_fps(&cam, mouse.x,mouse.y);
// ground rendering
ddraw_ground(0);
ddraw_flush();
// skeletal
profile("Skeletal update") {
float delta = window_has_pause() ? 0 : window_delta() * 30; // 30fps anim
girl.curframe = model_animate(girl, girl.curframe + delta);
}
profile("Skeletal render") {
enum { ROW = 32, MAX_INSTANCES = ROW * ROW };
static mat44 M[MAX_INSTANCES];
do_once {
int i = 0;
for(int z = 0; z < ROW; ++z) {
for(int x = 0; x < ROW; ++x, ++i) {
vec3 p = vec3(-x*3,0,-z*3);
vec3 r = vec3(0,0,0);
vec3 s = vec3(2,2,2);
rotationq44(M[i], eulerq(r)); scale44(M[i], s.x,s.y,s.z); relocate44(M[i], p.x,p.y,p.z);
}
}
}
model_render_instanced(girl, cam.proj, cam.view, M, 0, MAX_INSTANCES);
}
// skybox
profile("Skybox") {
skybox_render(&sky, cam.proj, cam.view);
}
}
}

View File

@ -0,0 +1,68 @@
// material demo
// - rlyeh, public domain
//
// @todo: object_print(obj, "");
#include "fwk.h"
int main() {
// create the window
window_create( 0.75f, WINDOW_MSAA8 );
// create camera
camera_t cam = camera();
// load video, RGB texture, no audio
video_t *v = video( "bjork-all-is-full-of-love.mp4", VIDEO_RGB | VIDEO_NO_AUDIO ); video_seek(v, 30);
// load texture
texture_t t1 = texture("kgirl/g01_texture.png", TEXTURE_RGB);
texture_t t2 = texture("matcaps/material3", 0);
// load model
model_t m1 = model("suzanne.obj", MODEL_NO_ANIMATIONS);
model_t m2 = model("suzanne.obj", MODEL_NO_ANIMATIONS|MODEL_MATCAPS);
// spawn object1 (diffuse)
object_t* obj1 = scene_spawn();
object_model(obj1, m1);
object_diffuse(obj1, t1);
object_scale(obj1, vec3(3,3,3));
object_move(obj1, vec3(-10+5*0,0,-10));
object_pivot(obj1, vec3(0,90,0));
// spawn object2 (matcap)
object_t* obj2 = scene_spawn();
object_model(obj2, m2);
object_diffuse(obj2, t2);
object_scale(obj2, vec3(3,3,3));
object_move(obj2, vec3(-10+5*2,0,-10));
object_pivot(obj2, vec3(0,90,0));
// spawn object2 (video)
object_t* obj3 = scene_spawn();
object_model(obj3, m1);
object_diffuse(obj3, video_textures(v)[0]);
object_scale(obj3, vec3(3,3,3));
object_move(obj3, vec3(-10+5*1,0,-10));
object_pivot(obj3, vec3(0,90,0));
while(window_swap() && !input(KEY_ESC)) {
// draw environment
viewport_color( RGB3(22,22,32) );
ddraw_grid(0);
ddraw_flush();
// update video
video_decode( v );
// draw scene
scene_render(SCENE_FOREGROUND);
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouselook = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdec = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-input(KEY_C),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdec.x,wasdec.y,wasdec.z);
camera_fps(&cam, mouselook.x,mouselook.y);
window_cursor( !active );
}
}

675
demos/99-pbr.c 100644
View File

@ -0,0 +1,675 @@
// PBR model viewer. Based on Foxotron by @gargaj + cce/Peisik (UNLICENSE).
// - rlyeh, public domain.
//
// @todo: Middle mouse button to pan camera @todo
#include "fwk.h"
#if is(tcc) && !is(win32) // @todo: remove this & test on linux
int log2_64 (uint64_t value) {
const int tab64[64] = {
63, 0, 58, 1, 59, 47, 53, 2,
60, 39, 48, 27, 54, 33, 42, 3,
61, 51, 37, 40, 49, 18, 28, 20,
55, 30, 34, 11, 43, 14, 22, 4,
62, 57, 46, 52, 38, 26, 32, 41,
50, 36, 17, 19, 29, 10, 13, 21,
56, 45, 25, 31, 35, 16, 9, 12,
44, 24, 15, 8, 23, 7, 6, 5};
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value |= value >> 32;
return tab64[((uint64_t)((value - (value >> 1))*0x07EDD5E59A4E28C2)) >> 58];
}
#define log2 log2_64
#endif
// -----------------------------------------------------------------------------
// textures
texture_t *LoadTextureRGBA8( const char *pathfile, unsigned flags ) {
int flags_hdr = strendi(pathfile, ".hdr") ? TEXTURE_FLOAT | TEXTURE_RGBA : 0;
texture_t t = texture(pathfile, flags | TEXTURE_LINEAR | TEXTURE_MIPMAPS | TEXTURE_REPEAT | flags_hdr);
if( t.id == texture_checker().id ) {
return NULL;
}
texture_t *tex = CALLOC(1, sizeof(texture_t));
*tex = t;
return tex;
}
// -----------------------------------------------------------------------------
// models
typedef struct Mesh {
GLuint vao, vbo, ibo;
int vert_stride;
void *vert_stream;
int num_verts, num_tris;
int material_idx;
bool transparent;
vec3 aabb_min, aabb_max;
} Mesh;
typedef struct Model {
array(Mesh) meshes;
array(pbr_material_t) materials;
unsigned shader;
} Model;
bool ModelLoad( Model *G, const char *_path );
void ModelDestroy( Model *G );
void ModelRebind( Model *G, unsigned shader );
void ModelRender( Model *G, const mat44 _worldRootMatrix );
void ModelDestroy( Model *G) {
for( int i = 0, end = array_count(G->materials); i < end; ++i ) {
pbr_material_destroy(&G->materials[i]);
}
array_free(G->materials);
for( int i = 0, end = array_count(G->meshes); i < end; ++i ) {
Mesh *it = &G->meshes[i];
glDeleteBuffers( 1, &it->ibo );
glDeleteBuffers( 1, &it->vbo );
glDeleteVertexArrays( 1, &it->vao );
}
array_free(G->meshes);
}
bool ModelLoad( Model *G, const char *_path ) {
ModelDestroy(G);
// -------------------------------------------------------------------------
Model g = {0};
*G = g;
model_t m = model(_path, 0);
int scn_num_meshes = m.num_meshes;
int scn_num_materials = array_count(m.materials);
// -------------------------------------------------------------------------
for( int i = 0; i < scn_num_materials; i++ ) {
const char *name = m.materials[i].name;
PRINTF("Loading material %d/%d: '%s'\n", i + 1, scn_num_materials, name);
pbr_material_t mt;
pbr_material(&mt, name);
array_push(G->materials, mt);
}
for( int i = 0; i < scn_num_meshes; i++ ) {
PRINTF("Loading mesh %d/%d\n", i + 1, scn_num_meshes);
int verts = m.num_verts;
int faces = m.num_triangles;
unsigned material_index = 0; // &m.iqm->meshes[i].material; // aiGetMeshMaterialIndex(scn_mesh[i]);
bool has_data = verts && faces;
if( !has_data ) {
continue;
}
PRINTF("Loading mesh v%d/f%d\n", verts, faces);
Mesh mesh = { 0 };
mesh.vao = m.vao;
mesh.vbo = m.vbo;
mesh.ibo = m.ibo;
mat44 id; id44(id);
mesh.aabb_min = model_aabb(m, id).min;
mesh.aabb_max = model_aabb(m, id).max;
// p3 n3 t3 b3 u2
mesh.vert_stride = m.stride;
mesh.vert_stream = m.verts;
mesh.num_verts = verts;
mesh.num_tris = faces;
mesh.material_idx = material_index;
// By importing materials before meshes we can investigate whether a mesh is transparent and flag it as such.
const pbr_material_t* mtl = G->materials ? &G->materials[mesh.material_idx] : NULL;
mesh.transparent = false;
if( mtl ) {
mesh.transparent |= mtl->albedo .texture ? mtl->albedo .texture->transparent : mtl->albedo .color.a < 1.0f;
mesh.transparent |= mtl->diffuse.texture ? mtl->diffuse.texture->transparent : mtl->diffuse.color.a < 1.0f;
}
array_push(G->meshes, mesh);
}
#if 0
G->mGlobalAmbient = vec4( 0.3,0.3,0.3,0.3 );
int scn_num_lights = 0;
for( int i = 0; i < scn_num_lights; i++ ) {
PRINTF("Loading light %d/%d\n", i + 1, scn_num_lights);
vec4 *color = aiGetLightColor(scn_light[i]);
char *type = aiGetLightType(scn_light[i]);
if( 0 == strcmp(type, "AMBIENT") ) {
memcpy( &G->mGlobalAmbient, &color->r, sizeof( float ) * 4 );
} else {
// @todo
}
}
#endif
return true;
}
void ModelRender( Model *G, const mat44 _worldRootMatrix ) {
unsigned _shader = G->shader;
shader_bind( _shader );
shader_vec4("global_ambient", vec4(1,1,1,1)); // unused
// loop thrice: first opaque, then transparent backface, then transparent frontface
for(int j = 0; j < 3; ++j) {
bool bTransparentPass = j > 0;
if(bTransparentPass) {
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glCullFace( j == 1 ? GL_FRONT : GL_BACK ); // glDepthMask( GL_FALSE);
}
mat44 mat_world; copy44(mat_world, _worldRootMatrix); // @fixme mMatrices[ node.mID ] * _worldRootMatrix
shader_mat44( "mat_world", mat_world );
for( int i = 0, end = array_count(G->meshes); i < end; i++ ) {
const Mesh *mesh = &G->meshes[ i ];
// Postpone rendering transparent meshes
if(mesh->transparent != bTransparentPass)
continue;
const pbr_material_t *material = &G->materials[ mesh->material_idx ];
shader_colormap( "map_diffuse", material->diffuse );
shader_colormap( "map_normals", material->normals );
shader_colormap( "map_specular", material->specular );
shader_colormap( "map_albedo", material->albedo );
shader_colormap( "map_roughness", material->roughness );
shader_colormap( "map_metallic", material->metallic );
shader_colormap( "map_ao", material->ao );
shader_colormap( "map_ambient", material->ambient );
shader_colormap( "map_emissive", material->emissive );
shader_float( "specular_shininess", material->specular_shininess ); // unused, basic_specgloss.fs only
shader_vec2( "resolution", vec2(window_width(),window_height()));
glActiveTexture(GL_TEXTURE0); // be nice to Mesa before rendering
glBindVertexArray( mesh->vao );
glDrawElements( GL_TRIANGLES, mesh->num_tris * 3, GL_UNSIGNED_INT, NULL );
}
if(bTransparentPass) {
glDisable( GL_BLEND );
// glDepthMask( GL_TRUE );
}
}
//glBindVertexArray( 0 );
//glUseProgram( 0 );
}
static
void G_SetupVertexArray( unsigned _shader, const char *name, int stride, int num_floats, int *offset, int opt_location ) {
int location = opt_location >= 0 ? opt_location : glGetAttribLocation( _shader, name );
if( location >= 0 ) {
glVertexAttribPointer( location, num_floats, GL_FLOAT, GL_FALSE, stride, (GLvoid *)(uintptr_t)(*offset) );
glEnableVertexAttribArray( location );
}
*offset += num_floats * sizeof( GLfloat );
}
void ModelRebind( Model *G, unsigned _shader ) {
shader_bind(_shader);
if(_shader == G->shader) return;
G->shader = _shader;
for( int i = 0, end = array_count(G->meshes); i < end; i++ ) {
const Mesh *mesh = &G->meshes[ i ];
glBindVertexArray( mesh->vao );
glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ibo );
glDisableVertexAttribArray( 0 );
glDisableVertexAttribArray( 1 );
glDisableVertexAttribArray( 2 );
glDisableVertexAttribArray( 3 );
glDisableVertexAttribArray( 4 );
glDisableVertexAttribArray( 5 );
glDisableVertexAttribArray( 6 );
glDisableVertexAttribArray( 7 );
int offset = 0, stride = mesh->vert_stride;
G_SetupVertexArray( _shader, "in_pos", stride, 3, &offset, -1/*0*/ );
G_SetupVertexArray( _shader, "in_texcoord", stride, 2, &offset, -1/*1*/ );
G_SetupVertexArray( _shader, "in_normal", stride, 3, &offset, -1/*2*/ );
G_SetupVertexArray( _shader, "in_tangent", stride, 4, &offset, -1/*3*/ );
//glBindVertexArray( 0 );
}
}
// -----------------------------------------------------------------------------
// skyboxes
// Extracts single key value from an HDRLabs IBL file. Returns an empty string on error.
static const char *ibl_readkey( const char* pathfile, const char* key ) {
char *data = vfs_read(pathfile);
if( data ) {
const char *found = strstr(data, va("%s=", key));
if( found ) return found + strlen(key) + 1;
found = strstr(data, va("%s =", key));
if( found ) return found + strlen(key) + 2;
}
return "";
}
typedef struct Skybox {
vec3 sunColor; float sunYaw, sunPitch; // ibl settings
texture_t *reflection; // reflection map (hdr)
texture_t *env; // irradiance map (env)
} Skybox;
Skybox g_skybox = { {1,1,1} };
void SkyboxDestroy( Skybox *s ) {
if( s->reflection ) texture_destroy( s->reflection );
if( s->env ) texture_destroy( s->env );
*s = (Skybox){0};
}
bool SkyboxLoad( Skybox *s, const char **slots ) { // hdr,env,ibl
SkyboxDestroy( s );
const char* reflectionPath = slots[0];
const char* envPath = slots[1];
const char* iblPath = slots[2];
// unsigned invalid = texture_checker().id;
// Reflection map
if( reflectionPath ) {
if( (s->reflection = LoadTextureRGBA8( reflectionPath, TEXTURE_SRGB )) != NULL ) {
glBindTexture( GL_TEXTURE_2D, s->reflection->id );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
// Irradiance map
if( envPath ) {
if( (s->env = LoadTextureRGBA8( envPath, TEXTURE_SRGB )) != NULL ) {
glBindTexture( GL_TEXTURE_2D, s->env->id );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
// Sun color & direction from .ibl file
s->sunColor = vec3(1,1,1);
s->sunYaw = 0, s->sunPitch = 0;
if( iblPath ) {
vec3 sc; if( 3 == sscanf(ibl_readkey(iblPath, "SUNcolor"), "%f,%f,%f", &sc.x, &sc.y, &sc.z) ) {
s->sunColor = scale3(sc, 1/255.f);
}
vec2 uv = vec2(atof(ibl_readkey(iblPath, "SUNu")), atof(ibl_readkey(iblPath, "SUNv")));
if( len2(uv) > 0 ) {
s->sunYaw = C_PI * (-2. * uv.x + 0.5f);
s->sunPitch = (0.5f - uv.y) * C_PI;
}
}
return s->reflection && s->env;
}
// -----------------------------------------------------------------------------
// main
const char *shader_names[] = {"Physically Based", "Basic SpecGloss" };
const char *shaders[][2] = { // name, vs, fs
{ "Shaders/pbr.vs", "Shaders/pbr.fs" },
{ "Shaders/basic_specgloss.vs", "Shaders/basic_specgloss.fs" }
};
const char *skyboxes[][3] = { // reflection, env, metadata
{"hdr/Tokyo_BigSight_1k.hdr","hdr/Tokyo_BigSight_Env.hdr","hdr/Tokyo_BigSight.ibl"},
{"hdr/GCanyon_C_YumaPoint_1k.hdr","hdr/GCanyon_C_YumaPoint_Env.hdr","hdr/GCanyon_C_YumaPoint.ibl"},
{"hdr/Factory_Catwalk_1k.hdr","hdr/Factory_Catwalk_Env.hdr","hdr/Factory_Catwalk.ibl"},
{"hdr/MonValley_G_DirtRoad_1k.hdr","hdr/MonValley_G_DirtRoad_Env.hdr","hdr/MonValley_G_DirtRoad.ibl"},
{"hdr/Shiodome_Stairs_1k.hdr","hdr/Shiodome_Stairs_Env.hdr","hdr/Shiodome_Stairs.ibl"},
};
Model gModel;
unsigned gShader = ~0u;
unsigned gShaderConfig = ~0u;
bool LoadShaderConfig( int slot ) { // name,vs,fs
unsigned newShader = shader( vfs_read(shaders[slot][0]), vfs_read(shaders[slot][1]), NULL, NULL );
if( newShader == ~0u ) return false;
shader_destroy( gShader );
gShaderConfig = slot;
gShader = newShader;
return true;
}
void camera_fit(camera_t *cam) {
vec3 target = scale3( add3( gModel.meshes[0].aabb_min, gModel.meshes[0].aabb_max ), 0.5f);
float distance = len3( sub3( gModel.meshes[0].aabb_max, gModel.meshes[0].aabb_min ) ) * 0.85f;
cam->position = add3(target, scale3(norm3(sub3(cam->position,target)), distance));
camera_lookat(cam, vec3(0,0,0));
}
int main( int argc, const char *argv[] ) {
window_create( 75, WINDOW_MSAA2 );
window_title(__FILE__);
// load all fx files in all subdirs
fx_load("fx**.fs");
if( !LoadShaderConfig( 0 ) ) {
return -4;
}
brdf_lut();
// -------------------------------------------------------------------------
// Mainloop
float model_yaw = 0, model_pitch = 0;
float lightYaw = 0.0f;
float lightPitch = 0.0f;
vec4 skyBackgroundColor = vec4(0.01,0.01,0.02,1); // vec4(1,0,0,1);
float skyExposure = 1.0; // plain 'exposure' instead? this is camera related
float skyBlur = 0.00; // 0.00
float skyOpacity = 0.99; // 0.99
bool do_wireframe = false;
bool do_xzySpace = true; // xzySpace or xyzSpace
bool do_flipY = false;
const mat44 xzyMatrix = {
1, 0, 0, 0,
0, 0, 1, 0,
0,+1, 0, 0,
0, 0, 0, 1 };
camera_t cam = camera(); cam.speed = 0.1;
int firstskyboxes = 0; // 0: tokyo_bigsight
SkyboxLoad( &g_skybox, &skyboxes[firstskyboxes][0] );
lightYaw = g_skybox.sunYaw;
lightPitch = g_skybox.sunPitch;
unsigned skysphereShader = shader( vfs_read("Skyboxes/skysphere.vs"), vfs_read("Skyboxes/skysphere.fs"), NULL, NULL );
Model skysphere = { 0 }; ModelLoad(&skysphere, "Skyboxes/skysphere.fbx"); ModelRebind(&skysphere, skysphereShader);
if( ModelLoad( &gModel, argc > 1 && argv[1][0] != '-' ? argv[ 1 ] : "damagedhelmet.gltf" ) ) {
ModelRebind( &gModel, gShader );
}
cam.position = vec3(+1,0,+1);
camera_fit(&cam);
static mat44 worldRootXYZ; do_once id44(worldRootXYZ); // mat44( 1.0f );
while( window_swap() && !input(KEY_ESC) ) {
if( input(KEY_F5) ) window_reload();
if( input_down( KEY_F ) ) camera_fit(&cam);
// ---------------------------------------------------------------------
static int fps_mode;
if(input_down(KEY_TAB)) { fps_mode ^= 1; camera_fit(&cam); }
if(fps_mode) {
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
window_cursor( !active );
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdecq.x,wasdecq.y,wasdecq.z);
camera_fps(&cam, mouse.x,mouse.y);
} else {
// orbit camera
window_cursor( true );
bool active = !ui_active() && !ui_hover() && !gizmo_active();
vec2 inc_mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active * input(MOUSE_L));
float inc_distance = -0.2f * active * input_diff(MOUSE_W);
camera_orbit(&cam, inc_mouse.x, inc_mouse.y, inc_distance);
// rotate model
model_yaw -= input_diff(MOUSE_X) * 0.2f * active * input(MOUSE_R);
model_pitch += input_diff(MOUSE_Y) * 0.2f * active * input(MOUSE_R);
}
// ---------------------------------------------------------------------
glClearColor( skyBackgroundColor.r, skyBackgroundColor.g, skyBackgroundColor.b, skyBackgroundColor.a );
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
// ---------------------------------------------------------------------
// Mesh state
fx_begin();
profile("PBR Model (bindings)") {
ModelRebind( &gModel, gShader );
shader_mat44( "mat_projection", cam.proj );
//cameraPosition = scale3(cameraPosition, gCameraDistance);
shader_vec3( "camera_position", cam.position );
vec3 lightDirection = vec3( 0, 0, 1 );
lightDirection = rotatex3( lightDirection, deg(lightPitch) );
lightDirection = rotatey3( lightDirection, deg(lightYaw) );
vec3 fillLightDirection = vec3( 0, 0, 1 );
fillLightDirection = rotatex3( fillLightDirection, deg(lightPitch - 0.4f) );
fillLightDirection = rotatey3( fillLightDirection, deg(lightYaw + 0.8f) );
shader_vec3( "lights[0].direction", lightDirection );
shader_vec3( "lights[0].color", g_skybox.sunColor );
shader_vec3( "lights[1].direction", fillLightDirection );
shader_vec3( "lights[1].color", vec3( 0.5f, 0.5f, 0.5f ) );
shader_vec3( "lights[2].direction", neg3(fillLightDirection) );
shader_vec3( "lights[2].color", vec3( 0.25f, 0.25f, 0.25f ) );
shader_float( "skysphere_rotation", lightYaw - g_skybox.sunYaw );
mat44 viewMatrix, inv_viewMatrix;
copy44(viewMatrix, cam.view);
invert44( inv_viewMatrix, viewMatrix);
shader_mat44( "mat_view", viewMatrix );
shader_mat44( "mat_view_inverse", inv_viewMatrix );
shader_bool( "has_tex_skysphere", g_skybox.reflection != NULL );
shader_bool( "has_tex_skyenv", g_skybox.env != NULL );
if( g_skybox.reflection ) {
float mipCount = floor( log2( g_skybox.reflection->h ) );
shader_texture( "tex_skysphere", *g_skybox.reflection );
shader_float( "skysphere_mip_count", mipCount );
}
if( g_skybox.env ) {
shader_texture( "tex_skyenv", *g_skybox.env );
}
shader_texture( "tex_brdf_lut", brdf_lut() );
shader_float( "exposure", skyExposure );
shader_uint( "frame_count", (unsigned)window_frame() );
}
// ---------------------------------------------------------------------
// Mesh render
mat44 M;
copy44( M, do_xzySpace ? xzyMatrix : worldRootXYZ );
if( do_flipY ) scale44( M, 1,-1,1 );
rotate44( M, model_yaw, 0,0,1 );
rotate44( M, model_pitch, 1,0,0 );
profile("PBR Model (render)") {
ModelRender( &gModel, M );
}
profile("PBR Model (wireframe)") {
if( do_wireframe ) {
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
glDepthFunc( GL_LEQUAL );
shader_float("exposure", 100.0f );
ModelRender( &gModel, M );
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glDepthFunc( GL_LESS );
}
}
// ---------------------------------------------------------------------
// Skysphere render
profile("PBR Skybox") {
ModelRebind(&skysphere, skysphereShader );
mat44 projview; multiply44x2(projview, cam.proj, cam.view);
shader_mat44( "mat_mvp", projview );
shader_bool( "has_tex_skysphere", g_skybox.reflection != NULL );
shader_bool( "has_tex_skyenv", g_skybox.env != NULL );
if( g_skybox.reflection ) {
const float mipCount = floor( log2( g_skybox.reflection->h ) );
shader_texture( "tex_skysphere", *g_skybox.reflection );
shader_float( "skysphere_mip_count", mipCount );
}
if( g_skybox.env ) {
shader_texture( "tex_skyenv", *g_skybox.env );
}
shader_vec4( "background_color", skyBackgroundColor );
shader_float( "skysphere_blur", skyBlur );
shader_float( "skysphere_opacity", skyOpacity );
shader_float( "skysphere_rotation", lightYaw - g_skybox.sunYaw );
shader_float( "exposure", skyExposure );
shader_uint( "frame_count", (unsigned)window_frame() );
glDepthFunc( GL_LEQUAL );
ModelRender(&skysphere, worldRootXYZ );
glDepthFunc( GL_LESS );
}
fx_end();
// ---------------------------------------------------------------------
// UI
if( ui_panel( "Viewer", 0 ) ) {
ui_bool( "Wireframe", &do_wireframe );
ui_separator();
if( ui_radio("Shader config:", shader_names, countof(shader_names), &gShaderConfig) ) {
LoadShaderConfig( gShaderConfig );
ModelRebind(&gModel, gShader );
}
ui_separator();
for( int i = 0; i < countof(skyboxes); i++ ) {
const char *filename = skyboxes[i][0];
bool selected = !strcmp(g_skybox.reflection->filename, file_name(filename));
if( ui_bool( filename, &selected ) ) {
SkyboxLoad( &g_skybox, &skyboxes[i][0] );
lightYaw = g_skybox.sunYaw;
lightPitch = g_skybox.sunPitch;
}
}
ui_separator();
ui_float( "Sky exposure", &skyExposure); skyExposure = clampf(skyExposure, 0.1f, 4.0f );
ui_float( "Sky blur", &skyBlur); skyBlur = clampf(skyBlur, 0.0f, 1.0f );
ui_float( "Sky opacity", &skyOpacity); skyOpacity = clampf(skyOpacity, 0.0f, 1.0f );
ui_color4f( "Sky background", (float *) &skyBackgroundColor.x );
ui_separator();
ui_float( "SunLight Yaw", &lightYaw );
ui_float( "SunLight Pitch", &lightPitch );
ui_panel_end();
}
if( ui_panel( "Model", 0 ) ) {
ui_label(va("Material count: %d", array_count(gModel.materials)));
ui_label(va("Mesh count: %d", array_count(gModel.meshes)));
int triCount = 0; for( int i = 0, end = array_count(gModel.meshes); i < end; ++i ) triCount += gModel.meshes[i].num_tris;
ui_label(va("Triangle count: %d", triCount));
ui_separator();
bool xyzSpace = !do_xzySpace;
if( ui_bool( "XYZ space", &xyzSpace ) ) {
do_xzySpace = !do_xzySpace;
}
ui_bool( "XZY space", &do_xzySpace );
ui_bool( "invert Y", &do_flipY );
ui_separator();
for( int i = 0, end = array_count(gModel.materials); i < end; ++i ) {
pbr_material_t *it = &gModel.materials[i];
ui_label(va("Name: %s", it->name));
ui_float( "Specular shininess", &it->specular_shininess );
ui_separator(); if(ui_colormap( "Albedo", &it->albedo )) colormap(&it->albedo , dialog_load(), 1);
ui_separator(); if(ui_colormap( "Ambient", &it->ambient )) colormap(&it->ambient , dialog_load(), 0);
ui_separator(); if(ui_colormap( "AO", &it->ao )) colormap(&it->ao , dialog_load(), 0);
ui_separator(); if(ui_colormap( "Diffuse", &it->diffuse )) colormap(&it->diffuse , dialog_load(), 1);
ui_separator(); if(ui_colormap( "Emissive", &it->emissive )) colormap(&it->emissive , dialog_load(), 1);
ui_separator(); if(ui_colormap( "Metallic", &it->metallic )) colormap(&it->metallic , dialog_load(), 0);
ui_separator(); if(ui_colormap( "Normal", &it->normals )) colormap(&it->normals , dialog_load(), 0);
ui_separator(); if(ui_colormap( "Roughness", &it->roughness )) colormap(&it->roughness, dialog_load(), 0);
ui_separator(); if(ui_colormap( "Specular", &it->specular )) colormap(&it->specular , dialog_load(), 0);
}
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
}
ui_panel_end();
}
if( ui_panel("Help", 0)) {
if( fps_mode ) {
ui_label("TAB: switch to Orbit camera mode");
ui_label("WASD,QEC: move camera");
ui_label("Drag + Mouse Button: camera freelook");
} else {
ui_label("TAB: switch to FPS camera mode");
ui_label("Drag + Left Mouse Button: orbit camera");
ui_label("Drag + Right Mouse Button: rotate model");
ui_label("Mouse wheel: camera distance");
}
ui_label("F: center view");
ui_panel_end();
}
}
}

28
demos/99-script.c 100644
View File

@ -0,0 +1,28 @@
#include "fwk.h"
#define SCRIPT(...) #__VA_ARGS__
#if 0 // teal
script_run("local tl=require(\"tl\")\ntl.loader()");
script_run("addsub = require(\"s2\"); print (addsub.add(10, 20))");
s2.tl:
local function add(a: number, b: number): number
return a + b
end
local s = add(1,2)
print(s)
#endif
int main() {
script_init();
script_run(SCRIPT(
window_create(75.0,0);
while window_swap() do
ddraw_grid(10);
if ui_panel("Hello from Lua!", 0) then
ui_panel_end();
end;
end;
));
}

View File

@ -0,0 +1,43 @@
// shadertoy viewer
// - rlyeh, public domain
#include "fwk.h"
int main() {
window_create(75, 0); // WINDOW_MSAA8);
window_title(__FILE__);
const char **list = file_list("demos/art/shadertoys/", "**.fs");
if(!list[0]) exit(-1);
array(char*) browser = 0;
while(*list) array_push(browser, STRDUP(file_name(*list++)));
int browser_count = array_count(browser);
shadertoy_t sh = {0};
while(window_swap()) {
// selector
int next = input_down(KEY_UP) || input_down(KEY_LEFT);
int prev = input_down(KEY_DOWN) || input_down(KEY_RIGHT);
static int selector = 0;
static int reload = 1;
if( next ) if( selector > 0 ) --selector, reload = 1;
if( prev ) if( selector < browser_count - 1 ) ++selector, reload = 1;
if( reload ) {
reload = 0;
window_title(va("FWK - %s", browser[selector]));
sh = shadertoy( browser[selector], 0 );
}
// draw
shadertoy_render(&sh, window_delta());
// UI
if( ui_panel("Shadertoy", 0)) {
if( ui_list("In use", (const char**)browser, browser_count, &selector) ) {
reload = 1;
}
ui_panel_end();
}
}
}

568
demos/99-spine.c 100644
View File

@ -0,0 +1,568 @@
// spine json loader (wip)
// - rlyeh, public domain
//
// [ref] http://es.esotericsoftware.com/spine-json-format
//
// notable misses:
// - mesh deforms
// - cubic beziers
// - shears
// - bounding boxes
#include "fwk.h"
#define spine spine2
#define spine_render spine_render2
#define spine_ui spine_ui2
#define spine_animate spine_animate2
#define spine_skin spine_skin2
enum { _64 = 64 }; // max bones
typedef struct spine_bone_t {
char *name, *parent;
struct spine_bone_t *parent_bone;
float z; // draw order usually matches bone-id. ie, zindex == bone_id .. root(0) < chest (mid) < finger(top)
float x, y, deg; // base
float x2, y2, deg2; // accum / temporaries during bone transform time
float x3, y3, deg3; // values from timeline
unsigned rect_id;
unsigned atlas_id;
} spine_bone_t;
typedef struct spine_slot_t {
char *name, *bone, *attach;
} spine_slot_t;
typedef struct spine_rect_t {
char *name;
float x,y,w,h,sx,sy,deg;
} spine_rect_t;
typedef struct spine_skin_t {
char *name;
array(spine_rect_t) rects;
} spine_skin_t;
typedef struct spine_animkey_t { // offline; only during loading
float time, curve[4]; // time is mandatory, curve is optional
union {
char *name; // type: attachment (mode-1)
struct { float deg; }; // type: rotate (mode-2)
struct { float x,y; }; // type: translate (mode-3)
};
} spine_animkey_t;
#if 0
typedef struct spine_pose_t { // runtime; only during playing
unsigned frame;
array(vec4) xform; // entry per bone. translation(x,y),rotation(z),attachment-id(w)
} spine_pose_t;
#endif
typedef struct spine_anim_t {
char *name;
union {
#if 0
struct {
unsigned frames;
array(spine_pose_t) poses;
};
#endif
struct {
array(spine_animkey_t) attach_keys[_64];
array(spine_animkey_t) rotate_keys[_64];
array(spine_animkey_t) translate_keys[_64];
};
};
} spine_anim_t;
typedef struct spine_atlas_t {
char *name;
float x,y,w,h,deg;
} spine_atlas_t;
typedef struct spine_t {
char *name;
texture_t texture;
unsigned skin;
array(spine_bone_t) bones;
array(spine_slot_t) slots;
array(spine_skin_t) skins;
array(spine_anim_t) anims;
array(spine_atlas_t) atlas;
// anim controller
unsigned inuse;
float time, maxtime;
} spine_t;
// ---
void spine_convert_animkeys_to_animpose(spine_anim_t *input) {
spine_anim_t copy = *input; // @todo
// @leak: attach/rot/tra keys
}
int find_bone_id(spine_t *s, const char *bone_name) {
for( unsigned i = 0, end = array_count(s->bones); i < end; ++i )
if( !strcmp(s->bones[i].name, bone_name)) return i;
return -1;
}
spine_bone_t *find_bone(spine_t *s, const char *bone_name) {
int bone_id = find_bone_id(s, bone_name);
return bone_id >= 0 ? &s->bones[bone_id] : NULL;
}
void spine_skin(spine_t *p, unsigned skin) {
if( !p->texture.id ) return;
if( skin >= array_count(p->skins) ) return;
p->skin = skin;
char *skin_name = va("%s/", p->skins[skin].name);
int header = strlen(skin_name);
for( int i = 0; i < array_count(p->atlas); ++i) {
if(!strbeg(p->atlas[i].name, skin_name)) continue;
int bone_id = find_bone_id(p, p->atlas[i].name+header );
if( bone_id < 0 ) continue;
p->bones[bone_id].atlas_id = i;
}
for( int i = 0; i < array_count(p->skins[p->skin].rects); ++i) {
int bone_id = find_bone_id(p, p->skins[p->skin].rects[i].name );
if( bone_id < 0 ) continue;
p->bones[bone_id].rect_id = i;
}
}
spine_t spine(const char *file_json, const char *file_atlas, unsigned flags) {
spine_t z = {0}, t = z;
char *atlas = vfs_read(file_atlas);
if(!atlas || !atlas[0]) return z;
// goblins.png
// size: 1024, 128
// filter: Linear, Linear
// pma: true
// dagger
// bounds: 2, 18, 26, 108
// goblin/eyes-closed
// bounds: 2, 4, 34, 12
spine_atlas_t *sa = 0;
const char *last_id = 0;
const char *texture_name = 0;
const char *texture_filter = 0;
const char *texture_format = 0;
const char *texture_repeat = 0;
float texture_width = 0, texture_height = 0, temp;
for each_substring(atlas, "\r\n", it) {
it += strspn(it, " \t\f\v");
/**/ if( strbeg(it, "pma:" ) || strbeg(it, "index:") ) {} // ignored
else if( strbeg(it, "size:" ) ) sscanf(it+5, "%f,%f", &texture_width, &texture_height);
else if( strbeg(it, "rotate:" ) ) { float tmp; tmp=sa->w,sa->w=sa->h,sa->h=tmp; sa->deg = 90; } // assert(val==90)
else if( strbeg(it, "repeat:" ) ) texture_repeat = it+7; // temp string
else if( strbeg(it, "filter:" ) ) texture_filter = it+7; // temp string
else if( strbeg(it, "format:" ) ) texture_format = it+7; // temp string
else if( strbeg(it, "bounds:" ) ) {
sscanf(it+7, "%f,%f,%f,%f", &sa->x, &sa->y, &sa->w, &sa->h);
}
else if( !texture_name ) texture_name = va("%s", it);
else {
array_push(t.atlas, ((spine_atlas_t){0}) );
sa = &t.atlas[array_count(t.atlas) - 1];
sa->name = STRDUP(it);
}
}
for( int i = 0; i < array_count(t.atlas); ++i ) {
sa = &t.atlas[i];
sa->x /= texture_width, sa->y /= texture_height;
sa->w /= texture_width, sa->h /= texture_height;
}
if(!texture_name) return z;
t.texture = texture(texture_name, 0); // @todo: add texture flags here
json_push(vfs_read(file_json)); // @fixme: json_push_from_file() ?
array_resize(t.bones, json_count("/bones"));
array_reserve(t.slots, json_count("/slots"));
array_resize(t.skins, json_count("/skins"));
array_resize(t.anims, json_count("/animations"));
for( int i = 0, end = json_count("/bones"); i < end; ++i ) {
spine_bone_t v = {0};
v.name = STRDUP(json_string("/bones[%d]/name", i));
v.parent = STRDUP(json_string("/bones[%d]/parent", i));
v.x = json_float("/bones[%d]/x", i);
v.y = json_float("/bones[%d]/y", i);
v.z = i;
v.deg = json_float("/bones[%d]/rotation", i);
t.bones[i] = v;
for( int j = i-1; j > 0; --j ) {
if( strcmp(t.bones[j].name,v.parent) ) continue;
t.bones[i].parent_bone = &t.bones[j];
break;
}
}
for( int i = 0, end = json_count("/slots"); i < end; ++i ) {
spine_slot_t v = {0};
v.name = STRDUP(json_string("/slots[%d]/name", i));
v.bone = STRDUP(json_string("/slots[%d]/bone", i));
v.attach = STRDUP(json_string("/slots[%d]/attachment", i));
array_push(t.slots, v);
// slots define draw-order. so, update draw-order/zindex in bone
spine_bone_t *b = find_bone(&t, v.name);
if( b ) b->z = i;
}
for( int i = 0, end = json_count("/skins"); i < end; ++i ) {
spine_skin_t v = {0};
v.name = STRDUP(json_string("/skins[%d]/name", i));
for( int j = 0, jend = json_count("/skins[%d]/attachments",i); j < jend; ++j ) // /skins/default/
for( int k = 0, kend = json_count("/skins[%d]/attachments[%d]",i,j); k < kend; ++k ) { // /skins/default/left hand item/
spine_rect_t r = {0};
r.name = STRDUP(json_key("/skins[%d]/attachments[%d][%d]",i,j,k)); // stringf("%s-%s-%s", json_key("/skins[%d]",i), json_key("/skins[%d][%d]",i,j), json_key("/skins[%d][%d][%d]",i,j,k));
r.x = json_float("/skins[%d]/attachments[%d][%d]/x",i,j,k);
r.y = json_float("/skins[%d]/attachments[%d][%d]/y",i,j,k);
r.sx= json_float("/skins[%d]/attachments[%d][%d]/scaleX",i,j,k); r.sx += !r.sx;
r.sy= json_float("/skins[%d]/attachments[%d][%d]/scaleY",i,j,k); r.sy += !r.sy;
r.w = json_float("/skins[%d]/attachments[%d][%d]/width",i,j,k);
r.h = json_float("/skins[%d]/attachments[%d][%d]/height",i,j,k);
r.deg = json_float("/skins[%d]/attachments[%d][%d]/rotation",i,j,k);
array_push(v.rects, r);
}
t.skins[i] = v;
}
#if 1
// simplify:
// merge /skins/default into existing /skins/*, then delete /skins/default
if( array_count(t.skins) > 1 ) {
for( int i = 1; i < array_count(t.skins); ++i ) {
for( int j = 0; j < array_count(t.skins[0].rects); ++j ) {
array_push(t.skins[i].rects, t.skins[0].rects[j]);
}
}
// @leak @fixme: free(t.skins[0])
for( int i = 0; i < array_count(t.skins)-1; ++i ) {
t.skins[i] = t.skins[i+1];
}
array_pop(t.skins);
}
#endif
for( int i = 0, end = json_count("/animations"); i < end; ++i ) {
int id;
const char *name;
spine_anim_t v = {0};
v.name = STRDUP(json_key("/animations[%d]", i));
// slots / attachments
for( int j = 0, jend = json_count("/animations[%d]/slots",i); j < jend; ++j )
for( int k = 0, kend = json_count("/animations[%d]/slots[%d]",i,j); k < kend; ++k ) // ids
{
int bone_id = find_bone_id(&t, json_key("/animations[%d]/bones[%d]",i,j));
if( bone_id < 0 ) continue;
for( int l = 0, lend = json_count("/animations[%d]/slots[%d][%d]",i,j,k); l < lend; ++l ) { // channels (rot,tra,attach)
spine_animkey_t key = {0};
key.name = STRDUP(json_string("/animations[%d]/slots[%d][%d][%d]/name",i,j,k,l));
key.time = json_float("/animations[%d]/slots[%d][%d][%d]/time",i,j,k,l);
if( json_count("/animations[%d]/slots[%d][%d][%d]/curve",i,j,k,l) == 4 ) {
key.curve[0] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[0]",i,j,k,l);
key.curve[1] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[1]",i,j,k,l);
key.curve[2] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[2]",i,j,k,l);
key.curve[3] = json_float("/animations[%d]/slots[%d][%d][%d]/curve[3]",i,j,k,l);
}
// @todo: convert name to id
// for(id = 0; t.bones[id].name && strcmp(t.bones[id].name,key.name); ++id)
// printf("%s vs %s\n", key.name, t.bones[id].name);
array_push(v.attach_keys[bone_id], key);
}
}
// bones
for( int j = 0, jend = json_count("/animations[%d]/bones",i); j < jend; ++j ) // slots or bones
for( int k = 0, kend = json_count("/animations[%d]/bones[%d]",i,j); k < kend; ++k ) { // bone ids
int bone_id = find_bone_id(&t, json_key("/animations[%d]/bones[%d]",i,j));
if( bone_id < 0 ) continue;
// parse bones
for( int l = 0, lend = json_count("/animations[%d]/bones[%d][%d]",i,j,k); l < lend; ++l ) { // channels (rot,tra,attach)
const char *channel = json_key("/animations[%d]/bones[%d][%d]",i,j,k);
int track = !strcmp(channel, "rotate") ? 1 : !strcmp(channel, "translate") ? 2 : 0;
if( !track ) continue;
spine_animkey_t key = {0};
key.time = json_float("/animations[%d]/bones[%d][%d][%d]/time",i,j,k,l);
if( json_count("/animations[%d]/bones[%d][%d][%d]/curve",i,j,k,l) == 4 ) {
key.curve[0] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[0]",i,j,k,l);
key.curve[1] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[1]",i,j,k,l);
key.curve[2] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[2]",i,j,k,l);
key.curve[3] = json_float("/animations[%d]/bones[%d][%d][%d]/curve[3]",i,j,k,l);
}
if( track == 1 )
key.deg = json_float("/animations[%d]/bones[%d][%d][%d]/value",i,j,k,l), // "/angle"
array_push(v.rotate_keys[bone_id], key);
else
key.x = json_float("/animations[%d]/bones[%d][%d][%d]/x",i,j,k,l),
key.y = json_float("/animations[%d]/bones[%d][%d][%d]/y",i,j,k,l),
array_push(v.translate_keys[bone_id], key);
}
}
t.anims[i] = v;
}
json_pop();
spine_skin(&t, 0);
return t;
}
void spine_render(spine_t *p, vec3 offset, unsigned flags) {
if( !p->texture.id ) return;
if( !flags ) return;
ddraw_push_2d();
if( flags & 2 ) ddraw_line(vec3(0,0,0), vec3(window_width(),window_height(),0));
if( flags & 2 ) ddraw_line(vec3(window_width(),0,0), vec3(0,window_height(),0));
for( int i = 1; i < array_count(p->bones); ++i ) {
spine_bone_t *self = &p->bones[i];
static array(spine_bone_t*) chain = 0; array_resize(chain, 0);
for( spine_bone_t *next = self; next ; next = next->parent_bone ) {
array_push(chain, next);
}
vec3 target = {0}, prev = {0};
for( int j = 1, end = array_count(chain); j < end; ++j ) { // traverse from root(skipped) -> `i` bone direction
int j_opposite = (end - 1) - j;
spine_bone_t *b = chain[j_opposite]; // bone
spine_bone_t *pb = chain[j_opposite+1]; // parent bone
prev = target;
const float deg2rad = C_PI / 180;
b->x2 = b->x3 + pb->x2 + b->x * cos( -pb->deg2 * deg2rad ) - b->y * sin( -pb->deg2 * deg2rad );
b->y2 = -b->y3 + pb->y2 - b->y * cos( pb->deg2 * deg2rad ) + b->x * sin( pb->deg2 * deg2rad );
b->deg2 = -b->deg3 + pb->deg2 - b->deg;
target = vec3(b->x2,b->y2,b->deg2);
}
float deg = target.z, deg_prev = prev.z;
target.z = 0; prev.z = 0;
target = add3(target, offset);
prev = add3(prev, offset);
if( flags & 2 ) {
ddraw_point( target );
ddraw_text( target, -0.25f, self->name );
ddraw_line( target, prev ); // from bone to parent
}
if( flags & 1 ) {
vec4 rect = ptr4(&p->atlas[self->atlas_id].x);
float zindex = self->z;
float offsx = 0; // -(rect.w * p->texture.w); // -p->atlas[self->atlas_id].w - (self->rect_id ? p->skins[p->skin].rects[self->rect_id].w/2 : 0);
float offsy = 0; // /*-(rect.z * p->texture.h)*2*/ -p->atlas[self->atlas_id].h - (self->rect_id ? p->skins[p->skin].rects[self->rect_id].h/2 : 0);
float deg_rect = self->rect_id ? p->skins[p->skin].rects[self->rect_id].deg : 0;
float tilt = p->atlas[self->atlas_id].deg + self->deg2 - deg_rect; // + self->deg2 + deg_rect + p->atlas[self->atlas_id].deg
unsigned tint = ~0u;
sprite_rect(p->texture, rect, zindex, add3(vec3(target.x,target.y,1),vec3(offsx,offsy,0)), tilt, tint);
}
}
ddraw_pop_2d();
ddraw_flush();
}
void spine_animate(spine_t *p, float *time, float *maxtime, float delta) {
if( !p->texture.id ) return;
if( delta > 1/120.f ) delta = 1/120.f;
if( *time >= *maxtime ) *time = 0; else *time += delta;
// reset root // needed?
p->bones[0].x2 = 0;
p->bones[0].y2 = 0;
p->bones[0].deg2 = 0;
p->bones[0].x3 = 0;
p->bones[0].y3 = 0;
p->bones[0].deg3 = 0;
for( int i = 0, end = array_count(p->bones); i < end; ++i) {
// @todo: attach channel
// @todo: per channel: if curve == linear || curve == stepped || array_count(curve) == 4 {...}
for each_array_ptr(p->anims[p->inuse].rotate_keys[i], spine_animkey_t, r) {
double r0 = r->time;
*maxtime = maxf( *maxtime, r0 );
if( absf(*time - r0) < delta ) {
p->bones[i].deg3 = r->deg;
}
}
for each_array_ptr(p->anims[p->inuse].translate_keys[i], spine_animkey_t, r) {
double r0 = r->time;
*maxtime = maxf( *maxtime, r0 );
if( absf(*time - r0) < delta ) {
p->bones[i].x3 = r->x;
p->bones[i].y3 = r->y;
}
}
}
}
void spine_ui(spine_t *p) {
if( ui_collapse(va("Anims: %d", array_count(p->anims)), va("%p-a", p))) {
for each_array_ptr(p->anims, spine_anim_t, q) {
if(ui_slider2("", &p->time, va("%.2f/%.0f %.2f%%", p->time, p->maxtime, p->time * 100.f))) {
spine_animate(p, &p->time, &p->maxtime, 0);
}
int choice = ui_label2_toolbar(q->name, ICON_MD_PAUSE_CIRCLE " " ICON_MD_PLAY_CIRCLE);
if( choice == 1 ) window_pause( 0 ); // play
if( choice == 2 ) window_pause( 1 ); // pause
for( int i = 0; i < _64; ++i ) {
ui_separator();
ui_label(va("Bone %d: Attachment keys", i));
for each_array_ptr(q->attach_keys[i], spine_animkey_t, r) {
ui_label(va("%.2f [%.2f %.2f %.2f %.2f] %s", r->time, r->curve[0], r->curve[1], r->curve[2], r->curve[3], r->name));
}
ui_label(va("Bone %d: Rotate keys", i));
for each_array_ptr(q->rotate_keys[i], spine_animkey_t, r) {
ui_label(va("%.2f [%.2f %.2f %.2f %.2f] %.2f deg", r->time, r->curve[0], r->curve[1], r->curve[2], r->curve[3], r->deg));
}
ui_label(va("Bone %d: Translate keys", i));
for each_array_ptr(q->translate_keys[i], spine_animkey_t, r) {
ui_label(va("%.2f [%.2f %.2f %.2f %.2f] (%.2f,%.2f)", r->time, r->curve[0], r->curve[1], r->curve[2], r->curve[3], r->x, r->y));
}
}
}
ui_collapse_end();
}
if( ui_collapse(va("Bones: %d", array_count(p->bones)), va("%p-b", p))) {
for each_array_ptr(p->bones, spine_bone_t, q)
if( ui_collapse(q->name, va("%p-b2", q)) ) {
ui_label2("Parent:", q->parent);
ui_label2("X:", va("%.2f", q->x));
ui_label2("Y:", va("%.2f", q->y));
ui_label2("Rotation:", va("%.2f", q->deg));
ui_collapse_end();
}
ui_collapse_end();
}
if( ui_collapse(va("Slots: %d", array_count(p->slots)), va("%p-s", p))) {
for each_array_ptr(p->slots, spine_slot_t, q)
if( ui_collapse(q->name, va("%p-s2", q)) ) {
ui_label2("Bone:", q->bone);
ui_label2("Attachment:", q->attach);
ui_collapse_end();
}
ui_collapse_end();
}
if( ui_collapse(va("Skins: %d", array_count(p->skins)), va("%p-k", p))) {
for each_array_ptr(p->skins, spine_skin_t, q)
if( ui_collapse(q->name, va("%p-k2", q)) ) {
for each_array_ptr(q->rects, spine_rect_t, r)
if( ui_collapse(r->name, va("%p-k3", r)) ) {
ui_label2("X:", va("%.2f", r->x));
ui_label2("Y:", va("%.2f", r->y));
ui_label2("Scale X:", va("%.2f", r->sx));
ui_label2("Scale Y:", va("%.2f", r->sy));
ui_label2("Width:", va("%.2f", r->w));
ui_label2("Height:", va("%.2f", r->h));
ui_label2("Rotation:", va("%.2f", r->deg));
ui_collapse_end();
spine_bone_t *b = find_bone(p, r->name);
if( b ) {
static float tilt = 0;
if( input(KEY_LCTRL) ) tilt += 60*1/60.f; else tilt = 0;
spine_atlas_t *r = p->atlas + b->atlas_id;
sprite_flush();
camera_get_active()->position = vec3(0,0,2);
vec4 rect = ptr4(&r->x); float zindex = 0; vec3 xy_zoom = vec3(0,0,0); unsigned tint = ~0u;
sprite_rect(p->texture,
// rect: vec4(r->x*1.0/p->texture.w,r->y*1.0/p->texture.h,(r->x+r->w)*1.0/p->texture.w,(r->y+r->h)*1.0/p->texture.h),
ptr4(&r->x), // atlas
0, vec3(0,0,0), r->deg + tilt, tint);
sprite_flush();
camera_get_active()->position = vec3(+window_width()/3,window_height()/2.25,2);
}
}
ui_collapse_end();
}
ui_collapse_end();
}
if( ui_int("Use skin", &p->skin) ) {
p->skin = clampf(p->skin, 0, array_count(p->skins) - 1);
spine_skin(p, p->skin);
}
if( p->texture.id ) ui_texture(0, p->texture);
}
int main() {
window_create(0.75, 0);
camera_t cam = camera();
cam.position = vec3(0,0,1);
camera_enable(&cam);
spine_t s = spine("goblins.json", "goblins.atlas", 0);
while( window_swap() ) {
camera_get_active()->position.x = window_width()/2;
camera_get_active()->position.y = window_height()/2;
static bool do_skin = 1, do_skel = 1;
spine_animate(&s, &s.time, &s.maxtime, !window_has_pause() * window_delta());
spine_render(&s, vec3(window_width()/2, window_height()/2, 0), do_skin );
sprite_flush();
glClear(GL_DEPTH_BUFFER_BIT);
spine_render(&s, vec3(window_width()/2, window_height()/2, 0), (do_skel*2));
if( ui_panel("Spine", 0) ) {
if(ui_button("Load")) {
s = spine("goblins.json", "goblins.atlas", 0);
}
spine_ui(&s);
if(ui_bool("Draw Skin", &do_skin));
if(ui_bool("Draw Skeleton", &do_skel));
ui_panel_end();
}
}
}

229
demos/99-sprite.c 100644
View File

@ -0,0 +1,229 @@
// sprite routines
// - rlyeh,
//
// credits: original lovely demo by rxi (MIT License).
// see https://github.com/rxi/autobatch/tree/master/demo/cats
#include "fwk.h"
texture_t kids, catImage, shadowImage, inputs;
int NUM_SPRITES = 100, NUM_SPRITES_CHANGED = 1;
typedef struct Cat {
int cat, flip;
double x, y;
double vx, vy;
double animSpeed;
double moveTimer;
double elapsed;
} Cat;
void demo_cats() {
static array(Cat) cats = 0;
// init
if( NUM_SPRITES_CHANGED ) {
NUM_SPRITES_CHANGED = 0;
array_resize(cats, NUM_SPRITES); int i = 0;
for each_array_ptr(cats, Cat, c) {
randset(i++);
c->x = randf() * window_width();
c->y = randf() * window_height();
c->vx = c->vy = 0;
c->cat = randi(0, 4);
c->flip = randf() < 0.5;
c->animSpeed = 0.8 + randf() * 0.3;
c->moveTimer = 0;
c->elapsed = 0;
}
}
// move
const float dt = 1/120.f;
const int appw = window_width(), apph = window_height();
enum { yscale = 1 };
for( int i = 0; i < NUM_SPRITES; ++i ) {
Cat *c = &cats[i];
// Add velocity to position //and wrap to screen
c->x += yscale * c->vx * dt; // % ;
c->y += yscale * c->vy * dt; // % (int)window_height();
if( c->x < 0 ) c->x += appw; else if( c->x > appw ) c->x -= appw;
if( c->y < 0 ) c->y += apph; else if( c->y > apph ) c->y -= apph;
// Faster animation if walking
int boost = c->vx == 0 && c->vy == 0 ? 1 : 3;
// Update elapsed time
c->elapsed += dt * boost;
// Update move timer -- if we hit zero then change or zero velocity
c->moveTimer -= dt * boost;
if (c->moveTimer < 0) {
if (randf() < .2) {
c->vx = (randf() * 2 - 1) * 30 * 2;
c->vy = (randf() * 2 - 1) * 15 * 2;
c->flip = c->vx < 0;
} else {
c->vx = c->vy = 0;
}
c->moveTimer = 1 + randf() * 5;
}
}
// render
uint32_t white = rgba(255,255,255,255);
uint32_t alpha = rgba(255,255,255,255*0.6);
for( int i = 0; i < NUM_SPRITES; ++i ) {
Cat *c = &cats[i];
// Get current animation frame (8x4 tilesheet)
double e = c->elapsed * c->animSpeed;
double frame_num = c->cat * 8 + floor( ((int)(e * 8)) % 4 );
frame_num = c->vx != 0 || c->vy != 0 ? frame_num + 4 : frame_num;
// Get x scale based on flip flag
int xscale = yscale * (c->flip ? -1 : 1);
// Draw
float angle = 0; //fmod(window_time()*360/5, 360);
float scale[2] = { 2*xscale, 2*yscale };
float position[3] = { c->x,c->y,c->y }, no_offset[2] = {0,0}, spritesheet[3] = { frame_num,8,4 };
sprite_sheet(catImage,
spritesheet, // frame_number in a 8x4 spritesheet
position, angle, // position(x,y,depth: sort by Y), angle
no_offset, scale, // offset(x,y), scale(x,y)
0,white,0 // is_additive, tint color, resolution independant
);
float position_neg_sort[3] = { c->x,c->y,-c->y }, offset[2] = {-1,5}, no_spritesheet[3] = {0,0,0};
sprite_sheet(shadowImage,
no_spritesheet, // no frame_number (0x0 spritesheet)
position_neg_sort, angle, // position(x,y,depth: sort by Y), angle
offset, scale, // offset(x,y), scale(x,y)
0,alpha,0 // is_additive, tint color, resolution independant
);
}
}
void demo_kids() {
static int angle; //++angle;
static int *x, *y, *v;
// init
if( NUM_SPRITES_CHANGED ) {
NUM_SPRITES_CHANGED = 0;
y = (int*)REALLOC(y, 0 );
x = (int*)REALLOC(x, NUM_SPRITES * sizeof(int) );
y = (int*)REALLOC(y, NUM_SPRITES * sizeof(int) );
v = (int*)REALLOC(v, NUM_SPRITES * sizeof(int) );
for( int i = 0; i < NUM_SPRITES; ++i ) {
randset(i);
x[i] = randi(0, window_width());
y[i] = randi(0, window_height());
v[i] = randi(1, 3);
}
}
// config
const int appw = window_width(), apph = window_height();
// move & render
for( int i = 0; i < NUM_SPRITES; ++i ) {
y[i] = (y[i] + v[i]) % (apph + 128);
int col = ((x[i] / 10) % 4); // 4x4 tilesheet
int row = ((y[i] / 10) % 4);
int num_frame = col * 4 + row;
float position[3] = {x[i],y[i],y[i]}, offset[2]={0,0}, scale[2]={1,1}, spritesheet[3]={num_frame,4,4};
sprite_sheet(kids,
spritesheet, // num_frame in a 4x4 spritesheet
position, angle, // position(x,y,depth: sort by Y), angle
offset, scale, // offset(x,y), scale(x,y)
0, ~0u, 0 // is_additive, tint color, resolution independant
);
}
}
int main(int argc, char **argv) {
window_create(75.f, 0);
window_title("FWK - Sprite");
// options
int do_cats = 1;
NUM_SPRITES = optioni("--num_sprites,-N", NUM_SPRITES);
if(do_cats) NUM_SPRITES/=2; // cat-sprite+cat-shadow == 2 sprites
// load sprites and sheets
kids = texture( "spriteSheetExample.png", TEXTURE_LINEAR );
catImage = texture( "cat.png", TEXTURE_LINEAR ); //
shadowImage = texture( "cat-shadow.png", TEXTURE_LINEAR );
inputs = texture( "prompts_tilemap_34x24_16x16x1.png", TEXTURE_LINEAR );
// load all post fx files
for(const char **list = file_list("./","fx**.fs"); *list; list++) {
fx_load(*list);
}
// init camera (x,y) (z = zoom)
camera_t cam = camera();
cam.position = vec3(window_width()/2,window_height()/2,1);
camera_enable(&cam);
while(window_swap()) {
if( input(KEY_F5)) window_reload();
if( input(KEY_F11)) window_fullscreen( window_has_fullscreen() ^ 1);
if( input(KEY_ESC) ) break;
viewport_color3(vec3(0.4,0.4,0.4));
// camera panning (x,y) & zooming (z)
if( !ui_hover() && !ui_active() ) {
if( input(MOUSE_L) ) cam.position.x -= input_diff(MOUSE_X);
if( input(MOUSE_L) ) cam.position.y -= input_diff(MOUSE_Y);
cam.position.z += input_diff(MOUSE_W) * 0.1; // cam.p.z += 0.001f; for tests
}
// apply post-fxs from here
fx_begin();
profile("Sprite batching") {
if(do_cats) demo_cats(); else demo_kids();
}
// flush retained renderer, so we ensure the fbos are up to date before fx_end()
profile("Sprite flushing") {
sprite_flush();
}
// post-fxs end here
fx_end();
// draw pixel-art hud, 16x16 ui element, scaled and positioned in resolution-independant way
{
vec3 old_pos = camera_get_active()->position;
sprite_flush();
camera_get_active()->position = vec3(window_width()/2,window_height()/2,1);
float zindex = window_height(); // large number, on top
float spritesheet[3] = {17,34,24}, offset[2] = {0, - 2*absf(sin(window_time()*5))}; // sprite cell and animation
float scale[2] = {3, 3}, tile_w = 16 * scale[0], tile_h = 16 * scale[1]; // scaling
float position[3] = {window_width() - tile_w, window_height() - tile_h, zindex }; // position in screen-coordinates
sprite_sheet(inputs, spritesheet, position, 0/*rotation*/, offset, scale, false/*is_additive*/, WHITE/*color*/, false/*resolution_independant*/);
sprite_flush();
camera_get_active()->position = old_pos;
}
if( ui_panel("Sprite", 0) ) {
const char *labels[] = {"Kids","Cats"};
if( ui_list("Sprite type", labels, countof(labels), &do_cats) ) NUM_SPRITES_CHANGED = 1;
if( ui_int("Number of Sprites", &NUM_SPRITES) ) NUM_SPRITES_CHANGED = 1;
if( ui_clampf("Zoom", &cam.position.z, 0.1, 10));
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
}
ui_panel_end();
}
}
}

125
demos/99-syncdemo.c 100644
View File

@ -0,0 +1,125 @@
#define FWK_IMPLEMENTATION
#include "engine/joint/fwk.h"
#include "fwk_netsync.h"
enum { MAX_NPCS = 5 };
struct player_t {
uint64_t seen_until;
float x,y,z,angle;
uint32_t color;
};
struct npc_t {
float x,y,z;
uint32_t color;
};
struct world_t {
struct player_t player[MAX_CLIENTS];
struct npc_t npc[MAX_NPCS];
} world = {0};
char *show_notification(char *msg) {
printf("notif %s\n", msg);
ui_notify("server", msg);
return NULL;
}
void bind_netbuffers(int64_t self_id) {
uint32_t colors[] = { ORANGE,GREEN,RED,CYAN,PURPLE,YELLOW,GRAY,PINK,AQUA };
for (int64_t i=0; i<MAX_NPCS; ++i) {
// as an example, let only server to set initial pos
if (self_id==0) {
world.npc[i].x = i*3.f + 4.f;
}
world.npc[i].color = colors[i%(sizeof colors / sizeof colors[0])];
network_buffer(&world.npc[i], sizeof(struct npc_t), NETWORK_RECV, 0);
}
for (int64_t i=0; i<MAX_CLIENTS; ++i) {
world.player[i].color = colors[i%(sizeof colors / sizeof colors[0])];
network_buffer(&world.player[i], sizeof(struct player_t), i!=self_id ? NETWORK_RECV : NETWORK_SEND, i /* each client owns exactly 1 buffer */);
};
// register server->client rpc
if (self_id > 0) {
network_rpc("char* show_notification(char*)", show_notification);
}
}
int main() {
// ifdef(win32, FreeConsole()); // tty_detach()
// network setup
network_create("127.0.0.1", 0, flag("--client") ? NETWORK_CONNECT : 0);
int64_t self_id = network_get(NETWORK_RANK);
bind_netbuffers(self_id);
// game setup
camera_t cam = camera();
window_create( 0.35f, WINDOW_MSAA8|WINDOW_SQUARE );
struct player_t *self = &world.player[self_id];
// game loop
while( window_swap() && !input(KEY_ESC) ) {
// network sync
char **event = network_sync(0); // timeout_ms:0
while(*event) printf( "network event: %s\n", *event++ );
self_id = network_get(NETWORK_RANK);
if (network_get(NETWORK_LIVE) == 0) {
network_create("127.0.0.1", 0, flag("--client") ? NETWORK_CONNECT|NETWORK_NOFAIL : 0);
self_id = network_get(NETWORK_RANK);
if (self_id != -1) {
bind_netbuffers(self_id);
}
continue;
}
/* quick hack to broadcast notif from host */
if (self_id == 0 && input_down(KEY_F3)) {
printf("rpc %s\n", "show_notification \"hi, sailor!\"");
network_rpc_send(rand()%4, "show_notification \"hi, sailor!\"");
}
// camera tracking
cam.position = vec3(self->x,100,self->z);
camera_lookat(&cam, vec3(self->x,0,self->z));
// input - move player
float iy = input(KEY_UP) - input(KEY_DOWN);
float ix = input(KEY_RIGHT) - input(KEY_LEFT);
if( iy || ix ) {
self->x += iy*window_delta()*15;
self->z += ix*window_delta()*15;
}
self->seen_until = date_epoch() + 4;
// npc - update npc movement on server-side
if (self_id == 0) {
for (int i = 0; i < MAX_NPCS; ++i) {
struct npc_t *n = &world.npc[i];
n->z = sinf(window_time())*4.f;
}
}
// background - draw grid
ddraw_ground(0);
// foreground - draw all players
for( int id = 0; id < MAX_CLIENTS; ++id ) {
struct player_t *p = &world.player[id];
if (p->seen_until < date_epoch()) continue; /* skip inactive players */
ddraw_color( p->color );
ddraw_capsule(vec3(p->x,0,p->z), vec3(p->x,2,p->z), 1);
ddraw_text(vec3(p->x,4,p->z), 0.01, va("player #%d", id));
}
for( int id = 0; id < MAX_NPCS; ++id ) {
struct npc_t *p = &world.npc[id];
ddraw_color( p->color );
ddraw_capsule(vec3(p->x,0,p->z), vec3(p->x,2,p->z), 1);
ddraw_text(vec3(p->x,4,p->z), 0.01, va("npc #%d", id));
}
// stats
window_title(va("player #%lld", self_id));
}
}

44
demos/99-video.c 100644
View File

@ -0,0 +1,44 @@
// video player
// - rlyeh, public domain
#include "fwk.h"
int main() {
// 75% window, msaa x2
window_create( 75, WINDOW_MSAA2 );
// load video
int is_rgb = flag("--rgb") ? 1 : 0;
video_t *v = video( "bjork-all-is-full-of-love.mp4", is_rgb ? VIDEO_RGB : VIDEO_YCBCR );
while( window_swap() ) {
// decode video frame and get associated textures (audio is automatically sent to audiomixer)
texture_t *textures;
profile( "Video decoder" ) {
textures = video_decode( v );
}
// present decoded textures as a fullscreen composed quad
profile( "Video quad" ) {
if(is_rgb) fullscreen_quad_rgb( textures[0], 1.3f );
else fullscreen_quad_ycbcr( textures, 1.3f );
}
// input controls
if( input(KEY_ESC) ) break;
// ui video
if( ui_panel("Video", 0) ) {
if( ui_button("Rewind") ) video_seek(v, video_position(v) - 3);
if( ui_button("Pause") ) video_pause(v, video_is_paused(v) ^ 1);
if( ui_button("Forward") ) video_seek(v, video_position(v) + 3);
ui_panel_end();
}
// audio
if( ui_panel("Audio", 0)) {
static float master = 1;
if( ui_slider2("Master", &master, va("%.2f", master))) audio_volume_master(master);
ui_panel_end();
}
}
}

11
demos/MAKE.bat 100644
View File

@ -0,0 +1,11 @@
#!/bin/bash 2>nul || goto :windows
sh ../MAKE.bat demos
exit
:windows
pushd ..
call MAKE.bat demos %*
popd

Binary file not shown.

View File

@ -0,0 +1,38 @@
SIERRA ON-LINE, INC.
3-D Animated Adventure Game Soundtrack Series
===============================================
LEISURE SUIT LARRY III: PASSIONATE PATTI-
IN PURSUIT OF THE PULSATING PECTORALS
"TAWNI AT THE BEACH"
Mike Dana
===============================================
Copyright (c)1989 Sierra On-Line, Inc.
===============================================
GENERAL MIDI VERSION
System Requirements:
- MIDI Playback Software capable of reading Type 1 Standard
MIDI File format
- General MIDI sound device (Wave Table or better recommended)
This Standard MIDI File was recorded directly from Sierra's "Leisure Suit
Larry 3" adventure game. It has been converted from the MT-32 version for
playback on General MIDI sound cards. A Wave Table or better sound card is
highly recommended for optimal playback.
Recorded/converted for General MIDI by Tom Lewandowski.
Address questions or comments to:
QUEST STUDIOS
Tom Lewandowski
tom@queststudios.com
www.QuestStudios.com

Binary file not shown.

View File

@ -0,0 +1,154 @@
/**
* (c) 2021 FMS_Cat
* Original shader: https://www.shadertoy.com/view/MdffD7
* I dumbass don't know what it says despite it's my own shader
*/
/*
* Copyright 2021 FMS_Cat
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define half2 vec2
#define half3 vec3
#define half4 vec4
#define saturate(c) clamp(c, 0.0, 1.0)
//uniform float iTime;
uniform float iScale;
//uniform vec2 iResolution;
uniform vec4 iBackground;
const float PI = 3.14159265f;
const int SAMPLES = 6;
const float COLOR_NOISE_AMP = 0.1f;
const vec3 YIQ_OFFSET = vec3( -0.1f, -0.1f, 0.0f );
const vec3 YIQ_AMP = vec3( 1.2f, 1.1f, 1.5f );
bool validuv( vec2 uv )
{
return 0.0f < uv.x && uv.x < 1.0f && 0.0f < uv.y && uv.y < 1.0f;
}
vec2 yflip( vec2 uv )
{
return vec2( uv.x, 1.0 - uv.y );
}
float fs( float s )
{
return fract( sin( s * 114.514f ) * 1919.810f );
}
float fs2( vec2 s )
{
return fs( s.x + fs( s.y ) );
}
mat2x2 rotate2D( float t )
{
return mat2x2( cos( t ), sin( t ), -sin( t ), cos( t ) );
}
vec3 rgb2yiq( vec3 rgb )
{
return mat3x3( 0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f ) * rgb;
}
vec3 yiq2rgb( vec3 yiq )
{
return mat3x3( 1.000f, 1.000f, 1.000f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f ) * yiq;
}
float v2Random( vec2 v )
{
vec2 vf = fract( v * 256.0f );
vec2 vi = floor( v * 256.0f ) / 256.0f;
vec2 d = vec2( 0.0f, 1.0f / 256.0f );
return mix(
mix( fs2( vi + d.xx ), fs2( vi + d.yx ), vf.x ),
mix( fs2( vi + d.xy ), fs2( vi + d.yy ), vf.x ),
vf.y
);
}
half3 vhsTex2D( vec2 uv ) {
if ( validuv( uv ) ) {
half3 yiq = half3( 0.0f, 0.0f, 0.0f );
for ( int i = 0; i < SAMPLES; i ++ ) {
vec2 uvt = uv - vec2( float( i ), 0.0f ) / iResolution;
if ( validuv( uvt ) ) {
half4 tex = texture(iChannel0, uvt );
yiq += (
rgb2yiq( mix( iBackground.rgb, tex.rgb, tex.a ) ) *
vec2( float( i ), float( SAMPLES - 1 - i ) ).yxx / float( SAMPLES - 1 )
) / float( SAMPLES ) * 2.0f;
}
}
return yiq2rgb( yiq );
}
return half3( 0.1f, 0.1f, 0.1f );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 uv = fragCoord.xy / iResolution.xy;
vec2 uvt = yflip( uv );
vec3 col = vec3( 0.0f, 0.0f, 0.0f );
// tape wave
uvt.x += ( v2Random( vec2( uvt.y / 10.0f, iTime / 10.0f ) / 1.0f ) - 0.5f ) / iResolution.x * 1.0f;
uvt.x += ( v2Random( vec2( uvt.y, iTime * 10.0f ) ) - 0.5f ) / iResolution.x * 1.0f;
// tape crease
float tcPhase = smoothstep( 0.9f, 0.96f, sin( uvt.y * 8.0f - ( iTime + 0.14f * v2Random( iTime * vec2( 0.67f, 0.59f ) ) ) * PI * 1.2f ) );
float tcNoise = smoothstep( 0.3f, 1.0f, v2Random( vec2( uvt.y * 4.77f, iTime ) ) );
float tc = tcPhase * tcNoise;
uvt.x = uvt.x - tc / iResolution.x * 8.0f;
// switching noise
float snPhase = smoothstep( 6.0f / iResolution.y, 0.0f, uvt.y );
uvt.y += snPhase * 0.3f;
uvt.x += snPhase * ( ( v2Random( vec2( uv.y * 100.0f, iTime * 10.0f ) ) - 0.5f ) / iResolution.x * 24.0f );
// fetch
half4 tex = texture(iChannel0, uv);
half3 color = vhsTex2D( yflip( uvt ) );
color = pow( color, vec3(0.4545f) );
// crease noise
float cn = tcNoise * ( 0.3f + 0.7f * tcPhase );
if ( 0.29f < cn ) {
vec2 uvtt = ( uvt + vec2( 1.0f, 0.0f ) * v2Random( vec2( uvt.y, iTime ) ) ) * vec2( 0.1f, 1.0f );
float n0 = v2Random( uvtt );
float n1 = v2Random( uvtt + vec2( 1.0f, 0.0f ) / iResolution.x );
if ( n1 < n0 ) {
color = mix( color, vec3( 2.0f, 2.0f, 2.0f ), pow( n0, 10.0f ) );
}
}
// ac beat
color *= 1.0f + 0.1f * smoothstep( 0.4f, 0.6f, v2Random( vec2( 0.0f, 0.1f * ( uv.y + iTime * 0.2f ) ) / 10.0f ) );
// color noise
half2 noiseuv = uvt + vec2( fs( iTime ), fs( iTime / 0.7f ) );
half3 noise = half3(
v2Random( noiseuv ),
v2Random( noiseuv + 0.7f ),
v2Random( noiseuv + 1.4f )
);
color = saturate( color );
// yiq
color = rgb2yiq( color );
color += COLOR_NOISE_AMP * ( noise - 0.5f );
color = YIQ_OFFSET + YIQ_AMP * color;
color = yiq2rgb( color );
color = pow( color, vec3(2.2f) );
fragColor = half4( color, tex.a );
}

Binary file not shown.

View File

@ -0,0 +1,12 @@
------------------------------------------------------
LowPoly Models by @Quaternius
Consider supporting me on Patreon, even $1 helps me a lot!
https://www.patreon.com/quaternius
-------------------------------------------------------
License:
CC0 1.0 Universal (CC0 1.0)
Public Domain Dedication
https://creativecommons.org/publicdomain/zero/1.0/

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@ -0,0 +1,12 @@
------------------------------------------------------
LowPoly Models by @Quaternius
Consider supporting me on Patreon, even $1 helps me a lot!
https://www.patreon.com/quaternius
-------------------------------------------------------
License:
CC0 1.0 Universal (CC0 1.0)
Public Domain Dedication
https://creativecommons.org/publicdomain/zero/1.0/

View File

@ -0,0 +1,9 @@
# Halloween Little Witch
## Source
[https://sketchfab.com/models/ccc023590bfb4789af9322864e42d1ab](https://sketchfab.com/models/ccc023590bfb4789af9322864e42d1ab)
## License
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

716
demos/fwk_netsync.h 100644
View File

@ -0,0 +1,716 @@
// high-level, socket-less networking api. inspired by Quake, MPI and RenderBuckets theories.
// - rlyeh, public domain
//
// Usage:
// 1. configure networked memory buffers with flags (world, player1, player2, etc). network_buffer();
// 2. then during game loop:
// - modify your buffers as much as needed.
// - sync buffers at least once per frame. network_sync();
// - render your world
// 3. optionally, monitor network status & variables. network_get();
//
// @todo: maybe network_send(msg) + msg *network_recv(); instead of event queue of network_sync() ?
//enum { NETWORK_HANDSHAKE, NETWORK_ENCRYPT, NETWORK_VERSIONED, NETWORK_CHECKSUM }; // negotiation
//enum { NETWORK_TCP, NETWORK_UDP, NETWORK_KCP, NETWORK_ENET, NETWORK_WEBSOCKET }; // transport, where
enum { NETWORK_BIND = 2, NETWORK_CONNECT = 4, NETWORK_NOFAIL = 8 };
enum { MAX_CLIENTS = 32 };
API void network_create(const char *ip, const char *port, unsigned flags); // both ip and port can be null
//enum { NETWORK_LOSSY, NETWORK_COMPRESS }; // post-processes
//enum { NETWORK_UNRELIABLE, NETWORK_UNORDERED, NETWORK_PRIORITY }; // how
//enum { NETWORK_PREDICT, NETWORK_RECONCILE, NETWORK_INTERPOLATE, NETWORK_COMPENSATE }; // time authority, when
//enum { NETWORK_LAGS, NETWORK_DROPS, NETWORK_THROTTLES, NETWORK_DUPES }; // quality sim, how much
//enum { NETWORK_CONST = 1, NETWORK_64,NETWORK_32,NETWORK_16,NETWORK_8, NETWORK_FLT, NETWORK_STR, NETWORK_BLOB }; // type, what
enum { NETWORK_SEND = 2, NETWORK_RECV = 4 };
API void* network_buffer(void *ptr, unsigned sz, unsigned flags, int64_t rank); // configures a shared/networked buffer
API char** network_sync(unsigned timeout_ms); // syncs all buffers & returns null-terminated list of network events
enum { NETWORK_RANK = 0 }; // [0..N] where 0 is server
enum { NETWORK_PING = 1 }; // NETWORK_BANDWIDTH, NETWORK_QUALITY };
enum { NETWORK_PORT = 2, NETWORK_IP, NETWORK_LIVE };
//enum { NETWORK_USERID, NETWORK_SALT, NETWORK_COUNT/*N users*/ /*...*/,
API int64_t network_get(uint64_t key);
API int64_t network_put(uint64_t key, int64_t value);
API void network_rpc(const char *signature, void *function);
API void network_rpc_send_to(int64_t rank, unsigned id, const char *cmdline);
API void network_rpc_send(unsigned id, const char *cmdline);
// -----------------------------------------------------------------------------
// low-level api (sockets based)
API bool server_bind(int max_clients, int port);
API void server_poll();
API void server_broadcast_bin(const void *ptr, int len);
API void server_broadcast(const char *msg);
API void server_terminate();
API void server_send(int64_t handle, const char *msg);
API void server_send_bin(int64_t handle, const void *ptr, int len);
API void server_drop(int64_t handle);
API int64_t client_join(const char *ip, int port);
#define client_send(msg) server_broadcast(msg)
#define client_send_bin(ptr,len) server_broadcast_bin(ptr, len)
#define client_terminate() server_terminate()
#define ANYHOST_IPV4 "0.0.0.0"
#define ANYHOST_IPV6 "::0"
#define LOCALHOST_IPV4 "127.0.0.1"
#define LOCALHOST_IPV6 "::1"
// -----------------------------------------------------------------------------
// implementation
typedef void* (*rpc_function)();
typedef struct rpc_call {
char *method;
rpc_function function;
uint64_t function_hash;
} rpc_call;
#define RPC_SIGNATURE_i_iii UINT64_C(0x78409099752fa48a) // printf("%llx\n, HASH_STR("int(int,int,int)"));
#define RPC_SIGNATURE_i_ii UINT64_C(0x258290edf43985a5) // printf("%llx\n, HASH_STR("int(int,int)"));
#define RPC_SIGNATURE_s_s UINT64_C(0x97deedd17d9afb12) // printf("%llx\n, HASH_STR("char*(char*)"));
#define RPC_SIGNATURE_s_v UINT64_C(0x09c16a1242049b80) // printf("%llx\n, HASH_STR("char*(void)"));
static
rpc_call rpc_new_call(const char *signature, rpc_function function) {
if( signature && function ) {
array(char*)tokens = strsplit(signature, "(,)");
if( array_count(tokens) >= 1 ) {
char *method = strrchr(tokens[0], ' ')+1;
char *rettype = va("%.*s", (int)(method - tokens[0] - 1), tokens[0]);
int num_args = array_count(tokens) - 1;
char* hash_sig = va("%s(%s)", rettype, num_args ? (array_pop_front(tokens), strjoin(tokens, ",")) : "void");
uint64_t hash = hash_str(hash_sig);
method = va("%s%d", method, num_args );
#if RPC_DEBUG
printf("%p %p %s `%s` %s(", function, (void*)hash, rettype, hash_sig, method); for(int i = 0, end = array_count(tokens); i < end; ++i) printf("%s%s", tokens[i], i == (end-1)? "":", "); puts(");");
#endif
return (rpc_call) { strdup(method), function, hash }; // LEAK
}
}
return (rpc_call) {0};
}
static map(char*, rpc_call) rpc_calls = 0;
static
void rpc_insert(const char *signature, void *function ) {
rpc_call call = rpc_new_call(signature, function);
if( call.method ) {
if( !rpc_calls ) map_init(rpc_calls, less_str, hash_str);
if( map_find(rpc_calls, call.method)) {
map_erase(rpc_calls, call.method);
}
map_insert(rpc_calls, call.method, call);
}
}
static
char *rpc_full(unsigned id, const char* method, unsigned num_args, char *args[]) {
#if RPC_DEBUG
printf("id:%x method:%s args:", id, method );
for( int i = 0; i < num_args; ++i ) printf("%s,", args[i]); puts("");
#endif
method = va("%s%d", method, num_args);
rpc_call *found = map_find(rpc_calls, (char*)method);
if( found ) {
switch(found->function_hash) {
case RPC_SIGNATURE_i_iii: return va("%d %d", id, (int)(uintptr_t)found->function(atoi(args[0]), atoi(args[1]), atoi(args[2])) );
case RPC_SIGNATURE_i_ii: return va("%d %d", id, (int)(uintptr_t)found->function(atoi(args[0]), atoi(args[1])) );
case RPC_SIGNATURE_s_s: return va("%d %s", id, (char*)found->function(args[0]) );
case RPC_SIGNATURE_s_v: return va("%d %s", id, (char*)found->function() );
default: break;
}
}
return va("%d -1", id);
}
static
array(char*) rpc_parse_args( const char *cmdline, bool quote_whitespaces ) { // parse cmdline arguments. must array_free() after use
// - supports quotes: "abc" "abc def" "abc \"def\"" "abc \"def\"""ghi" etc.
// - #comments removed
array(char*) args = 0; // LEAK
for( int i = 0; cmdline[i]; ) {
char buf[256] = {0}, *ptr = buf;
while(cmdline[i] && isspace(cmdline[i])) ++i;
bool quoted = cmdline[i] == '\"';
if( quoted ) {
while(cmdline[++i]) {
char ch = cmdline[i];
/**/ if (ch == '\\' && cmdline[i + 1] == '\"') *ptr++ = '\"', ++i;
else if (ch == '\"' && cmdline[i + 1] == '\"') ++i;
else if (ch == '\"' && (!cmdline[i + 1] || isspace(cmdline[i + 1]))) {
++i; break;
}
else *ptr++ = ch;
}
} else {
while(cmdline[i] && !isspace(cmdline[i])) *ptr++ = cmdline[i++];
}
if (buf[0] && buf[0] != '#') { // exclude empty args + comments
if( quote_whitespaces && quoted )
array_push(args, va("\"%s\"",buf));
else
array_push(args, va("%s",buf));
}
}
return args;
}
static
char* rpc(unsigned id, const char* cmdline) {
array(char*) args = rpc_parse_args(cmdline, false);
int num_args = array_count(args);
char *ret = num_args ? rpc_full(id, args[0], num_args - 1, &args[1]) : rpc_full(id, "", 0, NULL);
array_free(args);
return ret;
}
static void enet_quit(void) {
do_once {
// enet_deinitialize();
}
}
static void enet_init() {
do_once {
if( enet_initialize() != 0 ) {
PANIC("cannot initialize enet");
}
atexit( enet_quit );
}
}
static ENetHost *Server;
static map(ENetPeer *, int64_t) clients;
static map(int64_t, ENetPeer *) peers;
static int64_t next_client_id = 1; // assumes ID 0 is server
enum { MSG_INIT, MSG_BUF, MSG_RPC, MSG_RPC_RESP };
bool server_bind(int max_clients, int port) {
map_init(clients, less_64, hash_64);
map_init(peers, less_64, hash_64);
assert(port == 0 || (port > 1024 && port < 65500));
ENetAddress address = {0};
address.host = ENET_HOST_ANY;
address.port = port;
Server = enet_host_create(&address, max_clients, 2 /*channels*/, 0 /*in bandwidth*/, 0 /*out bandwidth*/);
return Server != NULL;
}
static
void server_drop_client(int64_t handle) {
map_erase(clients, *(ENetPeer **)map_find(peers, handle));
map_erase(peers, *(int64_t *)handle);
}
static
void server_drop_client_peer(ENetPeer *peer) {
map_erase(peers, *(int64_t *)map_find(clients, peer));
map_erase(clients, peer);
}
void server_poll() {
ENetEvent event;
while( enet_host_service(Server, &event, 2 /*timeout,ms*/) > 0 ) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT:;
char ip[128]; enet_peer_get_ip(event.peer, ip, 128);
PRINTF( "A new client connected from ::%s:%u.\n", ip, event.peer->address.port );
/* Store any relevant client information here. */
event.peer->data = "Client information";
int64_t client_id = next_client_id++;
map_find_or_add(clients, event.peer, client_id);
map_find_or_add(peers, client_id, event.peer);
break;
case ENET_EVENT_TYPE_RECEIVE:
PRINTF( "A packet of length %zu containing %s was received from %s on channel %u.\n",
event.packet->dataLength,
event.packet->data,
(char *)event.peer->data,
event.channelID );
char *dbg = (char *)event.peer->data;
char *ptr = event.packet->data;
unsigned sz = event.packet->dataLength;
uint32_t mid = *(uint32_t*)ptr;
ptr += 4;
// @todo: propagate event to user
switch (mid) {
case MSG_INIT: {
uint64_t *cid = map_find(clients, event.peer);
if (cid) {
char init_msg[12];
*(uint32_t*)&init_msg[0] = MSG_INIT;
*(uint64_t*)&init_msg[4] = *cid;
ENetPacket *packet = enet_packet_create(init_msg, 12, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event.peer, 0, packet);
} else {
PRINTF("ignoring unk MSG_INIT client packet.\n");
}
} break;
case MSG_RPC:
case MSG_RPC_RESP:
// @todo: process and send a response back
break;
default:
PRINTF("recving unk %d sz %d from peer %s\n", mid, sz, dbg);
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy( event.packet );
break;
case ENET_EVENT_TYPE_DISCONNECT:
PRINTF( "%s disconnected.\n", (char *)event.peer->data );
/* Reset the peer's client information. */
event.peer->data = NULL;
server_drop_client_peer(event.peer);
break;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
PRINTF( "%s timeout.\n", (char *)event.peer->data );
event.peer->data = NULL;
server_drop_client_peer(event.peer);
break;
case ENET_EVENT_TYPE_NONE: break;
}
}
}
void client_poll() {
ENetEvent event;
while( enet_host_service(Server, &event, 2 /*timeout,ms*/) > 0 ) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT:;
break;
case ENET_EVENT_TYPE_RECEIVE:
PRINTF( "A packet of length %zu containing %s was received from %s on channel %u.\n",
event.packet->dataLength,
event.packet->data,
(char *)event.peer->data,
event.channelID );
char *dbg = (char *)event.peer->data;
char *ptr = event.packet->data;
unsigned sz = event.packet->dataLength;
uint32_t mid = *(uint32_t*)ptr;
ptr += 4;
// @todo: propagate event to user
switch (mid) {
case MSG_INIT:
/* handled during client_join */
break;
case MSG_RPC:
case MSG_RPC_RESP:
// @todo: process and send a response back
break;
default:
PRINTF("recving unk %d sz %d from peer %s\n", mid, sz, dbg);
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy( event.packet );
break;
case ENET_EVENT_TYPE_DISCONNECT:
PRINTF( "%s disconnected.\n", (char *)event.peer->data );
/* Reset the peer's client information. */
event.peer->data = NULL;
server_drop_client_peer(event.peer);
break;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
PRINTF( "%s timeout.\n", (char *)event.peer->data );
event.peer->data = NULL;
server_drop_client_peer(event.peer);
break;
case ENET_EVENT_TYPE_NONE: break;
}
}
}
void server_broadcast_bin(const void *msg, int len) {
ENetPacket *packet = enet_packet_create(msg, len, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(Server, 0, packet);
//enet_host_flush(Server); // flush if needed
}
void server_broadcast(const char *msg) {
server_broadcast_bin(msg, strlen(msg)+1);
}
void server_terminate() {
enet_host_destroy(Server);
Server = 0;
}
volatile int client_join_connected = 0;
static int client_join_threaded(void *userdata) {
ENetHost *host = (ENetHost *)userdata;
ENetPacket *packet = enet_packet_create("", 1, ENET_PACKET_FLAG_RELIABLE);
enet_host_broadcast(Server, 0, packet);
/* Wait up to 5 seconds for the connection attempt to succeed. */
ENetEvent event;
client_join_connected = 0;
client_join_connected = enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT;
return 0;
}
int64_t client_join(const char *ip, int port) {
assert(port > 1024 && port < 65500);
ENetAddress address = {0};
// address.host = ENET_HOST_ANY;
enet_address_set_host(&address, !strcmp(ip, "localhost") ? "127.0.0.1" : ip);
address.port = port;
ENetHost *host = enet_host_create(NULL, 1 /*outgoing connections*/, 2 /*channels*/, 0 /*in bandwidth*/, 0 /*out bandwidth*/);
if(!host) return -1;
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
if(!peer) return -1;
Server = host;
#if 1
#if 0
// sync wait (not working in localhost, unless threaded)
thread_ptr_t th = thread_init(client_join_threaded, host, "client_join_threaded()", 0 );
thread_join( th );
thread_destroy( th );
#else
ENetEvent event;
bool client_join_connected = enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT;
#endif
if(!client_join_connected) { enet_peer_reset(peer); return -1; }
#endif
// ask for server slot
char init_msg[4]; *(uint32_t*)init_msg = MSG_INIT;
server_broadcast_bin(init_msg, sizeof(init_msg));
// wait for the response
bool msg_received = enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_RECEIVE;
if (!msg_received) { enet_peer_reset(peer); return -1; }
char *ptr = (char *)event.packet->data;
int64_t cid = -1;
// decapsulate incoming packet.
uint32_t mid = *(uint32_t*)(ptr + 0);
ptr += 4;
switch (mid) {
case MSG_INIT:
cid = *(int64_t*)ptr;
break;
default:
enet_peer_reset(peer);
return -1;
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy( event.packet );
return cid;
}
void server_drop(int64_t handle) {
enet_peer_disconnect_now(*(ENetPeer **)map_find(peers, handle), 0);
server_drop_client(handle);
}
void server_send_bin(int64_t handle, const void *ptr, int len) {
ENetPacket *packet = enet_packet_create(ptr, len, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(*(ENetPeer **)map_find(peers, handle), 0, packet);
}
void server_send(int64_t handle, const char *msg) {
server_send_bin(handle, msg, strlen(msg)+1);
}
// ---
typedef struct netbuffer_t {
int64_t owner;
void *ptr;
unsigned sz;
unsigned flags;
} netbuffer_t;
static array(char*) events; // @todo: make event 128 bytes max?
static array(int64_t) values; // @todo: map<key,values> instead?
static map( int64_t, array(netbuffer_t) ) buffers; // map<client,array<netbuffer>>
void network_create(const char *ip, const char *port_, unsigned flags) {
if (buffers) map_clear(buffers);
do_once {
array_resize(values, 128);
map_init(buffers, less_64, hash_64);
enet_init();
}
ip = ip ? ip : "0.0.0.0";
int port = atoi(port_ ? port_ : "1234");
// network_put(NETWORK_IP, 0x7F000001); // 127.0.0.1
network_put(NETWORK_PORT, port);
network_put(NETWORK_LIVE, -1);
if( !(flags&NETWORK_CONNECT) || flags&NETWORK_BIND ) {
// server, else client
PRINTF("Trying to bind server, else we connect as a client...\n");
network_put(NETWORK_RANK, 0);
if( server_bind(MAX_CLIENTS, port) ) {
network_put(NETWORK_LIVE, 1);
PRINTF("Server bound\n");
} else {
network_put(NETWORK_RANK, -1); /* unassigned until we connect successfully */
int64_t socket = client_join(ip, port);
if( socket >= 0 ) {
PRINTF("Client connected, id %lld\n", socket);
network_put(NETWORK_LIVE, 1);
network_put(NETWORK_RANK, socket);
} else {
PRINTF("!Client conn failed\n");
network_put(NETWORK_LIVE, 0);
if (!(flags&NETWORK_NOFAIL))
PANIC("cannot neither connect to %s:%d, nor create a server", ip, port);
}
}
} else {
// client only
PRINTF("Connecting to server...\n");
network_put(NETWORK_RANK, -1); /* unassigned until we connect successfully */
int64_t socket = client_join(ip, port);
if( socket > 0 ) {
PRINTF("Client connected, id %lld\n", socket);
network_put(NETWORK_LIVE, 1);
network_put(NETWORK_RANK, socket);
} else {
PRINTF("!Client conn failed\n");
network_put(NETWORK_LIVE, 0);
if (!(flags&NETWORK_NOFAIL))
PANIC("cannot connect to server %s:%d", ip, port);
}
}
PRINTF("Network rank:%lld ip:%s port:%lld\n", network_get(NETWORK_RANK), ip, network_get(NETWORK_PORT));
}
int64_t network_put(uint64_t key, int64_t value) {
int64_t *found = key < array_count(values) ? &values[key] : NULL;
if(found) *found = value;
return value;
}
int64_t network_get(uint64_t key) {
int64_t *found = key < array_count(values) ? &values[key] : NULL;
return found ? *found : 0;
}
void* network_buffer(void *ptr, unsigned sz, unsigned flags, int64_t rank) {
assert(flags);
array(netbuffer_t) *found = map_find_or_add(buffers, rank, NULL);
netbuffer_t nb;
nb.owner = rank;
nb.ptr = ptr;
nb.sz = sz;
nb.flags = flags;
array_push(*found, nb);
return ptr;
}
char** network_sync(unsigned timeout_ms) {
array_clear(events);
int64_t whoami = network_get(NETWORK_RANK);
bool is_server = whoami == 0;
bool is_client = !is_server;
if(timeout_ms < 2) timeout_ms = 2;
// sleep_ms(timeout_ms); // @fixme. server only?
// Split buffers into clients @todo
// clients need to do this before network polling; servers should do this after polling.
map_foreach(buffers, int64_t, rank, array(netbuffer_t), list) {
for(int i = 0, end = array_count(list); i < end; ++i) {
netbuffer_t *nb = &list[i];
if (!is_server && !(nb->flags & NETWORK_SEND))
continue;
static array(char) encapsulate;
array_resize(encapsulate, nb->sz + 28);
uint32_t *mid = (uint32_t*)&encapsulate[0]; *mid = MSG_BUF;
uint64_t *st = (uint64_t*)&encapsulate[4]; *st = nb->flags;
uint32_t *idx = (uint32_t*)&encapsulate[12]; *idx = i;
uint32_t *len = (uint32_t*)&encapsulate[16]; *len = nb->sz;
uint64_t *who = (uint64_t*)&encapsulate[20]; *who = nb->owner;
// PRINTF("sending %llx %u %lld %u\n", *st, *idx, *who, *len);
memcpy(&encapsulate[28], nb->ptr, nb->sz);
server_broadcast_bin(&encapsulate[0], nb->sz + 28);
}
}
// network poll
for( ENetEvent event; Server && enet_host_service(Server, &event, timeout_ms) > 0; ) {
char *msg = 0;
char ip[128]; enet_peer_get_ip(event.peer, ip, 128);
switch (event.type) {
default: // case ENET_EVENT_TYPE_NONE:
break;
case ENET_EVENT_TYPE_CONNECT:;
msg = va( "A new client connected from ::%s:%u", ip, event.peer->address.port );
/* Store any relevant client information here. */
event.peer->data = "Client information";
/* ensure we have free slot for client */
if (map_count(clients) >= MAX_CLIENTS) {
msg = va("%s\n", "Server is at maximum capacity, disconnecting the peer...");
enet_peer_disconnect_now(event.peer, 1);
break;
}
int64_t client_id = next_client_id++;
map_find_or_add(clients, event.peer, client_id);
map_find_or_add(peers, client_id, event.peer);
break;
case ENET_EVENT_TYPE_RECEIVE:;
/*
msg = va( "A packet of length %u containing %s was received from %s on channel %u",
(unsigned)event.packet->dataLength,
event.packet->data,
(char *)event.peer->data,
event.channelID );
*/
char *dbg = (char *)event.peer->data;
char *ptr = (char *)event.packet->data;
unsigned sz = (unsigned)event.packet->dataLength;
unsigned id = (unsigned)event.channelID;
// debug
// puts(dbg);
// hexdump(ptr, sz);
// decapsulate incoming packet.
uint32_t mid = *(uint32_t*)(ptr + 0);
ptr += 4;
switch (mid) {
case MSG_INIT:
if (is_server) {
uint64_t *cid = map_find(clients, event.peer);
if (cid) {
char init_msg[12];
*(uint32_t*)&init_msg[0] = MSG_INIT;
*(int64_t*)&init_msg[4] = *cid;
ENetPacket *packet = enet_packet_create(init_msg, 12, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event.peer, 0, packet);
PRINTF("Client req id %lld for peer ::%s:%u\n", *cid, ip, event.peer->address.port);
} else {
PRINTF("!Ignoring unk MSG_INIT client packet.\n");
}
}
break;
case MSG_BUF: {
uint64_t *flags = (uint64_t*)(ptr + 0);
uint32_t *idx = (uint32_t*)(ptr + 8);
uint32_t *len = (uint32_t*)(ptr + 12);
uint64_t *who = (uint64_t*)(ptr + 16);
// PRINTF("recving %d %llx %u %u %lld\n", mid, *flags, *idx, *len, *who);
ptr += 24;
// validate if peer owns the buffer
uint8_t client_valid = 0;
if (is_server) {
int64_t *cid = map_find(clients, event.peer);
client_valid = cid ? *cid == *who : 0;
}
// apply incoming packet.
if( is_client ? *who != whoami : client_valid ) { // clients merge always foreign packets. servers merge foreign packets.
array(netbuffer_t) *list = map_find(buffers, *who);
assert( list );
assert( *idx < array_count(*list) );
netbuffer_t *nb = &(*list)[*idx];
assert( *len == nb->sz );
memcpy(nb->ptr, ptr, *len);
}
} break;
case MSG_RPC: {
unsigned id = *(uint32_t*)ptr; ptr += 4;
char *cmdline = ptr;
char *resp = rpc(id, cmdline);
char *resp_msg = va("%*.s%s", 4, "", resp);
*(uint32_t*)&resp_msg[0] = MSG_RPC_RESP;
ENetPacket *packet = enet_packet_create(resp_msg, 4 + strlen(resp), ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(event.peer, 0, packet);
} break;
case MSG_RPC_RESP: {
// @todo: react on response?
msg = ptr;
} break;
default:
// PRINTF("!Receiving unk %d sz %d from peer ::%s:%u\n", mid, sz, ip, event.peer->address.port);
break;
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy( event.packet );
break;
case ENET_EVENT_TYPE_DISCONNECT:
msg = va( "%s disconnected", (char *)event.peer->data );
/* Reset the peer's client information. */
event.peer->data = NULL;
if (is_server) server_drop_client_peer(event.peer);
else {network_put(NETWORK_RANK, -1); network_put(NETWORK_LIVE, 0);}
break;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
msg = va( "%s timeout", (char *)event.peer->data );
event.peer->data = NULL;
if (is_server) server_drop_client_peer(event.peer);
else {network_put(NETWORK_RANK, -1); network_put(NETWORK_LIVE, 0);}
break;
}
if(msg) array_push(events, va("%d %s", event.type, msg));
// if(msg) server_broadcast(msg);
}
array_push(events, NULL);
return events;
}
void network_rpc(const char *signature, void *function) {
rpc_insert(signature, function);
}
void network_rpc_send_to(int64_t rank, unsigned id, const char *cmdline) {
assert(network_get(NETWORK_RANK) == 0); /* must be a host */
char *msg = va("%*.s%s", 8, "", cmdline);
*(uint32_t*)&msg[0] = MSG_RPC;
*(uint32_t*)&msg[4] = id;
server_send_bin(rank, msg, 8 + strlen(cmdline));
}
void network_rpc_send(unsigned id, const char *cmdline) {
char *msg = va("%*.s%s", 8, "", cmdline);
*(uint32_t*)&msg[0] = MSG_RPC;
*(uint32_t*)&msg[4] = id;
server_broadcast_bin(msg, 8 + strlen(cmdline));
}

View File

@ -0,0 +1,58 @@
#!/bin/bash 2>nul || goto :windows
if [ "$1" = "" ]; then
sh MAKE.bat demo_ui.c
exit
fi
## clone emscripten sdk
git clone https://github.com/emscripten-core/emsdk ../../../../../emsdk
pushd ../../../../../emsdk
./emsdk install 3.0.0 ## latest
./emsdk activate 3.0.0 ## latest
source ./emsdk_env.sh
popd
## cook art
if [ "$(uname)" = "Darwin" ]; then
chmod +x ../../tools/cook.osx
../../tools/cook.osx --cook-jobs=1 --cook-ini=../../tools/cook.ini
cp .art[00].zip index.data
else
chmod +x ../../tools/cook.linux
../../tools/cook.linux --cook-jobs=1 --cook-ini=../../tools/cook.ini
cp .art[00].zip index.data
fi
## host webserver, compile and launch
python -m http.server --bind 127.0.0.1 8000 1> /dev/null 2> /dev/null &
emcc $@ -g ../../engine/fwk.c -I../../engine -o index.html -s FULL_ES3 -s USE_PTHREADS -s USE_GLFW=3 -s SINGLE_FILE=1 -s PRECISE_F32=1 -s TOTAL_MEMORY=256mb -s ENVIRONMENT=worker,web --shell-file template.html -Wfatal-errors --preload-file .art[00].zip -s ALLOW_MEMORY_GROWTH=1 -lidbfs.js && xdg-open http://localhost:8000/index.html
exit
:windows
if "%1"=="" MAKE.bat demo_collide.c
rem clone emscripten sdk
if not exist "emsdk" (
git clone https://github.com/emscripten-core/emsdk emsdk
pushd emsdk
call emsdk install 3.0.0 && rem latest
call emsdk activate 3.0.0 && rem latest
popd
)
if "%EMSDK%"=="" call emsdk\emsdk_env.bat --system
rem cook art
..\..\tools\cook.exe --cook-jobs=1 --cook-ini=..\..\tools\cook.ini
copy .art[00].zip index.data
rem host webserver:8000 if not open , compile and launch
netstat /ano | find /i "listening" | find ":8000" >nul 2>nul && (
rem start python -m http.server --bind 127.0.0.1 8000
) || (
start python -m http.server --bind 127.0.0.1 8000
)
emcc %* -g ..\..\engine\fwk.c -I..\..\engine -o index.html -pthread -s FULL_ES3 -s USE_PTHREADS -s USE_GLFW=3 -s SINGLE_FILE=1 -s PRECISE_F32=1 -s TOTAL_MEMORY=256mb -s ENVIRONMENT=worker,web --shell-file template.html -Wfatal-errors --preload-file .art[00].zip -s ALLOW_MEMORY_GROWTH=1 -lidbfs.js && start "" http://localhost:8000/index.html

View File

@ -0,0 +1,21 @@
## Known HTML5 issues:
- [x] Game loop is event based (fixed: see `window_loop()`)
- [x] No automated emsdk installation (fixed: see `demos/html5/MAKE.bat`)
- [x] Art must be cooked beforehand (fixed: see `demos/html5/MAKE.bat`)
- [x] No VFS loading (fixed)
- [x] No UI rendering (fixed)
- [x] No cooker (fixed: win,osx,linux)
- [x] No input (fixed)
- [x] No gamepads (fixed)
- [x] No multi-touch (fixed)
- [x] No threads (fixed)
- [ ] No audio
- [ ] No file writing (untested)
- [ ] No glTexture1D()
- [ ] No network
- [ ] No fbos
- [ ] No callstacks
- [ ] No pbos
- [ ] Shaders require (automated?) GL->GLES translation (No postfxs, models, skyboxes, pbrs, ...) (@todo: embed Spir-v/glslcross tools?)
- [ ] Shaders: `vec2 iResolution = vec2(iWidth, iHeight); // ERROR: '=' : global variable initializers must be constant expressions`
- [ ] Shaders: `uniform float var = 1.0f; // ERROR: 'uniform' : cannot initialize this type of qualifier`

View File

@ -0,0 +1,616 @@
// original code by @vurtun (public domain)
// - rlyeh, public domain.
//
// @todo: fix leaks: poly_free()
#include "fwk.h"
// -- demo
int paused;
camera_t cam;
void game_loop(void *userdata) {
// key handler
if (input_down(KEY_F11) ) window_fullscreen( window_has_fullscreen()^1 );
if (input_down(KEY_ESC) ) window_loop_exit(); // @todo: break -> window_close()
// animation
static float dx = 0, dy = 0;
if (input_down(KEY_SPACE)) paused ^= 1;
float delta = (0.25f / 60.f) * !paused;
dx = dx + delta * 2.0f;
dy = dy + delta * 0.8f;
// fps camera
{
vec3 move = {0};
vec2 view = {0};
// show/hide cursor
bool dragging = input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
if( ui_active() || ui_hover() || gizmo_active() || input_touch_active() ) dragging = false;
window_cursor( !dragging );
// keyboard/mouse
if( dragging ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec3 wasdec = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-input(KEY_C),input(KEY_W)-input(KEY_S)), cam.speed);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * dragging);
move = add3(move, wasdec);
view = add2(view, mouse);
// gamepad
vec2 filtered_lpad = input_filter_deadzone(input2(GAMEPAD_LPAD), 0.15f /*15% deadzone*/);
vec2 filtered_rpad = input_filter_deadzone(input2(GAMEPAD_RPAD), 0.15f /*15% deadzone*/);
vec3 gamepad_move = scale3(vec3(filtered_lpad.x, input(GAMEPAD_LT) - input(GAMEPAD_RT), filtered_lpad.y), 1.0f);
vec2 gamepad_view = scale2(filtered_rpad, 1.0f);
move = add3(move, gamepad_move);
view = add2(view, gamepad_view);
// multi-touch
vec2 touch_move = input_touch_delta_from_origin(0, 0.0125f /*sensitivityFwd*/); // button #0 (left border)
vec2 touch_view = input_touch_delta(1, 0.125f /*sensitivityRot*/); // button #1 (right border)
move = add3(move, vec3(touch_move.x, 0, -touch_move.y));
view = add2(view, vec2(touch_view.x, -touch_view.y));
// apply inputs
camera_move(&cam, move.x,move.y,move.z);
camera_fps(&cam, view.x,view.y);
}
// projview matrix
mat44 projview; multiply44x2(projview, cam.proj, cam.view);
// rendering
viewport_color3(vec3(0.15,0.15,0.15));
#if 0
viewport_clear(true, true);
viewport_clip(vec2(0,0), vec2(window_width(), window_height()));
#endif
// debug draw collisions
{
// 3D
glEnable(GL_DEPTH_TEST);
// grid
ddraw_grid(0);
{
// Triangle-Ray Intersection*/
vec3 ro, rd;
int suc;
triangle tri = { vec3(-9,1,28), vec3(-10,0,28), vec3(-11,1,28) };
// ray
ro = vec3(-10,-1,20);
rd = vec3(-10+0.4f*sinf(dx), 2.0f*cosf(dy), 29.81023f);
rd = sub3(rd, ro);
rd = norm3(rd);
ray r = ray(ro, rd);
hit *hit = ray_hit_triangle(r, tri);
if (hit) {
// point of intersection
ddraw_color(RED);
ddraw_box(hit->p, vec3(0.10f, 0.10f, 0.10f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
}
// line
ddraw_color(RED);
rd = scale3(rd,10);
rd = add3(ro,rd);
ddraw_line(ro, rd);
// triangle
if (hit) ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_triangle(tri.p0,tri.p1,tri.p2);
}
{
// Plane-Ray Intersection*/
vec3 ro, rd;
mat33 rot;
// ray
static float d = 0;
d += delta * 2.0f;
ro = vec3(0,-1,20);
rd = vec3(0.1f, 0.5f, 9.81023f);
rd = sub3(rd, ro);
rd = norm3(rd);
// rotation
rotation33(rot, deg(d), 0,1,0);
rd = mulv33(rot, rd);
// intersection
ray r = ray(ro, rd);
plane pl = plane(vec3(0,0,28), vec3(0,0,1));
hit *hit = ray_hit_plane(r, pl);
if (hit) {
// point of intersection
ddraw_color(RED);
ddraw_box(hit->p, vec3(0.10f, 0.10f, 0.10f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
ddraw_color(RED);
}
// line
ddraw_color(RED);
rd = scale3(rd,9);
rd = add3(ro,rd);
ddraw_line(ro, rd);
// plane
if (hit) ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_plane(vec3(0,0,28), vec3(0,0,1), 3.0f);
}
{
// Sphere-Ray Intersection*/
vec3 ro, rd;
sphere s;
// ray
ro = vec3(0,-1,0);
rd = vec3(0.4f*sinf(dx), 2.0f*cosf(dy), 9.81023f);
rd = sub3(rd, ro);
rd = norm3(rd);
ray r = ray(ro, rd);
s = sphere(vec3(0,0,8), 1);
hit *hit = ray_hit_sphere(r, s);
if(hit) {
// points of intersection
vec3 in = add3(ro,scale3(rd,hit->t0));
ddraw_color(GREEN);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
in = add3(ro,scale3(rd,hit->t1));
ddraw_color(YELLOW);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
ddraw_color(RED);
}
// line
ddraw_color(RED);
rd = scale3(rd,10);
rd = add3(ro,rd);
ddraw_line(ro, rd);
// sphere
if (hit) ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_sphere(vec3(0,0,8), 1);
}
{ // ray-aabb
aabb bounds = aabb(vec3(10-0.5f,-0.5f,7.5f), vec3(10.5f,0.5f,8.5f));
vec3 ro = vec3(10,-1,0);
vec3 rd = vec3(10+0.4f*sinf(dx), 2.0f*cosf(dy), 9.81023f);
rd = norm3(sub3(rd, ro));
ray r = ray(ro, rd);
hit *hit = ray_hit_aabb(r, bounds);
if(hit) {
// points of intersection
vec3 in;
in = scale3(rd,hit->t0);
in = add3(ro,in);
ddraw_color(RED);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
in = scale3(rd,hit->t1);
in = add3(ro,in);
ddraw_color(RED);
ddraw_box(in, vec3(0.05f, 0.05f, 0.05f));
// intersection normal
ddraw_color(BLUE);
vec3 v = add3(hit->p, hit->n);
ddraw_arrow(hit->p, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(10,0,8), vec3(1,1,1));
// line
ddraw_color(RED);
rd = scale3(rd,10);
rd = add3(ro,rd);
ddraw_line(ro, rd);
}
{
// Sphere-Sphere intersection*/
sphere a = sphere(vec3(-10,0,8), 1);
sphere b = sphere(vec3(-10+0.6f*sinf(dx), 3.0f*cosf(dy),8), 1);
hit *m = sphere_hit_sphere(a, b);
if (m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_sphere(a.c, 1);
ddraw_sphere(b.c, 1);
}
{
// AABB-AABB intersection*/
const float x = 10+0.6f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = 20.0f;
aabb a = aabb(vec3(10-0.5f,-0.5f,20-0.5f), vec3(10+0.5f,0.5f,20.5f));
aabb b = aabb(vec3(x-0.5f,y-0.5f,z-0.5f), vec3(x+0.5f,y+0.5f,z+0.5f));
hit *m = aabb_hit_aabb(a, b);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(10,0,20), vec3(1,1,1));
ddraw_box(vec3(x,y,z), vec3(1,1,1));
}
{
// Capsule-Capsule intersection*/
const float x = 20+0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = 28.5f;
capsule a = capsule(vec3(20.0f,-1.0f,28.0f), vec3(20.0f,1.0f,28.0f), 0.2f);
capsule b = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
hit *m = capsule_hit_capsule(a, b);
if( m ) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
ddraw_capsule(vec3(20.0f,-1.0f,28.0f), vec3(20.0f,1.0f,28.0f), 0.2f);
}
{
// AABB-Sphere intersection*/
aabb a = aabb(vec3(20-0.5f,-0.5f,7.5f), vec3(20.5f,0.5f,8.5f));
sphere s = sphere(vec3(20+0.6f*sinf(dx), 3.0f*cosf(dy),8), 1);
hit *m = aabb_hit_sphere(a, s);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(20,0,8), vec3(1,1,1));
ddraw_sphere(s.c, 1);
}
{
// Sphere-AABB intersection*/
const float x = 10+0.6f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -8.0f;
sphere s = sphere(vec3(10,0,-8), 1);
aabb a = aabb(vec3(x-0.5f,y-0.5f,z-0.5f), vec3(x+0.5f,y+0.5f,z+0.5f));
hit *m = sphere_hit_aabb(s, a);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(x,y,z), vec3(1,1,1));
ddraw_sphere(s.c, 1);
}
{
// Capsule-Sphere intersection*/
capsule c = capsule(vec3(-20.5f,-1.0f,7.5f), vec3(-20+0.5f,1.0f,8.5f), 0.2f);
sphere b = sphere(vec3(-20+0.6f*sinf(dx), 3.0f*cosf(dy),8), 1);
hit *m = capsule_hit_sphere(c, b);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_sphere(b.c, 1);
ddraw_capsule(vec3(-20.5f,-1.0f,7.5f), vec3(-20+0.5f,1.0f,8.5f), 0.2f);
}
{
// Sphere-Capsule intersection*/
const float x = 20+0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -8;
sphere s = sphere(vec3(20,0,-8), 1);
capsule c = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
hit *m = sphere_hit_capsule(s, c);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
ddraw_sphere(s.c, 1);
}
{
// Capsule-AABB intersection*/
const float x = -20+0.6f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = 28.0f;
capsule c = capsule(vec3(-20.5f,-1.0f,27.5f), vec3(-20+0.5f,1.0f,28.5f), 0.2f);
aabb b = aabb(vec3(x-0.5f,y-0.5f,z-0.5f), vec3(x+0.5f,y+0.5f,z+0.5f));
hit *m = capsule_hit_aabb(c, b);
if(m) {
vec3 v;
ddraw_color(BLUE);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
v = add3(m->contact_point, m->normal);
ddraw_arrow(m->contact_point, v);
ddraw_color(RED);
} else ddraw_color(WHITE);
ddraw_box(vec3(x,y,z), vec3(1,1,1));
ddraw_capsule(vec3(-20.5f,-1.0f,27.5f), vec3(-20+0.5f,1.0f,28.5f), 0.2f);
}
{
// AABB-Capsule intersection*/
const float x = 0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -8;
aabb a = aabb(vec3(-0.5f,-0.5f,-8.5f), vec3(0.5f,0.5f,-7.5f));
capsule c = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
hit *m = aabb_hit_capsule(a, c);
if(m) {
ddraw_color(RED);
ddraw_box(m->contact_point, vec3(0.05f, 0.05f, 0.05f));
ddraw_arrow(m->contact_point, add3(m->contact_point, m->normal));
} else ddraw_color(WHITE);
ddraw_capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z-1.0f), 0.2f);
ddraw_box(vec3(0,0,-8.0f), vec3(1,1,1));
}
{
// poly(Pyramid)-Sphere (GJK) intersection*/
sphere s = sphere(vec3(-10+0.6f*sinf(dx), 3.0f*cosf(dy),-8), 1);
poly pyr = pyramid(vec3(-10.5f,-0.5f,-7.5f), vec3(-10.5f,1.0f,-7.5f), 1.0f);
gjk_result gjk;
if (poly_hit_sphere(&gjk, pyr, s))
ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_sphere(s.c, 1);
ddraw_pyramid(vec3(-10.5f,-0.5f,-7.5f), 0.5f/*vec3(-10.5f,1.0f,-7.5f)*/, 1.0f);
poly_free(&pyr);
ddraw_box(gjk.p0, vec3(0.05f, 0.05f, 0.05f));
ddraw_box(gjk.p1, vec3(0.05f, 0.05f, 0.05f));
ddraw_line(gjk.p0, gjk.p1);
}
{
// poly(Diamond)-Sphere (GJK) intersection*/
sphere s = sphere(vec3(-20+0.6f*sinf(dx), 3.0f*cosf(dy),-8), 1);
poly dmd = diamond(vec3(-20.5f,-0.5f,-7.5f), vec3(-20.5f,1.0f,-7.5f), 0.5f);
gjk_result gjk;
if (poly_hit_sphere(&gjk, dmd, s))
ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_sphere(s.c, 1);
ddraw_diamond(vec3(-20.5f,-0.5f,-7.5f), vec3(-20.5f,1.0f,-7.5f), 0.5f);
poly_free(&dmd);
ddraw_box(gjk.p0, vec3(0.05f, 0.05f, 0.05f));
ddraw_box(gjk.p1, vec3(0.05f, 0.05f, 0.05f));
ddraw_line(gjk.p0, gjk.p1);
}
{
// poly(Pyramid)-Capsule (GJK) intersection*/
const float x = 0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -15;
capsule c = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z), 0.2f);
poly pyr = pyramid(vec3(-0.5f,-0.5f,-15.5f), vec3(-0.5f,1.0f,-15.5f), 1.0f);
gjk_result gjk;
if (poly_hit_capsule(&gjk, pyr, c))
ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_capsule(c.a, c.b, c.r);
ddraw_pyramid(vec3(-0.5f,-0.5f,-15.5f), 0.5f/*vec3(-0.5f,1.0f,-15.5f)*/, 1.0f);
poly_free(&pyr);
ddraw_box(gjk.p0, vec3(0.05f, 0.05f, 0.05f));
ddraw_box(gjk.p1, vec3(0.05f, 0.05f, 0.05f));
ddraw_line(gjk.p0, gjk.p1);
}
{
// poly(Diamond)-Capsule (GJK) intersection*/
const float x = -10 + 0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -15;
capsule c = capsule(vec3(x,y-1.0f,z), vec3(x,y+1.0f,z), 0.2f);
poly dmd = diamond(vec3(-10.5f,-0.5f,-15.5f), vec3(-10.5f,1.0f,-15.5f), 0.5f);
gjk_result gjk;
if (poly_hit_capsule(&gjk, dmd, c))
ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_capsule(c.a, c.b, c.r);
ddraw_diamond(vec3(-10.5f,-0.5f,-15.5f), vec3(-10.5f,1.0f,-15.5f), 0.5f);
poly_free(&dmd);
ddraw_box(gjk.p0, vec3(0.05f, 0.05f, 0.05f));
ddraw_box(gjk.p1, vec3(0.05f, 0.05f, 0.05f));
ddraw_line(gjk.p0, gjk.p1);
}
{
// poly(Diamond)-poly(Pyramid) (GJK) intersection*/
const float x = -20 + 0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -15;
poly pyr = pyramid(vec3(x,y-0.5f,z), vec3(x,y+1,z), 0.8f);
poly dmd = diamond(vec3(-20.5f,-0.5f,-15.5f), vec3(-20.5f,1.0f,-15.5f), 0.5f);
gjk_result gjk;
if (poly_hit_poly(&gjk, dmd, pyr))
ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_pyramid(vec3(x,y-0.5f,z), 1/*vec3(x,y+1,z)*/, 1/*0.8f*/);
ddraw_diamond(vec3(-20.5f,-0.5f,-15.5f), vec3(-20.5f,1.0f,-15.5f), 0.5f);
poly_free(&dmd);
poly_free(&pyr);
ddraw_box(gjk.p0, vec3(0.05f, 0.05f, 0.05f));
ddraw_box(gjk.p1, vec3(0.05f, 0.05f, 0.05f));
ddraw_line(gjk.p0, gjk.p1);
}
{
// poly(Pyramid)-poly(Diamond) (GJK) intersection*/
const float x = 10 + 0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -15;
poly dmd = diamond(vec3(x,y-0.5f,z), vec3(x,y+1,z), 0.5f);
poly pyr = pyramid(vec3(10.5f,-0.5f,-15.5f), vec3(10.5f,1.0f,-15.5f), 1.0f);
gjk_result gjk;
if (poly_hit_poly(&gjk, dmd, pyr))
ddraw_color(RED);
else ddraw_color(WHITE);
ddraw_diamond(vec3(x,y-0.5f,z), vec3(x,y+1,z), 0.5f);
ddraw_pyramid(vec3(10.5f,-0.5f,-15.5f), 0.5f/*vec3(10.5f,1.0f,-15.5f)*/, 1.0f);
poly_free(&dmd);
poly_free(&pyr);
ddraw_box(gjk.p0, vec3(0.05f, 0.05f, 0.05f));
ddraw_box(gjk.p1, vec3(0.05f, 0.05f, 0.05f));
ddraw_line(gjk.p0, gjk.p1);
}
{
// poly(Diamond)-AABB (GJK) intersection*/
const float x = 20 + 0.4f*sinf(dx);
const float y = 3.0f*cosf(dy);
const float z = -15;
poly dmd = diamond(vec3(x,y-0.5f,z), vec3(x,y+1,z), 0.5f);
aabb a = aabb(vec3(19.5f,-0.5f,-14.5f), vec3(20.5f,0.5f,-15.5f));
gjk_result gjk;
if (poly_hit_aabb(&gjk, dmd, a))
ddraw_color(RED);
else ddraw_color(WHITE);
poly_free(&dmd);
ddraw_diamond(vec3(x,y-0.5f,z), vec3(x,y+1,z), 0.5f);
ddraw_box(vec3(20,0,-15), vec3(1,1,1));
ddraw_box(gjk.p0, vec3(0.05f, 0.05f, 0.05f));
ddraw_box(gjk.p1, vec3(0.05f, 0.05f, 0.05f));
ddraw_line(gjk.p0, gjk.p1);
}
}
//fx_begin();
//ddraw_flush();
//fx_end();
if( ui_panel("Audio", 0) ) {
if( ui_button("test audio") ) {
// audio (both clips & streams)
static audio_t voice; voice = audio_clip("coin.wav"); // "pew.sfxr"
static audio_t stream; stream = audio_stream("wrath_of_the_djinn.xm"); // "larry.mid"
audio_play(voice, 0);
audio_play(stream, 0);
}
ui_panel_end();
}
if( ui_panel("FX", 0) ) {
for( int i = 0; i < 64; ++i ) {
char *name = fx_name(i); if( !name ) break;
bool b = fx_enabled(i);
if( ui_bool(name, &b) ) fx_enable(i, fx_enabled(i) ^ 1);
}
ui_panel_end();
}
}
int main(void) {
// 75% sized, msaa x4 enabled
window_create(0.75f, WINDOW_MSAA4);
window_title( "FWK - SPACE pauses simulation" );
// for(const char **list = file_list("fx**.fs"); *list; list++) {
// //fx_load(*list);
// }
// camera that points to origin
cam = camera();
// main loop
window_loop(game_loop, NULL);
}

View File

@ -0,0 +1,57 @@
#include "fwk.h"
void render(void *arg) {
static int integer = 42;
static bool toggle = true;
static bool boolean = true;
static float floating = 3.14159;
static float float2[2] = {1,2};
static float float3[3] = {1,2,3};
static float float4[4] = {1,2,3,4};
static float rgb[3] = {0.84,0.67,0.17};
static float rgba[4] = {0.67,0.90,0.12,1};
static float slider = 0.5f;
static float slider2 = 0.5f;
static char string[64] = "hello world 123";
static int item = 0; const char *list[] = {"one","two","three"};
static bool show_dialog = false;
static uint8_t bitmask = 0x55;
if( ui_panel("UI", 0)) {
if( ui_label("my label")) {}
if( ui_label("my label with tooltip@built on " __DATE__ " " __TIME__)) {}
if( ui_separator() ) {}
if( ui_bool("my bool", &boolean) ) puts("bool changed");
if( ui_int("my int", &integer) ) puts("int changed");
if( ui_float("my float", &floating) ) puts("float changed");
if( ui_buffer("my string", string, 64) ) puts("string changed");
if( ui_separator() ) {}
if( ui_slider("my slider", &slider)) puts("slider changed");
if( ui_slider2("my slider 2", &slider2, va("%.2f", slider2))) puts("slider2 changed");
if( ui_separator() ) {}
if( ui_list("my list", list, 3, &item ) ) puts("list changed");
if( ui_separator() ) {}
if( ui_color3f("my color3", rgb) ) puts("color3 changed");
if( ui_color4f("my color4@this is a tooltip", rgba) ) puts("color4 changed");
if( ui_separator() ) {}
if( ui_float2("my float2", float2) ) puts("float2 changed");
if( ui_float3("my float3", float3) ) puts("float3 changed");
if( ui_float4("my float4", float4) ) puts("float4 changed");
if( ui_bits8("my bitmask", &bitmask) ) printf("bitmask changed %x\n", bitmask);
if( ui_separator() ) {}
if( ui_toggle("my toggle", &toggle) ) printf("toggle %s\n", toggle ? "on":"off");
if( ui_separator() ) {}
if( ui_image("my image", texture_checker().id, 0, 0) ) { puts("image clicked"); }
if( ui_button("my button") ) { puts("button clicked"); show_dialog = true; }
if( ui_dialog("my dialog", __FILE__ "\n" __DATE__ "\n" "Public Domain.", 2/*two buttons*/, &show_dialog) ) {}
ui_panel_end();
}
input_demo();
}
int main() {
window_create(0.75f, 0);
window_loop(render, NULL);
}

View File

@ -0,0 +1,2 @@
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
let coepCredentialless=!1;"undefined"==typeof window?(self.addEventListener("install",(()=>self.skipWaiting())),self.addEventListener("activate",(e=>e.waitUntil(self.clients.claim()))),self.addEventListener("message",(e=>{e.data&&("deregister"===e.data.type?self.registration.unregister().then((()=>self.clients.matchAll())).then((e=>{e.forEach((e=>e.navigate(e.url)))})):"coepCredentialless"===e.data.type&&(coepCredentialless=e.data.value))})),self.addEventListener("fetch",(function(e){const r=e.request;if("only-if-cached"===r.cache&&"same-origin"!==r.mode)return;const s=coepCredentialless&&"no-cors"===r.mode?new Request(r,{credentials:"omit"}):r;e.respondWith(fetch(s).then((e=>{if(0===e.status)return e;const r=new Headers(e.headers);return r.set("Cross-Origin-Embedder-Policy",coepCredentialless?"credentialless":"require-corp"),coepCredentialless||r.set("Cross-Origin-Resource-Policy","cross-origin"),r.set("Cross-Origin-Opener-Policy","same-origin"),new Response(e.body,{status:e.status,statusText:e.statusText,headers:r})})).catch((e=>console.error(e))))}))):(()=>{const e={shouldRegister:()=>!0,shouldDeregister:()=>!1,coepCredentialless:()=>(window.chrome!==undefined||window.netscape!==undefined),doReload:()=>window.location.reload(),quiet:!1,...window.coi},r=navigator;r.serviceWorker&&r.serviceWorker.controller&&(r.serviceWorker.controller.postMessage({type:"coepCredentialless",value:e.coepCredentialless()}),e.shouldDeregister()&&r.serviceWorker.controller.postMessage({type:"deregister"})),!1===window.crossOriginIsolated&&e.shouldRegister()&&(window.isSecureContext?r.serviceWorker&&r.serviceWorker.register(window.document.currentScript.src).then((s=>{!e.quiet&&console.log("COOP/COEP Service Worker registered",s.scope),s.addEventListener("updatefound",(()=>{!e.quiet&&console.log("Reloading page to make use of updated COOP/COEP Service Worker."),e.doReload()})),s.active&&!r.serviceWorker.controller&&(!e.quiet&&console.log("Reloading page to make use of COOP/COEP Service Worker."),e.doReload())}),(r=>{!e.quiet&&console.error("COOP/COEP Service Worker failed to register:",r)})):!e.quiet&&console.log("COOP/COEP Service Worker not registered, a secure context is required."))})();

View File

@ -0,0 +1,142 @@
<!DOCTYPE html>
<!-- This file license is CC0 (https://creativecommons.org/publicdomain/zero/1.0/). -->
<!-- Original code by @SpartanJ https://github.com/SpartanJ/eepp/blob/8552941da19380d7a629c4da80a976aec5d39e5c/bin/emscripten-fs.html -->
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
<!-- <title>Example</title> -->
<style>
body { margin: 0; background-color: black }
.emscripten {
position: absolute;
top: 0px;
left: 0px;
margin: 0px;
border: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: block;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
border-top: 3px solid #3daee9;
border-right: 3px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
.loader-cont {
display: flex;
width: 100vw;
justify-content: center;
height: 100vh;
align-items: center;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
<div id="cont" class="loader-cont">
<span class="loader"></span>
</div>
<script type='text/javascript'>
var loaderCont;
function getDemoScript(name) {
if (name)
return name;
return "index.js";
}
function getParameter(name) {
let url_string = window.location.href;
let url = new URL(url_string);
return url.searchParams.get(name);
}
function loadScript(url, callback) {
loaderCont = document.getElementById('cont');
let head = document.head;
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onreadystatechange = callback;
script.onload = callback;
head.appendChild(script);
}
var Module = {
preRun: [],
postRun: [],
print: (function() {
return function(text) {
text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
};
})(),
printErr: function(text) {
text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
canvas: (function() {
var canvas = document.getElementById('canvas');
return canvas;
})(),
setStatus: function(text) {
console.log("status: " + text);
if (text == "Running...")
loaderCont.style.display = 'none';
},
monitorRunDependencies: function(left) {
// no run dependencies to log
},
arguments: window.location.search.substr(1).split('&')
};
window.onerror = function() {
console.log("onerror: " + event);
};
(function() {
loadScript(getDemoScript(getParameter("run")));
})();
</script>
{{{ SCRIPT }}}
<button onclick="openFullscreen();" style="position:relative; z-index: 1000; float: right;">&#x26F6;</button>
<script>
function openFullscreen() {
var elem = document.getElementById("canvas");
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) { /* Safari */
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) { /* IE11 */
elem.msRequestFullscreen();
}
}
</script>
<script src="index.worker.coi.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,17 @@
#!/bin/bash 2>nul || goto :windows
sh ../../MAKE.bat dll
sh ../../MAKE.bat bind
./luajit.osx hello.lua
./luajit.linux hello.lua
python hello.py
exit
:windows
call ..\..\make.bat dll
call ..\..\make.bat bind
luajit hello.lua
python hello.py

26
engine/bind/fwk.py 100644
View File

@ -0,0 +1,26 @@
import os
import sys
import ctypes
import cffi
ffi = cffi.FFI()
with open('./fwk.lua') as f:
lines = [line for line in f if not line.startswith('#')]
lines = [line for line in lines if not 'va_list' in line]
lines = [line for line in lines if not 'inline ' in line]
lines = [line for line in lines if not line.startswith('typedef union ') ]
lines = [line for line in lines if not '//lcpp INF' in line ]
data = ''.join(lines)
data = data[data.find('[[')+2:data.find(']]')]
data = '''
typedef struct vec2i { float x,y; } vec2i;
typedef struct vec3i { float x,y,z; } vec3i;
typedef struct vec2 { float x,y; } vec2;
typedef struct vec3 { float x,y,z; } vec3;
typedef struct vec4 { float x,y,z,w; } vec4;
typedef struct quat { float x,y,z,w; } quat;
typedef union frustum frustum;
typedef union json_t json_t;
''' + data
ffi.cdef(data)
fwk = ffi.dlopen('./fwk.dll')

View File

@ -0,0 +1,56 @@
-- this will run on vanilla luajit.exe, provided that fwk.dll and this file are all present in same folder
local fwk=require('fwk')
-- specify location of cookbook
fwk.cook_config("../../tools/cook.ini");
-- create 75% sized + MSAAx2 anti-aliased window
fwk.window_create(75.0, fwk.WINDOW_MSAA2)
-- set window title
fwk.window_title("hello luajit")
-- config girl
local girl = fwk.model('kgirl/kgirls01.fbx', 0)
local girl_frame = 0
local girl_pivot = fwk.mat44()
fwk.rotationq44(girl_pivot, fwk.eulerq(fwk.vec3(0,0,0)))
fwk.scale44(girl_pivot, 2,2,2)
-- config & play music
local music = fwk.audio_stream('larry.mid') -- 'wrath_of_the_djinn.xm'
fwk.audio_play(music, 0);
-- config camera
local cam = fwk.camera()
-- main loop
while fwk.window_swap() == 1 do
-- fps camera
local grabbed = fwk.input(fwk.MOUSE_L) == 1 or fwk.input(fwk.MOUSE_R) == 1
fwk.window_cursor( fwk.ui_active() == 1 or fwk.ui_hover() == 1 and 1 or (not grabbed) )
if( fwk.window_has_cursor() ~= 1 ) then
local wasdec3 = fwk.vec3(fwk.input(fwk.KEY_D)-fwk.input(fwk.KEY_A),fwk.input(fwk.KEY_E)-(fwk.input(fwk.KEY_C)),fwk.input(fwk.KEY_W)-fwk.input(fwk.KEY_S))
local look2 = fwk.scale2(fwk.vec2(fwk.input_diff(fwk.MOUSE_X), -fwk.input_diff(fwk.MOUSE_Y)), 0.2)
local move3 = fwk.scale3(wasdec3, cam.speed)
fwk.camera_move(cam, wasdec3.x,wasdec3.y,wasdec3.z)
fwk.camera_fps(cam, look2.x,look2.y)
end
-- draw grid/axis
fwk.ddraw_grid(0)
fwk.ddraw_flush()
-- animate girl
local delta = fwk.window_delta() * 30 -- 30fps anim
girl_frame = fwk.model_animate(girl, girl_frame + delta)
-- draw girl
fwk.model_render(girl, cam.proj, cam.view, girl_pivot, 0)
-- showcase ui
if fwk.ui_panel("luajit", 0) == 1 then
fwk.ui_panel_end()
end
end

View File

@ -0,0 +1,10 @@
import os
from fwk import fwk
fwk.window_create(75.0, fwk.WINDOW_MSAA2)
fwk.window_title(b'hello Python')
cam = fwk.camera()
while fwk.window_swap():
fwk.ddraw_grid(0)
os._exit(0)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -235410,7 +235410,7 @@ int thread_detach( thread_ptr_t thread )
return CloseHandle( (HANDLE) thread ) != 0; return CloseHandle( (HANDLE) thread ) != 0;
#elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems
return pthread_detach( (pthread_t) thread ) == 0; return pthread_detach( (pthread_t) thread ) == 0;

View File

@ -18316,7 +18316,7 @@ int ui_label_(const char *label, int alignment) {
struct nk_style *style = &ui_ctx->style; struct nk_style *style = &ui_ctx->style;
bool bold = label[0] == '*'; label += bold; bool bold = label[0] == '*'; label += bold;
struct nk_font *font = bold ? nk_glfw.atlas.fonts->next->next /*3rd font*/ : NULL; // list struct nk_font *font = bold && nk_glfw.atlas.fonts->next ? nk_glfw.atlas.fonts->next->next /*3rd font*/ : NULL; // list
if( !has_icon ) { if( !has_icon ) {
// set bold style and color if needed // set bold style and color if needed

View File

@ -593,13 +593,13 @@ details > summary::-webkit-details-marker {
**F·W·K** **F·W·K**
|Version: | {{VERSION}} | |Version: | 2023.7 |
|:--------------|:------------| |:--------------|:------------|
|Branch: | main | |Branch: | main |
|Commit: | 1 | |Commit: | 2 |
<!--| Documentation last modified | { {LAST_MODIFIED} } |--> <!--| Documentation last modified | { {LAST_MODIFIED} } |-->
# [F·W·K {{VERSION}}](https://github.com/r-lyeh/FWK) # [F·W·K 2023.7 ](https://github.com/r-lyeh/FWK)
## a b o u t ## a b o u t
- https://github.com/r-lyeh/FWK is a 3D game framework in C, with Luajit bindings. - https://github.com/r-lyeh/FWK is a 3D game framework in C, with Luajit bindings.
@ -646,12 +646,8 @@ void assert_positive( int my_int ) { // lowercase snake_case everywhere
<details><summary>Goals</summary> <details><summary>Goals</summary>
- [x] ~~C++~~. C. - [x] ~~Full featured~~, ~~Fast~~, ~~Modern C++~~. Small, Naive, Simple C.
- [x] ~~Fast~~. Naive. - [x] ~~Rich build system~~, ~~Royaltie fee~~. Single file, Freely unlicensed.
- [x] ~~Modern~~. Simple.
- [x] ~~Full featured~~. Small.
- [x] ~~Rich build system~~. Single file.
- [x] ~~Royaltie fee~~. Free and unlicensed.
</details> </details>
@ -659,7 +655,7 @@ void assert_positive( int my_int ) { // lowercase snake_case everywhere
- [x] Pipeline: configurable and integrated [asset pipeline](tools/cook.ini). - [x] Pipeline: configurable and integrated [asset pipeline](tools/cook.ini).
- [x] Embedded: single-file header, all dependencies included. - [x] Embedded: [single-file header](engine/joint/fwk.h), all dependencies included.
- [x] Compiler: MSVC, MINGW64, TCC, GCC, clang, clang-cl and emscripten. - [x] Compiler: MSVC, MINGW64, TCC, GCC, clang, clang-cl and emscripten.
- [x] Linkage: Both static linkage and dynamic .dll/.so/.dylib support. - [x] Linkage: Both static linkage and dynamic .dll/.so/.dylib support.
- [x] Platform: Windows, Linux and OSX. Partial HTML5/Web support. - [x] Platform: Windows, Linux and OSX. Partial HTML5/Web support.
@ -694,7 +690,7 @@ void assert_positive( int my_int ) { // lowercase snake_case everywhere
- [x] Scene handling. - [x] Scene handling.
- [x] Profiler, stats and leaks finder. - [x] Profiler, stats and leaks finder.
- [x] [Editor (wip)](https://user-images.githubusercontent.com/35402248/174457347-f787a6a2-aac8-404c-a5da-f44310c3d432.mp4). - [x] [Editor (wip)](https://user-images.githubusercontent.com/35402248/174457347-f787a6a2-aac8-404c-a5da-f44310c3d432.mp4).
- [x] [Documentation (wip)](https://bit.ly/-f-w-k-). - [x] [Documentation (wip)](https://bit.ly/fwk2023).
</details> </details>
@ -702,21 +698,32 @@ void assert_positive( int my_int ) { // lowercase snake_case everywhere
- [ ] AI pass: actors, waypoints, pathfinding, behavior trees (h/fsm,goap), and navmesh generation. - [ ] AI pass: actors, waypoints, pathfinding, behavior trees (h/fsm,goap), and navmesh generation.
- [ ] Render pass: reverse-Z, automatic LODs, impostors, decals.
- [ ] Materials: (colors✱, textures✱, matcaps✱, videos✱, shadertoys✱). Shadertoys as post-fx✱. <!--materials as postfx, as they have an update() method -->
- [ ] Lighting: Hard/soft shadow mapping (VSM,CCSM). Baked lightmaps. Refl probes. Integrated PBR.
- [ ] Network/VM pass: Entity/component/systems and worlds. <!-- W/ECS, gameobj, serialization:load/save/merge, diff/patch ;; dead reckoning, interpolation, extrapolation, bandwidth budgets --> - [ ] Network/VM pass: Entity/component/systems and worlds. <!-- W/ECS, gameobj, serialization:load/save/merge, diff/patch ;; dead reckoning, interpolation, extrapolation, bandwidth budgets -->
- [ ] Core pass: struct serialization.
- [ ] Message pipeline and replication. <!-- manual/replication channels, node sharding/clustering. --> - [ ] Message pipeline and replication. <!-- manual/replication channels, node sharding/clustering. -->
- [ ] Digital signals, message buffering and event polling. - [ ] Digital signals, message buffering and event polling.
- [ ] World streaming and level loading. - [ ] World streaming and level loading.
- [ ] Scenegraphs and spatial partioning. BVH, PVS, occluders, frustum culling. - [ ] Scenegraphs and spatial partioning. BVH, PVS, occluders, frustum culling.
- [ ] Server/client architecture. Hybrid P2P. - [ ] Server/client architecture. Hybrid P2P.
- [ ] NAT traversal. Socketless API, message API and pub/sub wrappers (enet/websocket). - [ ] NAT traversal. Socketless API, message API and pub/sub wrappers (enet/websocket).
- [ ] Editor pass = netbased + offline rendering + virtual input.
- [ ] Basic: Gizmos✱, scene tree, property editor✱, load/save✱, undo/redo✱, copy/paste, on/off (vis,tick,ddraw,log), vcs.
- [ ] Scenenode: node singleton display, node console, node labels, node outlines✱.<!-- node == gameobj ? -->
- [ ] Debug: toggles on/off (billboards✱, materials, un/lit, cast shadows, wireframe, skybox✱/mie✱, fog/atmosphere, collide✱, physics).
- [ ] Level: volumes, triggers, platforms, level streaming.
- [ ] Sub-editor: timeline and data tracks, node graphs. <!-- worthy: will be reused into materials, animgraphs and blueprints -->
- [ ] Sub-editor: Procedural content, brushes, noise and CSG.
- [ ] Sub-editor: blendshapes, additive anims, head/foot/hand IKs.
- [ ] Script pass: DLL✱ (module->plugin/sys), Lua✱, Luajit✱, Teal✱ and TypeScript.
- [ ] Render pass: reverse-Z, automatic LODs, impostors, decals.
- [ ] Materials: (colors✱, textures✱, matcaps✱, videos✱, shadertoys✱). Shadertoys as post-fx✱. <!--materials as postfx, as they have an update() method -->
- [ ] Lighting: Hard/soft shadow mapping (VSM,CCSM). Baked lightmaps. Refl probes. Integrated PBR.
- [ ] Tools pass - [ ] Tools pass
- [ ] Extend shaders + bindings. Per-platform✱, per-type✱, per-asset options. GIF, PKM. - [ ] Extend shaders + bindings. Per-platform✱, per-type✱, per-asset options. GIF, PKM.
- [ ] Extend atlas (sprite/lightmaps). Fit packing (sprites). - [ ] Extend atlas (sprite/lightmaps). Fit packing (sprites).
- [ ] Extend bindings and messaging: parse C headers during cooking stage. <!-- msgs,docs,refl,meta,lua -- (*.c, *.h) as .proto/.pbc maybe, free reflection+automatic bindings --> - [ ] Extend bindings and messaging: parse C headers during cooking stage. <!-- msgs,docs,refl,meta,lua -- (*.c, *.h) as .proto/.pbc maybe, free reflection+automatic bindings -->
- [ ] API pass - [ ] API pass
- [ ] Extend math: quat2, bezier, catmull.
- [ ] Discuss API and freeze it. - [ ] Discuss API and freeze it.
- [ ] Document everything. - [ ] Document everything.
@ -833,17 +840,6 @@ int main() {
} }
``` ```
```C
#include "fwk.h" // Minimal HTML5 sample
void render(void *arg) {
if( !input(KEY_ESC) ) puts("hello FWK from HTML5!");
}
int main() {
window_create(75.0, 0); // 75% size, no extra flags
window_loop(render, NULL); // game loop
}
```
```lua ```lua
local fwk = require("fwk") -- Minimal Lua sample local fwk = require("fwk") -- Minimal Lua sample
fwk.window_create(75.0,0) -- 75% size, no extra flags fwk.window_create(75.0,0) -- 75% size, no extra flags
@ -857,6 +853,12 @@ end
<details><summary>Quickstart</summary> <details><summary>Quickstart</summary>
- Double-click `MAKE.bat` (Win) or `sh MAKE.bat` (Linux/OSX) to quick start.
- `MAKE.bat all` (Win) or `sh MAKE.bat all` (Linux/OSX) to build everything.
- `MAKE.bat proj` (Win) or `sh MAKE.bat proj` (Linux/OSX) to generate solutions/makefiles.
- `MAKE.bat help` (Win) or `sh MAKE.bat help` (Linux/OSX) for a bunch of options.
- `MAKE.bat hello.c` (Win) or `sh MAKE.bat hello.c` (Linux/OSX) to build a single executable.
- Alternatively,
```bat ```bat
echo win/vc && cl hello.c echo win/vc && cl hello.c
echo win/clang-cl && clang-cl hello.c echo win/clang-cl && clang-cl hello.c
@ -873,13 +875,9 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
<details><summary>Cook</summary> <details><summary>Cook</summary>
- Most asset types need to be cooked before being used in your application. Some other assets like `.png` do not. - Assets need to be cooked before being consumed in your application. The [tools/](tools/) folder contains all the related binaries to perform any asset processing plus the [cookbook](tools/cook.ini) to do so.
- Cooked assets will be written into .zipfiles close to your executable, and mounted before entering game loop. - Your game will cook all your assets as long as the [`tools/`](tools/) folder is next to your executable. Alternatively, cook them all just by invoking supplied [`tools/cook` standalone binary](tools/).
- Cooked .zipfiles and your executable are the only required assets when releasing your game. - In both cases, assets will be cooked and packed into .zipfiles next to your executable, then mounted before entering game loop. These .zipfiles plus your executable are the only required files when releasing your game.
- Cook manually your assets by invoking supplied [`tools/cook` standalone binary](tools/).
- Cook automatically your assets by just playing your game: a runtime cook is already embedded into your binary.
- In order to achieve this, ensure the [`tools/` folder](tools/) is close to your executable.
- This folder contains all the related binaries to perform any asset conversion plus the [cookbook](tools/cook.ini) to do so.
</details> </details>
@ -888,86 +886,100 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
- Any ico/png file named after the executable name will be automatically used as app icon. - Any ico/png file named after the executable name will be automatically used as app icon.
- Similar to the ico/png case above, the cooked .zipfiles can be named after the main executable as well. - Similar to the ico/png case above, the cooked .zipfiles can be named after the main executable as well.
- Dropped files into game window will be imported & saved into [`import/`](art/engine/import) folder. - Dropped files into game window will be imported & saved into [`import/`](engine/art/import/) folder.
- Update the gamepad controller database by upgrading the [`gamecontrollerdb.txt`](art/engine/input) file. - Update the gamepad controller database by upgrading the [`gamecontrollerdb.txt`](engine/art/input/) file.
- Depending on your IDE, you might need to browse to [`split/`](split/) sources when debugging FWK. - Depending on your IDE, you might need to browse to [`engine/split/`](engine/split/) sources when debugging FWK.
- Cook assets on demand, as opposed to cook all existing assets on depot, by using `--cook-on-demand` flag. - Cook assets on demand, as opposed to cook all existing assets on depot, by using `--cook-on-demand` flag.
- Linux/OSX users can optionally install wine and use the Windows tools instead (by using `--cook-wine` flag). - Linux/OSX users can optionally install wine and use the Windows tools instead (by using `--cook-wine` flag).
- Disable automatic cooking by using `--cook-jobs=0` flag (not recommended). - Disable automatic cooking by using `--cook-jobs=0` flag (not recommended).
- Generate a project solution by dropping `split/fwk.h, fwk.c and fwk` files into it. - Generate a project solution by dropping `engine/fwk.h, fwk.c and fwk` files into it.
- Auto-generated Luajit and Python bindings can be found in the [`engine/bind/`](engine/bind/) folder.
<!-- - On windows + vc, you can use `make bindings` or `make docs` to generate everything prior to a release -->
<!-- - Note: Windows: Assimp.dll may need [this package installed](https://www.microsoft.com/en-us/download/confirmation.aspx?id=30679).--> <!-- - Note: Windows: Assimp.dll may need [this package installed](https://www.microsoft.com/en-us/download/confirmation.aspx?id=30679).-->
</details> </details>
<details><summary>Credits (Artwork + demos)</summary> <details><summary>Credits</summary>
- [Nanofactory](https://sketchfab.com/3d-models/kgirls01-d2f946f58a8040ae993cda70c97b302c), for kgirls01 3D model (CC BY-NC-ND 4.0). **Artwork**
- [RottingPixels](https://opengameart.org/content/2d-castle-platformer-tileset-16x16), for castle-tileset (CC0). [Dean Evans, Raijin](https://youtu.be/RRvYkrrpMKo?t=147 "for the Map song (c)"),
- [wwwtyro](https://github.com/wwwtyro/glsl-atmosphere), for nicest rayleigh/mie scattering shader around (CC0). [FMS_Cat](https://gist.github.com/FMS-Cat/a1ccea3ce866c34706084e3526204f4f "for nicest VHS/VCR shader around (MIT)"),
[Goblin165cm](https://sketchfab.com/3d-models/halloween-little-witch-ccc023590bfb4789af9322864e42d1ab "for witch 3D model (CC BY 4.0)"),
[Nuulbee](https://sketchfab.com/3d-models/kgirls01-d2f946f58a8040ae993cda70c97b302c "for kgirls01 3D model (CC BY-NC-ND 4.0)"),
[Quaternius](https://www.patreon.com/quaternius "for the lovely 3D robots (CC0)"),
[Rotting Pixels](https://opengameart.org/content/2d-castle-platformer-tileset-16x16 "for castle-tileset (CC0)"),
[Tom Lewandowski](https://QuestStudios.com "for his MIDI recordings (c)"),
[Rye Terrell](https://github.com/wwwtyro/glsl-atmosphere "for nicest rayleigh/mie scattering shader around (CC0)"),
**Tools**
[Aaron Barany](https://github.com/akb825/Cuttlefish "for cuttlefish (APACHE2)"),
[Arseny Kapoulkine](https://github.com/zeux/pugixml/ "for pugixml (MIT)"),
[Assimp authors](https://github.com/assimp/assimp "for assimp (BSD3)"),
[Bernhard Schelling](https://github.com/schellingb/TinySoundFont "for tml.h (Zlib) and tsf.h (MIT)"),
[Christian Collins](http://www.schristiancollins.com "for GeneralUser GS soundfont (PD)"),
[FFMPEG authors](https://www.ffmpeg.org/ "for ffmpeg (LGPL21)"),
[Imagination](https://developer.imaginationtech.com/pvrtextool/ "for pvrtextoolcli (ITL)"),
[Krzysztof Gabis](https://github.com/kgabis/ape "for split.py/join.py (MIT)"),
[Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo "for iqm.cpp (PD)"),
[Martín Lucas Golini](https://github.com/SpartanJ/eepp/commit/8552941da19380d7a629c4da80a976aec5d39e5c "for emscripten-fs.html (CC0)"),
[Mattias Gustavsson](https://github.com/mattiasgustavsson/libs "for mid.h (PD)"),
[Michael Schmoock](http://github.com/willsteel/lcpp "for lcpp (MIT)"),
[Morgan McGuire](https://casual-effects.com/markdeep/ "for markdeep (BSD2)"),
[Olivier Lapicque, Konstanty Bialkowski](https://github.com/Konstanty/libmodplug "for libmodplug (PD)"),
[Polyglot Team](https://docs.google.com/spreadsheets/d/17f0dQawb-s_Fd7DHgmVvJoEGDMH_yoSd8EYigrb0zmM/edit "for polyglot gamedev (CC0)"),
[Tildearrow](https://github.com/tildearrow/furnace/ "for Furnace (GPL2)"),
[Tomas Pettersson](http://www.drpetter.se/ "for sfxr (PD)"),
[Tor Andersson](https://github.com/ccxvii/asstools "for assiqe.c (BSD)"),
**Runtime**
[Andreas Mantler](https://github.com/ands "for their math library (PD)"),
[Barerose](https://github.com/barerose "for swrap (CC0) and math library (CC0)"),
[Camilla Löwy](https://github.com/elmindreda "for glfw3 (Zlib)"),
[Dave Rand](https://tools.ietf.org/html/rfc1978 "for ppp (PD)"),
[David Herberth](https://github.com/dav1dde/ "for glad generated code (PD)"),
[David Reid](https://github.com/mackron "for miniaudio (PD)"),
[Dominic Szablewski](https://github.com/phoboslab/pl_mpeg "for pl_mpeg (MIT)"),
[Dominik Madarász](https://github.com/zaklaus "for json5 parser (PD)"),
[Eduard Suica](https://github.com/eduardsui/tlse "for tlse (PD)"),
[Evan Wallace](https://github.com/evanw "for their math library (CC0)"),
[Gargaj+cce/Peisik](https://github.com/gargaj/foxotron "for Foxotron/PBR shaders (UNLICENSE)"),
[Guilherme Lampert](https://github.com/glampert "for their math library (PD)"),
[Guillaume Vareille](http://tinyfiledialogs.sourceforge.net "for tinyfiledialogs (ZLIB)"),
[Haruhiko Okumura](https://oku.edu.mie-u.ac.jp/~okumura/compression/ "for lzss (PD)"),
[Igor Pavlov](https://www.7-zip.org/ "for LZMA (PD)"),
[Ilya Muravyov](https://github.com/encode84 "for bcm, balz, crush, ulz, lz4x (PD)"),
[Jon Olick](https://www.jonolick.com/ "for jo_mp1 and jo_mpeg (PD)"),
[Joonas Pihlajamaa](https://github.com/jokkebk/JUnzip "for JUnzip library (PD)"),
[Juliette Focault](https://github.com/juliettef/IconFontCppHeaders/blob/main/IconsMaterialDesign.h "for the generated MD header (ZLIB)"),
[Kristoffer Grönlund](https://github.com/krig "for their math library (CC0)"),
[Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo "for IQM spec & player (PD)"),
[Lee Salzman, V.Hrytsenko, D.Madarász](https://github.com/zpl-c/enet/ "for enet (MIT)"),
[Libtomcrypt](https://github.com/libtom/libtomcrypt "for libtomcrypt (Unlicense)"),
[Lua authors](https://www.lua.org/ "for Lua language (MIT)"),
[Mattias Gustavsson](https://github.com/mattiasgustavsson/libs "for thread.h and https.h (PD)"),
[Micha Mettke](https://github.com/vurtun "for their math library (PD)"),
[Micha Mettke, Chris Willcocks, Dmitry Hrabrov](https://github.com/vurtun/nuklear "for nuklear (PD)"),
[Michael Galetzka](https://github.com/Cultrarius/Swarmz "for swarmz (UNLICENSE)"),
[Morten Vassvik](https://github.com/vassvik/mv_easy_font "for mv_easy_font (Unlicense)"),
[Mārtiņš Možeiko](https://gist.github.com/mmozeiko/68f0a8459ef2f98bcd879158011cc275 "for A* pathfinding (PD)"),
[Omar Cornut, vaiorabbit](https://github.com/ocornut/imgui/pull/3627 "for tables of unicode ranges (MIT-0)"),
[Rabia Alhaffar](https://github.com/Rabios/ice_libs "for ice_batt.h (PD)"),
[Rich Geldreich](https://github.com/richgel999/miniz "for miniz (PD)"),
[Ross Williams](http://ross.net/compression/lzrw3a.html "for lzrw3a (PD)"),
[Samuli Raivio](https://github.com/bqqbarbhg/bq_websocket "for bq_websocket (PD)"),
[Sean Barrett](https://github.com/nothings "for stb_image, stb_image_write, stb_sprintf, stb_truetype and stb_vorbis (PD)"),
[Sebastian Steinhauer](https://github.com/kieselsteini "for sts_mixer (PD)"),
[Stan Melax, Cloud Wu](https://web.archive.org/web/20031204035320/http://www.melax.com/polychop/gdmag.pdf "for polychop C algorithm (PD)"),
[Stefan Gustavson](https://github.com/stegu/perlin-noise "for simplex noise (PD)"),
[Sterling Orsten](https://github.com/sgorsten "for their math library (UNLICENSE)"),
[Tor Andersson](https://github.com/ccxvii/minilibs "for xml.c (PD)"),
[Wolfgang Draxinger](https://github.com/datenwolf "for their math library (WTFPL2)"),
</details> <!--
- [DavidLam](https://en.wikipedia.org/wiki/Tokamak_(software) "for tokamak physics engine (ZLIB)")
<details><summary>Credits (Tools)</summary> - [ID Software, David St-Louis](https://github.com/Daivuk/PureDOOM "for PureDOOM (Doom License)")
- [Miloslav Číž](https://codeberg.org/drummyfish/Anarch "for Anarch (CC0)")
- [Rxi](https://github.com/rxi/autobatch "for lovely sprites & cats demo (MIT)")
- [Aaron Barany](https://github.com/akb825/Cuttlefish), for cuttlefish (APACHE2). -->
- [Arseny Kapoulkine](https://github.com/zeux/pugixml/), for pugixml (MIT).
- [Assimp authors](https://github.com/assimp/assimp), for assimp (BSD3).
- [Bernhard Schelling](https://github.com/schellingb/TinySoundFont), for tml.h (Zlib) and tsf.h (MIT).
- [ffmpeg authors](https://www.ffmpeg.org/), for ffmpeg (LGPL21).
- [Imagination](https://developer.imaginationtech.com/pvrtextool/), for pvrtextoolcli (ITL).
- [Krzysztof Gabis](https://github.com/kgabis/ape), for split.py/join.py (MIT).
- [Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo), for iqm.cpp (PD).
- [Mattias Gustavsson](https://github.com/mattiasgustavsson/libs), for mid.h (PD).
- [Michael Schmoock](http://github.com/willsteel/lcpp), for lcpp (MIT).
- [Olivier Lapicque, Konstanty Bialkowski](https://github.com/Konstanty/libmodplug), for libmodplug (PD).
- [Polyglot Team](https://docs.google.com/spreadsheets/d/17f0dQawb-s_Fd7DHgmVvJoEGDMH_yoSd8EYigrb0zmM/edit), for polyglot gamedev (CC0).
- [Tildearrow](https://github.com/tildearrow/furnace/), for Furnace (GPL2).
- [Tomas Pettersson](http://www.drpetter.se/), for sfxr (PD).
- [Tor Andersson](https://github.com/ccxvii/asstools), for assiqe.c (BSD).
</details>
<details><summary>Credits (Runtime)</summary>
- [Barerose](https://github.com/barerose), for swrap (CC0).
- [Camilla Löwy](https://github.com/elmindreda), for glfw3 (Zlib).
- [Dave Rand](https://tools.ietf.org/html/rfc1978) for ppp (PD).
- [David Herberth](https://github.com/dav1dde/), for glad generated code (PD).
- [David Reid](https://github.com/mackron), for miniaudio (PD).
- [Dominic Szablewski](https://github.com/phoboslab/pl_mpeg), for pl_mpeg (MIT).
- [Dominik Madarász](https://github.com/zaklaus), for json5 parser (PD).
- [Eduard Suica](https://github.com/eduardsui/tlse), for tlse (PD).
- [Gargaj+cce/Peisik](https://github.com/gargaj/foxotron), for Foxotron/PBR shaders (UNLICENSE).
- [Guillaume Vareille](http://tinyfiledialogs.sourceforge.net), for tinyfiledialogs (ZLIB).
- [Haruhiko Okumura](https://oku.edu.mie-u.ac.jp/~okumura/compression/) for lzss (PD).
- [Igor Pavlov](https://www.7-zip.org/) for LZMA (PD).
- [Ilya Muravyov](https://github.com/encode84) for bcm, balz, crush, ulz, lz4x (PD).
- [Jon Olick](https://www.jonolick.com/), for jo_mp1 and jo_mpeg (PD).
- [Joonas Pihlajamaa](https://github.com/jokkebk/JUnzip), for JUnzip library (PD).
- [Juliette Focault](https://github.com/juliettef/IconFontCppHeaders/blob/main/IconsMaterialDesign.h), for the generated MD header (ZLIB).
- [Lee Salzman](https://github.com/lsalzman/iqm/tree/5882b8c32fa622eba3861a621bb715d693573420/demo), for IQM spec & player (PD).
- [Lee Salzman, V.Hrytsenko, D.Madarász](https://github.com/zpl-c/enet/), for enet (MIT).
- [Libtomcrypt](https://github.com/libtom/libtomcrypt), for libtomcrypt (Unlicense).
- [Lua authors](https://www.lua.org/), for Lua language (MIT).
- [Mārtiņš Možeiko](https://gist.github.com/mmozeiko/68f0a8459ef2f98bcd879158011cc275), for A* pathfinding (PD).
- [Mattias Gustavsson](https://github.com/mattiasgustavsson/libs), for thread.h and https.h (PD).
- [Micha Mettke, Chris Willcocks, Dmitry Hrabrov](https://github.com/vurtun/nuklear), for nuklear (PD).
- [Michael Galetzka](https://github.com/Cultrarius/Swarmz), for swarmz (UNLICENSE).
- [Omar Cornut, vaiorabbit](https://github.com/ocornut/imgui/pull/3627), for tables of unicode ranges (MIT-0).
- [Rabia Alhaffar](https://github.com/Rabios/ice_libs), for ice_batt.h (PD).
- [Rich Geldreich](https://github.com/richgel999/miniz), for miniz (PD).
- [Ross Williams](http://ross.net/compression/lzrw3a.html) for lzrw3a (PD).
- [Samuli Raivio](https://github.com/bqqbarbhg/bq_websocket), for bq_websocket (PD).
- [Sean Barrett](https://github.com/nothings), for stb_image, stb_image_write, stb_sprintf, stb_truetype and stb_vorbis (PD).
- [Sebastian Steinhauer](https://github.com/kieselsteini), for sts_mixer (PD).
- [Stan Melax, Cloud Wu](https://web.archive.org/web/20031204035320/http://www.melax.com/polychop/gdmag.pdf), for polychop C algorithm (PD).
- [Stefan Gustavson](https://github.com/stegu/perlin-noise), for simplex noise (PD).
- [Tor Andersson](https://github.com/ccxvii/minilibs), for xml.c (PD).
- [Vassvik](https://github.com/vassvik/mv_easy_font), for mv_easy_font (Unlicense).
- Special thanks to [@ands](https://github.com/ands), [@barerose](https://github.com/barerose), [@datenwolf](https://github.com/datenwolf), [@evanw](https://github.com/evanw), [@glampert](https://github.com/glampert), [@krig](https://github.com/krig), [@sgorsten](https://github.com/sgorsten) and [@vurtun](https://github.com/vurtun) for their math libraries (PD,CC0,WTFPL2,CC0,PD,CC0,Unlicense,PD).
</details> </details>
@ -981,13 +993,9 @@ This software is released into the [public domain](https://unlicense.org/). Also
<details><summary>Links</summary> <details><summary>Links</summary>
<p> Still looking for alternatives? [amulet](https://github.com/ianmaclarty/amulet), [aroma](https://github.com/leafo/aroma/), [astera](https://github.com/tek256/astera), [blendelf](https://github.com/jesterKing/BlendELF), [bullordengine](https://github.com/MarilynDafa/Bulllord-Engine), [candle](https://github.com/EvilPudding/candle), [cave](https://github.com/kieselsteini/cave), [chickpea](https://github.com/ivansafrin/chickpea), [corange](https://github.com/orangeduck/Corange), [cute](https://github.com/RandyGaul/cute_framework), [dos-like](https://github.com/mattiasgustavsson/dos-like), [ejoy2d](https://github.com/ejoy/ejoy2d), [exengine](https://github.com/exezin/exengine), [gunslinger](https://github.com/MrFrenik/gunslinger), [hate](https://github.com/excessive/hate), [island](https://github.com/island-org/island), [juno](https://github.com/rxi/juno), [l](https://github.com/Lyatus/L), [lgf](https://github.com/Planimeter/lgf), [limbus](https://github.com/redien/limbus), [love](https://github.com/love2d/love/), [lovr](https://github.com/bjornbytes/lovr), [mini3d](https://github.com/mini3d/mini3d), [mintaro](https://github.com/mackron/mintaro), [mio](https://github.com/ccxvii/mio), [olive.c](https://github.com/tsoding/olive.c), [opensource](https://github.com/w23/OpenSource), [ouzel](https://github.com/elnormous/ouzel/), [pez](https://github.com/prideout/pez), [pixie](https://github.com/mattiasgustavsson/pixie), [punity](https://github.com/martincohen/Punity), [r96](https://github.com/badlogic/r96), [ricotech](https://github.com/dbechrd/RicoTech), [rizz](https://github.com/septag/rizz), [tigr](https://github.com/erkkah/tigr), [yourgamelib](https://github.com/duddel/yourgamelib)
<a href="https://github.com/r-lyeh/FWK/issues"><img alt="Issues" src="https://img.shields.io/github/issues-raw/r-lyeh/FWK.svg"/></a>
<a href="https://discord.gg/vu6Vt9d"><img alt="Discord" src="https://img.shields.io/discord/270565488365535232?color=5865F2&label=chat&logo=discord&logoColor=white"/></a><br/>
Still looking for alternatives? <a href="https://github.com/r-lyeh/FWK/issues"><img alt="Issues" src="https://img.shields.io/github/issues-raw/r-lyeh/FWK.svg"/></a> <a href="https://discord.gg/vu6Vt9d"><img alt="Discord" src="https://img.shields.io/discord/270565488365535232?color=5865F2&label=chat&logo=discord&logoColor=white"/></a>
[amulet](https://github.com/ianmaclarty/amulet), [aroma](https://github.com/leafo/aroma/), [astera](https://github.com/tek256/astera), [blendelf](https://github.com/jesterKing/BlendELF), [bullordengine](https://github.com/MarilynDafa/Bulllord-Engine), [candle](https://github.com/EvilPudding/candle), [cave](https://github.com/kieselsteini/cave), [chickpea](https://github.com/ivansafrin/chickpea), [corange](https://github.com/orangeduck/Corange), [cute](https://github.com/RandyGaul/cute_framework), [dos-like](https://github.com/mattiasgustavsson/dos-like), [ejoy2d](https://github.com/ejoy/ejoy2d), [exengine](https://github.com/exezin/exengine), [gunslinger](https://github.com/MrFrenik/gunslinger), [hate](https://github.com/excessive/hate), [island](https://github.com/island-org/island), [juno](https://github.com/rxi/juno), [l](https://github.com/Lyatus/L), [lgf](https://github.com/Planimeter/lgf), [limbus](https://github.com/redien/limbus), [love](https://github.com/love2d/love/), [lovr](https://github.com/bjornbytes/lovr), [mini3d](https://github.com/mini3d/mini3d), [mintaro](https://github.com/mackron/mintaro), [mio](https://github.com/ccxvii/mio), [olive.c](https://github.com/tsoding/olive.c), [opensource](https://github.com/w23/OpenSource), [ouzel](https://github.com/elnormous/ouzel/), [pez](https://github.com/prideout/pez), [pixie](https://github.com/mattiasgustavsson/pixie), [punity](https://github.com/martincohen/Punity), [r96](https://github.com/badlogic/r96), [ricotech](https://github.com/dbechrd/RicoTech), [rizz](https://github.com/septag/rizz), [tigr](https://github.com/erkkah/tigr), [yourgamelib](https://github.com/duddel/yourgamelib)
</p>
</details> </details>
## config ## config

View File

@ -252589,7 +252589,7 @@ int thread_detach( thread_ptr_t thread )
return CloseHandle( (HANDLE) thread ) != 0; return CloseHandle( (HANDLE) thread ) != 0;
#elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems
return pthread_detach( (pthread_t) thread ) == 0; return pthread_detach( (pthread_t) thread ) == 0;
@ -347177,7 +347177,7 @@ int ui_label_(const char *label, int alignment) {
struct nk_style *style = &ui_ctx->style; struct nk_style *style = &ui_ctx->style;
bool bold = label[0] == '*'; label += bold; bool bold = label[0] == '*'; label += bold;
struct nk_font *font = bold ? nk_glfw.atlas.fonts->next->next /*3rd font*/ : NULL; // list struct nk_font *font = bold && nk_glfw.atlas.fonts->next ? nk_glfw.atlas.fonts->next->next /*3rd font*/ : NULL; // list
if( !has_icon ) { if( !has_icon ) {
// set bold style and color if needed // set bold style and color if needed

View File

@ -873,7 +873,7 @@ int thread_detach( thread_ptr_t thread )
return CloseHandle( (HANDLE) thread ) != 0; return CloseHandle( (HANDLE) thread ) != 0;
#elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems
return pthread_detach( (pthread_t) thread ) == 0; return pthread_detach( (pthread_t) thread ) == 0;

View File

@ -1436,7 +1436,7 @@ int ui_label_(const char *label, int alignment) {
struct nk_style *style = &ui_ctx->style; struct nk_style *style = &ui_ctx->style;
bool bold = label[0] == '*'; label += bold; bool bold = label[0] == '*'; label += bold;
struct nk_font *font = bold ? nk_glfw.atlas.fonts->next->next /*3rd font*/ : NULL; // list struct nk_font *font = bold && nk_glfw.atlas.fonts->next ? nk_glfw.atlas.fonts->next->next /*3rd font*/ : NULL; // list
if( !has_icon ) { if( !has_icon ) {
// set bold style and color if needed // set bold style and color if needed

View File

@ -4,7 +4,7 @@
// # quickstart // # quickstart
// - win/vc : cl hello.c // - win/vc : cl hello.c
// - win/clang-cl : clang-cl hello.c // - win/clang-cl : clang-cl hello.c
// - win/tcc : tcc hello.c -m64 // - win/tcc : tools\tcc hello.c -m64
// - win/mingw : gcc hello.c -lws2_32 -lwinmm -ldbghelp -lole32 -luser32 -lgdi32 -lcomdlg32 // - win/mingw : gcc hello.c -lws2_32 -lwinmm -ldbghelp -lole32 -luser32 -lgdi32 -lcomdlg32
// - win/clang : clang hello.c -lws2_32 -lwinmm -ldbghelp -lole32 -luser32 -lgdi32 -lcomdlg32 // - win/clang : clang hello.c -lws2_32 -lwinmm -ldbghelp -lole32 -luser32 -lgdi32 -lcomdlg32
// - linux : cc hello.c -lm -ldl -lpthread -lX11 // - linux : cc hello.c -lm -ldl -lpthread -lX11
@ -12,7 +12,7 @@
// - osx : cc -ObjC hello.c -framework cocoa -framework iokit -framework audiotoolbox // - osx : cc -ObjC hello.c -framework cocoa -framework iokit -framework audiotoolbox
#define FWK_IMPLEMENTATION // unrolls single-header implementation #define FWK_IMPLEMENTATION // unrolls single-header implementation
#include "./engine/joint/fwk.h" // single-header file #include "engine/joint/fwk.h" // single-header file
int main() { int main() {
// options // options
@ -115,6 +115,7 @@ int main() {
bool enabled = fx_enabled(i); bool enabled = fx_enabled(i);
if( ui_bool(fx_name(i), &enabled) ) fx_enable(i, enabled); if( ui_bool(fx_name(i), &enabled) ) fx_enable(i, enabled);
} }
ui_panel_end(); ui_panel_end();
} }
} }

678
tools/debugger.lua 100644
View File

@ -0,0 +1,678 @@
--[[
Copyright (c) 2020 Scott Lembcke and Howling Moon Software
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
TODO:
* Print short function arguments as part of stack location.
* Properly handle being reentrant due to coroutines.
]]
local dbg
-- Use ANSI color codes in the prompt by default.
local COLOR_GRAY = ""
local COLOR_RED = ""
local COLOR_BLUE = ""
local COLOR_YELLOW = ""
local COLOR_RESET = ""
local GREEN_CARET = " => "
local function pretty(obj, max_depth)
if max_depth == nil then max_depth = dbg.pretty_depth end
-- Returns true if a table has a __tostring metamethod.
local function coerceable(tbl)
local meta = getmetatable(tbl)
return (meta and meta.__tostring)
end
local function recurse(obj, depth)
if type(obj) == "string" then
-- Dump the string so that escape sequences are printed.
return string.format("%q", obj)
elseif type(obj) == "table" and depth < max_depth and not coerceable(obj) then
local str = "{"
for k, v in pairs(obj) do
local pair = pretty(k, 0).." = "..recurse(v, depth + 1)
str = str..(str == "{" and pair or ", "..pair)
end
return str.."}"
else
-- tostring() can fail if there is an error in a __tostring metamethod.
local success, value = pcall(function() return tostring(obj) end)
return (success and value or "<!!error in __tostring metamethod!!>")
end
end
return recurse(obj, 0)
end
-- The stack level that cmd_* functions use to access locals or info
-- The structure of the code very carefully ensures this.
local CMD_STACK_LEVEL = 6
-- Location of the top of the stack outside of the debugger.
-- Adjusted by some debugger entrypoints.
local stack_top = 0
-- The current stack frame index.
-- Changed using the up/down commands
local stack_inspect_offset = 0
-- LuaJIT has an off by one bug when setting local variables.
local LUA_JIT_SETLOCAL_WORKAROUND = 0
-- Default dbg.read function
local function dbg_read(prompt)
dbg.write(prompt)
io.flush()
return io.read()
end
-- Default dbg.write function
local function dbg_write(str)
io.write(str)
end
local function dbg_writeln(str, ...)
if select("#", ...) == 0 then
dbg.write((str or "<NULL>").."\n")
else
dbg.write(string.format(str.."\n", ...))
end
end
local function format_loc(file, line) return COLOR_BLUE..file..COLOR_RESET..":"..COLOR_YELLOW..line..COLOR_RESET end
local function format_stack_frame_info(info)
local filename = info.source:match("@(.*)")
local source = filename and dbg.shorten_path(filename) or info.short_src
local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat)
local name = (info.name and "'"..COLOR_BLUE..info.name..COLOR_RESET.."'" or format_loc(source, info.linedefined))
return format_loc(source, info.currentline).." in "..namewhat.." "..name
end
local repl
-- Return false for stack frames without source,
-- which includes C frames, Lua bytecode, and `loadstring` functions
local function frame_has_line(info) return info.currentline >= 0 end
local function hook_factory(repl_threshold)
return function(offset, reason)
return function(event, _)
-- Skip events that don't have line information.
if not frame_has_line(debug.getinfo(2)) then return end
-- Tail calls are specifically ignored since they also will have tail returns to balance out.
if event == "call" then
offset = offset + 1
elseif event == "return" and offset > repl_threshold then
offset = offset - 1
elseif event == "line" and offset <= repl_threshold then
repl(reason)
end
end
end
end
local hook_step = hook_factory(1)
local hook_next = hook_factory(0)
local hook_finish = hook_factory(-1)
-- Create a table of all the locally accessible variables.
-- Globals are not included when running the locals command, but are when running the print command.
local function local_bindings(offset, include_globals)
local level = offset + stack_inspect_offset + CMD_STACK_LEVEL
local func = debug.getinfo(level).func
local bindings = {}
-- Retrieve the upvalues
do local i = 1; while true do
local name, value = debug.getupvalue(func, i)
if not name then break end
bindings[name] = value
i = i + 1
end end
-- Retrieve the locals (overwriting any upvalues)
do local i = 1; while true do
local name, value = debug.getlocal(level, i)
if not name then break end
bindings[name] = value
i = i + 1
end end
-- Retrieve the varargs (works in Lua 5.2 and LuaJIT)
local varargs = {}
do local i = 1; while true do
local name, value = debug.getlocal(level, -i)
if not name then break end
varargs[i] = value
i = i + 1
end end
if #varargs > 0 then bindings["..."] = varargs end
if include_globals then
-- In Lua 5.2, you have to get the environment table from the function's locals.
local env = (_VERSION <= "Lua 5.1" and getfenv(func) or bindings._ENV)
return setmetatable(bindings, {__index = env or _G})
else
return bindings
end
end
-- Used as a __newindex metamethod to modify variables in cmd_eval().
local function mutate_bindings(_, name, value)
local FUNC_STACK_OFFSET = 3 -- Stack depth of this function.
local level = stack_inspect_offset + FUNC_STACK_OFFSET + CMD_STACK_LEVEL
-- Set a local.
do local i = 1; repeat
local var = debug.getlocal(level, i)
if name == var then
dbg_writeln(COLOR_YELLOW.."debugger.lua"..GREEN_CARET.."Set local variable "..COLOR_BLUE..name..COLOR_RESET)
return debug.setlocal(level + LUA_JIT_SETLOCAL_WORKAROUND, i, value)
end
i = i + 1
until var == nil end
-- Set an upvalue.
local func = debug.getinfo(level).func
do local i = 1; repeat
local var = debug.getupvalue(func, i)
if name == var then
dbg_writeln(COLOR_YELLOW.."debugger.lua"..GREEN_CARET.."Set upvalue "..COLOR_BLUE..name..COLOR_RESET)
return debug.setupvalue(func, i, value)
end
i = i + 1
until var == nil end
-- Set a global.
dbg_writeln(COLOR_YELLOW.."debugger.lua"..GREEN_CARET.."Set global variable "..COLOR_BLUE..name..COLOR_RESET)
_G[name] = value
end
-- Compile an expression with the given variable bindings.
local function compile_chunk(block, env)
local source = "debugger.lua REPL"
local chunk = nil
if _VERSION <= "Lua 5.1" then
chunk = loadstring(block, source)
if chunk then setfenv(chunk, env) end
else
-- The Lua 5.2 way is a bit cleaner
chunk = load(block, source, "t", env)
end
if not chunk then dbg_writeln(COLOR_RED.."Error: Could not compile block:\n"..COLOR_RESET..block) end
return chunk
end
local SOURCE_CACHE = {}
local function where(info, context_lines)
local source = SOURCE_CACHE[info.source]
if not source then
source = {}
local filename = info.source:match("@(.*)")
if filename then
pcall(function() for line in io.lines(filename) do table.insert(source, line) end end)
elseif info.source then
for line in info.source:gmatch("(.-)\n") do table.insert(source, line) end
end
SOURCE_CACHE[info.source] = source
end
if source and source[info.currentline] then
for i = info.currentline - context_lines, info.currentline + context_lines do
local tab_or_caret = (i == info.currentline and GREEN_CARET or " ")
local line = source[i]
if line then dbg_writeln(COLOR_GRAY.."% 4d"..tab_or_caret.."%s", i, line) end
end
else
dbg_writeln(COLOR_RED.."Error: Source not available for "..COLOR_BLUE..info.short_src);
end
return false
end
-- Wee version differences
local unpack = unpack or table.unpack
local pack = function(...) return {n = select("#", ...), ...} end
local function cmd_step()
stack_inspect_offset = stack_top
return true, hook_step
end
local function cmd_next()
stack_inspect_offset = stack_top
return true, hook_next
end
local function cmd_finish()
local offset = stack_top - stack_inspect_offset
stack_inspect_offset = stack_top
return true, offset < 0 and hook_factory(offset - 1) or hook_finish
end
local function cmd_print(expr)
local env = local_bindings(1, true)
local chunk = compile_chunk("return "..expr, env)
if chunk == nil then return false end
-- Call the chunk and collect the results.
local results = pack(pcall(chunk, unpack(rawget(env, "...") or {})))
-- The first result is the pcall error.
if not results[1] then
dbg_writeln(COLOR_RED.."Error:"..COLOR_RESET.." "..results[2])
else
local output = ""
for i = 2, results.n do
output = output..(i ~= 2 and ", " or "")..pretty(results[i])
end
if output == "" then output = "<no result>" end
dbg_writeln(COLOR_BLUE..expr.. GREEN_CARET..output)
end
return false
end
local function cmd_eval(code)
local env = local_bindings(1, true)
local mutable_env = setmetatable({}, {
__index = env,
__newindex = mutate_bindings,
})
local chunk = compile_chunk(code, mutable_env)
if chunk == nil then return false end
-- Call the chunk and collect the results.
local success, err = pcall(chunk, unpack(rawget(env, "...") or {}))
if not success then
dbg_writeln(COLOR_RED.."Error:"..COLOR_RESET.." "..tostring(err))
end
return false
end
local function cmd_down()
local offset = stack_inspect_offset
local info
repeat -- Find the next frame with a file.
offset = offset + 1
info = debug.getinfo(offset + CMD_STACK_LEVEL)
until not info or frame_has_line(info)
if info then
stack_inspect_offset = offset
dbg_writeln("Inspecting frame: "..format_stack_frame_info(info))
if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end
else
info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL)
dbg_writeln("Already at the bottom of the stack.")
end
return false
end
local function cmd_up()
local offset = stack_inspect_offset
local info
repeat -- Find the next frame with a file.
offset = offset - 1
if offset < stack_top then info = nil; break end
info = debug.getinfo(offset + CMD_STACK_LEVEL)
until frame_has_line(info)
if info then
stack_inspect_offset = offset
dbg_writeln("Inspecting frame: "..format_stack_frame_info(info))
if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end
else
info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL)
dbg_writeln("Already at the top of the stack.")
end
return false
end
local function cmd_where(context_lines)
local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL)
return (info and where(info, tonumber(context_lines) or 5))
end
local function cmd_trace()
dbg_writeln("Inspecting frame %d", stack_inspect_offset - stack_top)
local i = 0; while true do
local info = debug.getinfo(stack_top + CMD_STACK_LEVEL + i)
if not info then break end
local is_current_frame = (i + stack_top == stack_inspect_offset)
local tab_or_caret = (is_current_frame and GREEN_CARET or " ")
dbg_writeln(COLOR_GRAY.."% 4d"..COLOR_RESET..tab_or_caret.."%s", i, format_stack_frame_info(info))
i = i + 1
end
return false
end
local function cmd_locals()
local bindings = local_bindings(1, false)
-- Get all the variable binding names and sort them
local keys = {}
for k, _ in pairs(bindings) do table.insert(keys, k) end
table.sort(keys)
for _, k in ipairs(keys) do
local v = bindings[k]
-- Skip the debugger object itself, "(*internal)" values, and Lua 5.2's _ENV object.
if not rawequal(v, dbg) and k ~= "_ENV" and not k:match("%(.*%)") then
dbg_writeln(" "..COLOR_BLUE..k.. GREEN_CARET..pretty(v))
end
end
return false
end
local function cmd_help()
dbg.write(""
.. COLOR_BLUE.." <return>"..GREEN_CARET.."re-run last command\n"
.. COLOR_BLUE.." c"..COLOR_YELLOW.."(ontinue)"..GREEN_CARET.."continue execution\n"
.. COLOR_BLUE.." s"..COLOR_YELLOW.."(tep)"..GREEN_CARET.."step forward by one line (into functions)\n"
.. COLOR_BLUE.." n"..COLOR_YELLOW.."(ext)"..GREEN_CARET.."step forward by one line (skipping over functions)\n"
.. COLOR_BLUE.." f"..COLOR_YELLOW.."(inish)"..GREEN_CARET.."step forward until exiting the current function\n"
.. COLOR_BLUE.." u"..COLOR_YELLOW.."(p)"..GREEN_CARET.."move up the stack by one frame\n"
.. COLOR_BLUE.." d"..COLOR_YELLOW.."(own)"..GREEN_CARET.."move down the stack by one frame\n"
.. COLOR_BLUE.." w"..COLOR_YELLOW.."(here) "..COLOR_BLUE.."[line count]"..GREEN_CARET.."print source code around the current line\n"
.. COLOR_BLUE.." e"..COLOR_YELLOW.."(val) "..COLOR_BLUE.."[statement]"..GREEN_CARET.."execute the statement\n"
.. COLOR_BLUE.." p"..COLOR_YELLOW.."(rint) "..COLOR_BLUE.."[expression]"..GREEN_CARET.."execute the expression and print the result\n"
.. COLOR_BLUE.." t"..COLOR_YELLOW.."(race)"..GREEN_CARET.."print the stack trace\n"
.. COLOR_BLUE.." l"..COLOR_YELLOW.."(ocals)"..GREEN_CARET.."print the function arguments, locals and upvalues.\n"
.. COLOR_BLUE.." h"..COLOR_YELLOW.."(elp)"..GREEN_CARET.."print this message\n"
.. COLOR_BLUE.." q"..COLOR_YELLOW.."(uit)"..GREEN_CARET.."halt execution\n"
)
return false
end
local last_cmd = false
local commands = {
["^c$"] = function() return true end,
["^s$"] = cmd_step,
["^n$"] = cmd_next,
["^f$"] = cmd_finish,
["^p%s+(.*)$"] = cmd_print,
["^e%s+(.*)$"] = cmd_eval,
["^u$"] = cmd_up,
["^d$"] = cmd_down,
["^w%s*(%d*)$"] = cmd_where,
["^t$"] = cmd_trace,
["^l$"] = cmd_locals,
["^h$"] = cmd_help,
["^q$"] = function() dbg.exit(0); return true end,
}
local function match_command(line)
for pat, func in pairs(commands) do
-- Return the matching command and capture argument.
if line:find(pat) then return func, line:match(pat) end
end
end
-- Run a command line
-- Returns true if the REPL should exit and the hook function factory
local function run_command(line)
-- GDB/LLDB exit on ctrl-d
if line == nil then dbg.exit(1); return true end
-- Re-execute the last command if you press return.
if line == "" then line = last_cmd or "h" end
local command, command_arg = match_command(line)
if command then
last_cmd = line
-- unpack({...}) prevents tail call elimination so the stack frame indices are predictable.
return unpack({command(command_arg)})
elseif dbg.auto_eval then
return unpack({cmd_eval(line)})
else
dbg_writeln(COLOR_RED.."Error:"..COLOR_RESET.." command '%s' not recognized.\nType 'h' and press return for a command list.", line)
return false
end
end
repl = function(reason)
-- Skip frames without source info.
while not frame_has_line(debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3)) do
stack_inspect_offset = stack_inspect_offset + 1
end
local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3)
reason = reason and (COLOR_YELLOW.."break via "..COLOR_RED..reason..GREEN_CARET) or ""
dbg_writeln(reason..format_stack_frame_info(info))
if tonumber(dbg.auto_where) then where(info, dbg.auto_where) end
repeat
local success, done, hook = pcall(run_command, dbg.read(COLOR_RED.."debugger.lua> "..COLOR_RESET))
if success then
debug.sethook(hook and hook(0), "crl")
else
local message = COLOR_RED.."INTERNAL DEBUGGER.LUA ERROR. ABORTING\n:"..COLOR_RESET.." "..done
dbg_writeln(message)
error(message)
end
until done
end
-- Make the debugger object callable like a function.
dbg = setmetatable({}, {
__call = function(_, condition, top_offset, source)
if condition then return end
top_offset = (top_offset or 0)
stack_inspect_offset = top_offset
stack_top = top_offset
debug.sethook(hook_next(1, source or "dbg()"), "crl")
return
end,
})
-- Expose the debugger's IO functions.
dbg.read = dbg_read
dbg.write = dbg_write
dbg.shorten_path = function (path) return path end
dbg.exit = function(err) os.exit(err) end
dbg.writeln = dbg_writeln
dbg.pretty_depth = 3
dbg.pretty = pretty
dbg.pp = function(value, depth) dbg_writeln(pretty(value, depth)) end
dbg.auto_where = false
dbg.auto_eval = false
local lua_error, lua_assert = error, assert
-- Works like error(), but invokes the debugger.
function dbg.error(err, level)
level = level or 1
dbg_writeln(COLOR_RED.."ERROR: "..COLOR_RESET..pretty(err))
dbg(false, level, "dbg.error()")
lua_error(err, level)
end
-- Works like assert(), but invokes the debugger on a failure.
function dbg.assert(condition, message)
if not condition then
dbg_writeln(COLOR_RED.."ERROR:"..COLOR_RESET..message)
dbg(false, 1, "dbg.assert()")
end
return lua_assert(condition, message)
end
-- Works like pcall(), but invokes the debugger on an error.
function dbg.call(f, ...)
return xpcall(f, function(err)
dbg_writeln(COLOR_RED.."ERROR: "..COLOR_RESET..pretty(err))
dbg(false, 1, "dbg.call()")
return err
end, ...)
end
-- Error message handler that can be used with lua_pcall().
function dbg.msgh(...)
if debug.getinfo(2) then
dbg_writeln(COLOR_RED.."ERROR: "..COLOR_RESET..pretty(...))
dbg(false, 1, "dbg.msgh()")
else
dbg_writeln(COLOR_RED.."debugger.lua: "..COLOR_RESET.."Error did not occur in Lua code. Execution will continue after dbg_pcall().")
end
return ...
end
-- Assume stdin/out are TTYs unless we can use LuaJIT's FFI to properly check them.
local stdin_isatty = true
local stdout_isatty = true
-- Conditionally enable the LuaJIT FFI.
local ffi = (jit and require("ffi"))
if ffi then
ffi.cdef[[
int isatty(int); // Unix
int _isatty(int); // Windows
void free(void *ptr);
char *readline(const char *);
int add_history(const char *);
]]
local function get_func_or_nil(sym)
local success, func = pcall(function() return ffi.C[sym] end)
return success and func or nil
end
local isatty = get_func_or_nil("isatty") or get_func_or_nil("_isatty")
stdin_isatty = isatty(0)
stdout_isatty = isatty(1)
end
-- Conditionally enable color support.
local color_maybe_supported = (stdout_isatty and os.getenv("TERM") and os.getenv("TERM") ~= "dumb")
if color_maybe_supported and not os.getenv("DBG_NOCOLOR") then
COLOR_GRAY = string.char(27) .. "[90m"
COLOR_RED = string.char(27) .. "[91m"
COLOR_BLUE = string.char(27) .. "[94m"
COLOR_YELLOW = string.char(27) .. "[33m"
COLOR_RESET = string.char(27) .. "[0m"
GREEN_CARET = string.char(27) .. "[92m => "..COLOR_RESET
end
if stdin_isatty and not os.getenv("DBG_NOREADLINE") then
pcall(function()
local linenoise = require 'linenoise'
-- Load command history from ~/.lua_history
local hist_path = os.getenv('HOME') .. '/.lua_history'
linenoise.historyload(hist_path)
linenoise.historysetmaxlen(50)
local function autocomplete(env, input, matches)
for name, _ in pairs(env) do
if name:match('^' .. input .. '.*') then
linenoise.addcompletion(matches, name)
end
end
end
-- Auto-completion for locals and globals
linenoise.setcompletion(function(matches, input)
-- First, check the locals and upvalues.
local env = local_bindings(1, true)
autocomplete(env, input, matches)
-- Then, check the implicit environment.
env = getmetatable(env).__index
autocomplete(env, input, matches)
end)
dbg.read = function(prompt)
local str = linenoise.linenoise(prompt)
if str and not str:match "^%s*$" then
linenoise.historyadd(str)
linenoise.historysave(hist_path)
end
return str
end
dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Linenoise support enabled.")
end)
-- Conditionally enable LuaJIT readline support.
pcall(function()
if dbg.read == nil and ffi then
local readline = ffi.load("readline")
dbg.read = function(prompt)
local cstr = readline.readline(prompt)
if cstr ~= nil then
local str = ffi.string(cstr)
if string.match(str, "[^%s]+") then
readline.add_history(cstr)
end
ffi.C.free(cstr)
return str
else
return nil
end
end
dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Readline support enabled.")
end
end)
end
-- Detect Lua version.
if jit then -- LuaJIT
LUA_JIT_SETLOCAL_WORKAROUND = -1
dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Loaded for "..jit.version)
elseif "Lua 5.1" <= _VERSION and _VERSION <= "Lua 5.4" then
dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Loaded for ".._VERSION)
else
dbg_writeln(COLOR_YELLOW.."debugger.lua: "..COLOR_RESET.."Not tested against ".._VERSION)
dbg_writeln("Please send me feedback!")
end
return dbg

View File

@ -0,0 +1,501 @@
/* A mathematical expression evaluator.
* It uses a recursive descent parser internally.
* Author: Werner Stoop
* This is free and unencumbered software released into the public domain.
* http://unlicense.org/
*/
#include <assert.h>
#include <ctype.h>
#include <math.h> /* remember to compile with -lm */
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
/* Special tokens used by the lexer function lex()
* they've been chosen as non-printable characters
* so that printable characters can be used for other
* purposes
*/
#define TOK_END 0 /* end of text */
#define TOK_INI 1 /* Initial state */
#define TOK_ID 2 /* identifier */
#define TOK_NUM 3 /* number */
/* Types of errors */
// 0 /* "no error" */
#define ERR_MEMORY 1 /* "out of memory" */
#define ERR_LEXER 2 /* "unknown token" */
#define ERR_LONGID 3 /* "identifier too long" */
#define ERR_VALUE 4 /* "value expected" */
#define ERR_BRACKET 5 /* "missing ')'" */
#define ERR_FUNC 6 /* "unknown function" */
#define ERR_ARGS 7 /* "wrong number of arguments" */
#define ERR_CONST 8 /* "unknown constant" */
/* Other definitions */
#define MAX_ID_LEN 11 /* Max length of an identifier */
#define OPERATORS "+-*/%(),^" /* Valid operators */
#define EVAL_PI 3.141592654
#define EVAL_E 2.718281828
#define EVAL_DEG (EVAL_PI/180)
/* Internal structure for the parser/evaluator */
struct eval {
jmp_buf j; /* For error handling */
const char *p; /* Position in the text being parsed */
double *st; /* Stack */
int st_size; /* Stack size */
int sp; /* Stack pointer */
/* The current and next tokens identified by the lexer */
struct {
int type; /* Type of the token */
double n_val; /* Numeric value of the previous lexed token */
char s_val[MAX_ID_LEN]; /* String (identifier) value of the previous lexed token */
} token[2];
int cur_tok; /* Current token, either 0 or 1 (see the comments of lex()) */
};
/* Prototypes */
static double pop(struct eval *ev);
static void push(struct eval *ev, double d);
static int lex(struct eval *ev);
/* Prototypes for the recursive descent parser */
static void expr(struct eval *ev);
static void add_expr(struct eval *ev);
static void mul_expr(struct eval *ev);
static void pow_expr(struct eval *ev);
static void uni_expr(struct eval *ev);
static void bra_expr(struct eval *ev);
static void id_expr(struct eval *ev);
static void num_expr(struct eval *ev);
/*
* Evaluates a mathemeatical expression
*/
double eval(const char *exp/*, int *ep*/) {
int _ep, *ep = &_ep;
struct eval ev;
double ans = 0.0;
assert(ep != NULL);
/* Allocate a stack */
ev.st_size = 10;
ev.st = CALLOC(ev.st_size, sizeof *ev.st);
if(!ev.st)
{
*ep = ERR_MEMORY;
return NAN; //0.0;
}
ev.sp = 0;
/* Manage errors */
*ep = setjmp(ev.j);
if(*ep != 0)
{
FREE(ev.st);
return NAN; //0.0;
}
/* Initialize the lexer */
ev.token[0].type = TOK_INI;
ev.token[0].s_val[0] = '\0';
ev.token[1].type = TOK_INI;
ev.token[1].s_val[0] = '\0';
ev.cur_tok = 0;
/* Initialize the parser */
ev.p = exp;
/* lex once to initialize the lexer */
if(lex(&ev) != TOK_END)
{
expr(&ev);
ans = pop(&ev);
}
FREE(ev.st);
return ans;
}
/*
* Pushes a value onto the stack, increases the stack size if necessary
*/
static void push(struct eval *ev, double d) {
if(ev->sp == ev->st_size) {
/* Resize the stack by 1.5 */
double *old = ev->st;
int new_size = ev->st_size + (ev->st_size >> 1);
ev->st = REALLOC(ev->st, new_size);
if(!ev->st) {
ev->st = old;
longjmp(ev->j, ERR_MEMORY);
}
ev->st_size = new_size;
}
ev->st[ev->sp++] = d;
}
// Pops a value from the top of the stack
static double pop(struct eval *ev) {
assert(ev->sp > 0);
return ev->st[--ev->sp];
}
// stricmp() is common, but not standard, so I provide my own
static int istrcmp(const char *p, const char *q) {
for(; tolower(p[0]) == tolower(q[0]) && p[0]; p++, q++);
return tolower(p[0]) - tolower(q[0]);
}
/*
* Lexical analyzer function
*
* In order to implement LL(1), struct eval has an array of two token structures,
* and its cur_tok member is used to point to the _current_ token, while the other
* element contains the _next_ token. This implements a 2 element ring buffer where
* the lexer always writes to the _next_ token so that the recursive descent parser can
* _peek_ at the next token.
*/
static int lex(struct eval *ev) {
int next_tok;
start:
/* Cycle the tokens */
next_tok = ev->cur_tok;
ev->cur_tok = ev->cur_tok?0:1;
while(isspace(ev->p[0])) ev->p++;
if(!ev->p[0]) {
/* End of the expression */
ev->token[next_tok].type = TOK_END;
goto end;
}
else if(isdigit(ev->p[0]) || ev->p[0] == '.') {
/* Number */
char *endp;
ev->token[next_tok].type = TOK_NUM;
ev->token[next_tok].n_val = strtod(ev->p, &endp);
ev->p = endp;
goto end;
}
else if(isalpha(ev->p[0])) {
/* Identifier */
int i;
for(i = 0; isalnum(ev->p[0]) && i < MAX_ID_LEN - 1; i++, ev->p++)
ev->token[next_tok].s_val[i] = ev->p[0];
if(isalpha(ev->p[0])) longjmp(ev->j, ERR_LONGID);
ev->token[next_tok].s_val[i] = '\0';
ev->token[next_tok].type = TOK_ID;
goto end;
}
else if(strchr(OPERATORS, ev->p[0])) {
/* Operator */
ev->token[next_tok].type = ev->p[0];
ev->p++;
goto end;
}
else /* Unknown token */
longjmp(ev->j, ERR_LEXER);
end:
/* If this was the first call, cycle the tokens again */
if(ev->token[ev->cur_tok].type == TOK_INI)
goto start;
return ev->token[ev->cur_tok].type;
}
#define EVAL_TYPE(e) (e->token[e->cur_tok].type)
#define EVAL_ERROR(c) longjmp(ev->j, (c))
// num_expr ::= NUMBER
static void num_expr(struct eval *ev) {
if(EVAL_TYPE(ev) != TOK_NUM)
EVAL_ERROR(ERR_VALUE);
push(ev, ev->token[ev->cur_tok].n_val);
lex(ev);
}
// expr ::= add_expr
static void expr(struct eval *ev) {
add_expr(ev);
}
// add_expr ::= mul_expr [('+'|'-') mul_expr]
static void add_expr(struct eval *ev) {
int t;
mul_expr(ev);
while((t =EVAL_TYPE(ev)) == '+' || t == '-') {
double a,b;
lex(ev);
mul_expr(ev);
b = pop(ev);
a = pop(ev);
if(t == '+')
push(ev, a + b);
else
push(ev, a - b);
}
}
// mul_expr ::= pow_expr [('*'|'/'|'%') pow_expr]
static void mul_expr(struct eval *ev) {
int t;
pow_expr(ev);
while((t = EVAL_TYPE(ev)) == '*' || t == '/' || t == '%') {
double a,b;
lex(ev);
pow_expr(ev);
b = pop(ev);
a = pop(ev);
if(t == '*')
push(ev, a * b);
else if(t == '/')
push(ev, a / b);
else
push(ev, fmod(a, b));
}
}
// pow_expr ::= uni_expr ['^' pow_expr]
static void pow_expr(struct eval *ev) {
/* Note that exponentiation is right associative:
2^3^4 is 2^(3^4), not (2^3)^4 */
uni_expr(ev);
if(EVAL_TYPE(ev) == '^') {
double a,b;
lex(ev);
pow_expr(ev);
b = pop(ev);
a = pop(ev);
push(ev, pow(a,b));
}
}
// uni_expr ::= ['+'|'-'] bra_expr
static void uni_expr(struct eval *ev) {
int t = '+';
if(EVAL_TYPE(ev) == '-' || EVAL_TYPE(ev) == '+') {
t = EVAL_TYPE(ev);
lex(ev);
}
bra_expr(ev);
if(t == '-') {
double a = pop(ev);
push(ev, -a);
}
}
// bra_expr ::= '(' add_expr ')' | id_expr
static void bra_expr(struct eval *ev) {
if(EVAL_TYPE(ev) == '(') {
lex(ev);
add_expr(ev);
if(EVAL_TYPE(ev) != ')')
EVAL_ERROR(ERR_BRACKET);
lex(ev);
}
else
id_expr(ev);
}
// id_expr ::= ID '(' add_expr [',' add_expr]* ')' | ID | num_expr
static void id_expr(struct eval *ev) {
int nargs = 0;
char id[MAX_ID_LEN];
if(EVAL_TYPE(ev) != TOK_ID) {
num_expr(ev);
} else {
strcpy(id, ev->token[ev->cur_tok].s_val);
lex(ev);
if(EVAL_TYPE(ev) != '(') {
/**/ if(!istrcmp(id, "true")) push(ev, 1.0);
else if(!istrcmp(id, "false")) push(ev, 0.0);
else if(!istrcmp(id, "on")) push(ev, 1.0);
else if(!istrcmp(id, "off")) push(ev, 0.0);
// pi - 3.141592654
else if(!istrcmp(id, "pi"))
push(ev, EVAL_PI);
// e - base of natural logarithms, 2.718281828
else if(!istrcmp(id, "e"))
push(ev, EVAL_E);
// deg - deg2rad, allows to degree conversion `sin(90*deg) = 1`
else if(!istrcmp(id, "deg"))
push(ev, EVAL_DEG);
else
EVAL_ERROR(ERR_CONST);
} else {
lex(ev);
while(EVAL_TYPE(ev) != ')') {
add_expr(ev);
nargs++;
if(EVAL_TYPE(ev) == ')') break;
if(EVAL_TYPE(ev) != ',')
EVAL_ERROR(ERR_BRACKET);
lex(ev);
}
lex(ev);
// abs(x) - absolute value of x
if(!istrcmp(id, "abs")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, fabs(pop(ev)));
}
// ceil(x) - smallest integer greater than x
else if(!istrcmp(id, "ceil")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, ceil(pop(ev)));
}
// floor(x) - largest integer smaller than x
else if(!istrcmp(id, "floor")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, floor(pop(ev)));
}
// sin(x) - sine of x, in radians
else if(!istrcmp(id, "sin")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, sin(pop(ev)));
}
// asin(x) - arcsine of x, in radians
else if(!istrcmp(id, "asin")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, asin(pop(ev)));
}
// cos(x) - cosine of x, in radians
else if(!istrcmp(id, "cos")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, cos(pop(ev)));
}
// acos(x) - arccosine of x, in radians
else if(!istrcmp(id, "acos")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, acos(pop(ev)));
}
// tan(x) - tangent of x, in radians
else if(!istrcmp(id, "tan")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, tan(pop(ev)));
}
// atan(x) - arctangent of x, in radians
else if(!istrcmp(id, "atan")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, atan(pop(ev)));
}
// atan(y,x) - arctangent of y/x, in radians.
else if(!istrcmp(id, "atan2")) {
double a, b;
if(nargs != 2) EVAL_ERROR(ERR_ARGS);
b = pop(ev);
a = pop(ev);
push(ev, atan2(a,b));
}
// sinh(x) - hyperbolic sine of x, in radians
else if(!istrcmp(id, "sinh")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, sinh(pop(ev)));
}
// cosh(x) - hyperbolic cosine of x, in radians
else if(!istrcmp(id, "cosh")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, cosh(pop(ev)));
}
// tanh(x) - hyperbolic tangent of x, in radians
else if(!istrcmp(id, "tanh")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, tanh(pop(ev)));
}
// log(x) - natural logarithm of x
else if(!istrcmp(id, "log")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, log(pop(ev)));
}
// log10(x) - logarithm of x, base-10
else if(!istrcmp(id, "log10")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, log10(pop(ev)));
}
// exp(x) - computes e^x
else if(!istrcmp(id, "exp")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, exp(pop(ev)));
}
// sqrt(x) - square root of x
else if(!istrcmp(id, "sqrt")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, sqrt(pop(ev)));
}
// rad(x) - converts x from degrees to radians
else if(!istrcmp(id, "rad")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, pop(ev)*EVAL_PI/180);
}
// deg(x) - converts x from radians to degrees
else if(!istrcmp(id, "deg")) {
if(nargs != 1) EVAL_ERROR(ERR_ARGS);
push(ev, pop(ev)*180/EVAL_PI);
}
// pow(x,y) - computes x^y
else if(!istrcmp(id, "pow")) {
double a, b;
if(nargs != 2) EVAL_ERROR(ERR_ARGS);
b = pop(ev);
a = pop(ev);
push(ev, pow(a,b));
}
// hypot(x,y) - computes sqrt(x*x + y*y)
else if(!istrcmp(id, "hypot")) {
double a, b;
if(nargs != 2) EVAL_ERROR(ERR_ARGS);
b = pop(ev);
a = pop(ev);
push(ev, sqrt(a*a + b*b));
}
else
EVAL_ERROR(ERR_FUNC);
}
}
}
//
#ifdef EVALDEMO
#include <stdio.h>
int main(int argc, char *argv[]) {
int i;
double e;
for(i = 1; i < argc; i++) {
e = eval(argv[i]);
if(e != e)
fprintf(stderr, "Error in expression %s\n", argv[i]);
else
printf("%s = %g\n", argv[i], e);
}
assert( eval("1+1") == 2 );
assert( eval("1+") != eval("1+") );
assert(~puts("Ok") );
}
#endif

View File

@ -0,0 +1,82 @@
#ifdef ICON
// 2 states
ICON(DONE),ICON(DONE_ALL),
ICON(CHECK_BOX),ICON(CHECK_BOX_OUTLINE_BLANK),
ICON(VISIBILITY),ICON(VISIBILITY_OFF),
ICON(NOTIFICATIONS),ICON(NOTIFICATIONS_OFF),
ICON(TOGGLE_ON),ICON(TOGGLE_OFF),
ICON(LIGHTBULB),ICON(LIGHTBULB_OUTLINE),
ICON(FULLSCREEN),ICON(FULLSCREEN_EXIT),
ICON(VIDEOCAM),ICON(VIDEOCAM_OFF),
ICON(FIBER_MANUAL_RECORD),ICON(FIBER_SMART_RECORD),
ICON(EXPAND_MORE),ICON(EXPAND_LESS),
ICON(SPORTS_ESPORTS),ICON(VIDEOGAME_ASSET),ICON(GAMEPAD),//ICON(STADIA_CONTROLLER),
ICON(FILE_DOWNLOAD),ICON(FILE_UPLOAD),
// 3 states
ICON(FOLDER_OPEN),ICON(CREATE_NEW_FOLDER),ICON(FOLDER_SPECIAL),
ICON(CONTENT_CUT),ICON(CONTENT_COPY),ICON(CONTENT_PASTE),
ICON(STAR),ICON(STAR_HALF),ICON(STAR_OUTLINE),
ICON(VOLUME_DOWN),ICON(VOLUME_UP),ICON(VOLUME_OFF),
// 6 states
ICON(FAST_REWIND),ICON(SKIP_PREVIOUS),ICON(PLAY_ARROW),ICON(SKIP_NEXT),ICON(FAST_FORWARD),ICON(REPEAT),
// 12 states
ICON(POWER),ICON(BATTERY_CHARGING_FULL),ICON(BATTERY_FULL),ICON(BATTERY_6_BAR),ICON(BATTERY_5_BAR),ICON(BATTERY_4_BAR),ICON(BATTERY_3_BAR),ICON(BATTERY_2_BAR),ICON(BATTERY_1_BAR),ICON(BATTERY_0_BAR),ICON(BATTERY_ALERT),
ICON(MOVIE),
ICON(CAMERA),ICON(PHOTO_CAMERA),
ICON(SPEED),
ICON(ROOM),
ICON(PUSH_PIN),
ICON(FLAG),
ICON(G_TRANSLATE),
ICON(SETTINGS),
ICON(CLOSED_CAPTION),
ICON(BUILD),
ICON(ROCKET_LAUNCH),
ICON(TODAY),ICON(EVENT_NOTE),
ICON(3D_ROTATION),
ICON(LAUNCH),
ICON(SEARCH),
ICON(TIMELAPSE),
ICON(ARROW_DOWNWARD),
ICON(CALL_MERGE),
ICON(ARCHIVE),
ICON(SHOW_CHART),
ICON(WARNING),
ICON(CREATE),ICON(ADD),
ICON(TEXT_FORMAT),
ICON(CHECK),
ICON(SAVE),
ICON(CANCEL),
ICON(DELETE),
ICON(CLOSE),
ICON(REFRESH),
ICON(SYNC),
ICON(HIGHLIGHT_OFF),
ICON(SYSTEM_UPDATE),
ICON(UNDO),ICON(REDO),
ICON(CLASS),
ICON(TITLE),
ICON(HD),
ICON(VIEW_QUILT),
ICON(FINGERPRINT),
ICON(VPN_KEY),
ICON(FACE),ICON(PERSON),
ICON(CHAT_BUBBLE),
ICON(COPYRIGHT),
#undef ICON
#endif

1836
tools/editor/editor.c 100644

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View File

@ -0,0 +1,471 @@
/*
<!-- editor = tree of nodes. levels and objects are nodes, and their widgets are also nodes -->
<!-- you can perform actions on nodes, with or without descendants, top-bottom or bottom-top -->
<!-- these operations include load/save, undo/redo, reset, play/render, ddraw, etc -->
<!-- nodes are saved to disk as a filesystem layout: parents are folders, and leafs are files -->
<!-- network replication can be done by external tools by comparing the filesystems and by sending the resulting zipped .diff -->
- [ ] Editor: Gizmos, scene tree, property editor, load/save, undo/redo, copy/paste, on/off (vis,tick,ddraw,log), vcs.
- [ ] Editor: Scenenode pass: node singleton display, node console, node labels, node outlines.<!-- node == gameobj ? -->
- [ ] Editor: Debug pass: toggles on/off (billboards, materials, un/lit, cast shadows, wireframe, skybox/mie, fog/atmosphere, collide, physics).
- [ ] Editor: Level pass: volumes, triggers, platforms, level streaming.
- [ ] Editor: Edit pass: Procedural content, brushes, noise and CSG.
- [ ] Editor: GUI pass: timeline and data tracks, node graphs. <!-- worthy: will be reused into materials, animgraphs and blueprints -->
*/
#include "fwk.c"
#include "editor2.h" // old editor interface
#define ui_push_hspace(px) \
(int xx = px; xx; xx = 0) \
for(struct nk_panel *layout = ui_ctx->current->layout; layout; ) \
for( xx = (layout->at_x += px, layout->bounds.w -= px, 0); layout; layout->at_x -= px, layout->bounds.w += px, layout = 0 )
// ----------------------------------------------------------------------------------------
#define expr expr2 // 3rd_lua.h
#include "3rd_eval.h"
// ----------------------------------------------------------------------------------------
#include "labs.meta/meta_reflect.c"
int *meta_changed(void *value) {
static map(void*,int) changes = 0;
do_once map_init_ptr(changes);
return map_find_or_add(changes, value, 0);
}
void reflect_ui( const reflect *r, void *value, void *userdata ) {
ui_label_icon_highlight = *meta_changed(value); // @hack: remove ui_label_icon_highlight hack
char *title = va(ICON_MD_UNDO "%s", r->info);
int changed = 0;
/**/ if( !strcmp(r->type, "int") ) changed = ui_int(title, (int*)value);
else if( !strcmp(r->type, "char") && r->is_ptr ) changed = ui_buffer(title, (char*)value, strlen((char*)value)+1);
else if( !strcmp(r->type, "string") ) changed = ui_string(title, (char**)value);
else if( !strcmp(r->type, "float") ) changed = ui_float(title, (float*)value);
else if( !strcmp(r->type, "double") ) changed = ui_double(title, (double*)value);
else if( !strcmp(r->type, "unsigned") ) changed = ui_unsigned(title, (unsigned*)value);
else if( !strcmp(r->type, "color") ) changed = ui_color4(va("%s #%02X%02X%02X%02X", title, (int)(0[(float*)value]),(int)(1[(float*)value]),(int)(2[(float*)value]),(int)(3[(float*)value])), (float*)value);
// else if( !strcmp(type, "vec3") ) ; // not supported. decays to 3 floats
else ui_label2(title, va("(%s)%s", r->type, r->name));
if( changed ) {
*meta_changed(value) = 1;
}
if( ui_label_icon_clicked_L.x >= 6 && ui_label_icon_clicked_L.x <= 26 ) { // @hack: if clicked on UNDO icon (1st icon)
*meta_changed(value) = 0;
}
}
bool reflect_parse(void *obj, const char *type, const char *val) {
/**/ if( !strcmp(type, "int") ) *((int*)obj) = eval(val);
// else if( !strcmp(r->type, "char") && r->is_ptr ) ; // @fixme: not supported, unless we do strncpy() or similar.
else if( !strcmp(type, "string") ) *((char**)obj) = stringf("%s", val);
else if( !strcmp(type, "float") ) *((float*)obj) = eval(val); // = v[0] == '~' ? (float)~atoi(val+1) : atof(val); // = atof(val);
else if( !strcmp(type, "double") ) *((double*)obj) = eval(val); // = v[0] == '~' ? (float)~atoi(val+1) : atof(val); // = atof(val);
else if( !strcmp(type, "unsigned") ) *((unsigned*)obj) = eval(val); // = v[0] == '~' ? (float)~atoi(val+1) : atof(val); // = atof(val);
else if( !strcmp(type, "color") ) *(((float*)obj)+3) = 255, sscanf(val, "%f %f %f %f", ((float*)obj)+0, ((float*)obj)+1, ((float*)obj)+2, ((float*)obj)+3);
else if( !strcmp(type, "vec3") ) sscanf(val, "%f %f %f", ((float*)obj)+0, ((float*)obj)+1, ((float*)obj)+2);
else return 0;
return 1;
}
// ----------------------------------------------------------------------------------------
typedef void(*obj_ctor)(void*);
static map(char*, obj_ctor) obj_ctors;
#define STRUCT_CTOR(type, ctor) STRUCT_CTOR(#type, (obj_ctor)ctor)
void (STRUCT_CTOR)( const char *type, obj_ctor ctor ) {
do_once map_init_str(obj_ctors);
map_find_or_add(obj_ctors, STRDUP(type), ctor);
}
bool obj_make(void *obj, const char *ini_data) { // initialize object from ini datas
char *hint = 0;
for( ini_t read = ini_from_mem(ini_data); !!read; map_free(read), read = 0) {
for each_map(read, char*, k, char*, v) {
array(char*) tokens = strsplit(k, ".");
if( array_count(tokens) != 2 ) continue;
const char *type = 0;
void *found = reflect_field( tokens[0], obj, tokens[1], &type );
if( !found ) continue;
if( reflect_parse(found, type, v) ) {
hint = tokens[0];
}
}
// constructor (post-init call)
obj_ctor *ctor = map_find(obj_ctors, hint);
if( ctor ) (*ctor)( obj );
}
return hint != 0;
}
// ----------------------------------------------------------------------------
#define POD_TYPES \
vec2i v2i; \
vec2 v2; \
vec3 v3; \
vec4 v4; \
quat q; \
char *s; \
double d; \
int64_t i; \
unsigned color; \
void *ptr; \
void (*fun)(void*); \
char *nametype; /* "name\0type" */
typedef union pod {
POD_TYPES
} pod;
typedef union var {
POD_TYPES
array(pod) array;
array(pod) pair; // array of 2: first,second. maybe used as key,value
array(pod) tuple; // array of N: first,second,third... maybe used as array of N*2: NAME1,val1,NAME2,val2, ...
map(pod,pod) map;
set(pod) set;
} var;
typedef struct node {
var k;
var v;
struct node *up;
struct node *next;
struct node *down;
} node;
char* node_name(node *n) {
return n->k.nametype;
}
char* node_type(node *n) {
return n->k.nametype + strlen(n->k.nametype) + 1;
}
char* node_set_nametype(node *n, const char *name, const char *type) {
*strchr(n->k.nametype = stringf("%s\1%s", name, type), '\1') = '\0'; // @leak
return n->k.nametype;
}
node* node_find(node *n, const char *path) {
if (!n->k.nametype) return 0;
if( !strcmp(node_name(n), path) ) return n;
node *r = 0;
if( n->next ) r = node_find(n->next, path);
if( n->down && !r ) if( !strcmp(node_name(n->down), path) ) return n->down; // r = node_find(n->down, path);
return r;
}
node* node_find_recurse(node *n, const char *path) {
array(char*) split = strsplit(path, "/");
while( n && array_count(split) ) {
n = node_find(n, split[0]);
array_pop(split);
}
return n;
}
node* node_attach_sibling(node *n, node *sibling) {
while( n->next ) n = n->next;
return n->next = sibling;
}
node* node_attach_child(node *n, node *child) {
child->up = n;
if( n->down ) return node_attach_sibling(n->down, child);
return n->down = child;
}
unsigned node_children(node *n) {
unsigned c = 0;
if( n->down ) {
n = n->down;
do ++c; while( (n = n->next) );
}
return c;
}
unsigned node_siblings(node *n) {
return n->up ? node_children( n->up->down ) : 0;
}
void node_init(node *n) {
profile_incstat("Editor.inits", +1);
if(n->next) node_init(n->next);
if(n->down) node_init(n->down);
}
void node_tick(node *n) {
profile_incstat("Editor.ticks", +1);
if(n->next) node_tick(n->next);
if(n->down) node_tick(n->down);
}
void node_draw(node *n) {
profile_incstat("Editor.draws", +1);
if(n->next) node_draw(n->next);
if(n->down) node_draw(n->down);
}
void node_quit(node *n) {
profile_incstat("Editor.quits", +1);
if(n->next) node_quit(n->next);
if(n->down) node_quit(n->down);
}
void node_edit(node *n, node *root) {
profile_incstat("Editor.edits", +1);
if( ui_collapse(va("%s %s (%u)", n->down ? ICON_MD_SOURCE : ICON_MD_FOLDER, node_name(n), node_children(n)), va("%p%p",root,n->v.ptr)) ) { // @fixme v.ptr
if( n->down ) node_edit(n->down,root);
if( reflect_has_fields( node_type(n), n->v.ptr ) ) {
for ui_push_hspace( 4 ) {
#define ICON_DOT ICON_CANCEL // ICON_MD_WIFI_1_BAR // ICON_MD_RADIO_BUTTON_UNCHECKED // ICON_MD_LENS_BLUR
static int flags[4] = {0};
char *toolbar = va("%s%s%s%s",
flags[3] ? ICON_MD_STAR : ICON_MD_STAR_OUTLINE, // ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER, // flags[3] == 0 ? ICON_MD_STAR_OUTLINE : flags[3] == 1 ? ICON_MD_STAR_HALF : ICON_MD_STAR,
flags[2] ? ICON_MD_CODE : ICON_DOT,
flags[1] ? ICON_MD_FLAG : ICON_DOT,
flags[0] ? ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF
);
ui_label_icon_highlight = *meta_changed(n); // @hack: remove ui_label_icon_highlight hack
char *section = va("*" ICON_MD_UNDO "%s", node_type(n));
int choice = ui_label2_toolbar(section, toolbar);
if( choice ) flags[ choice - 1 ] = (flags[ choice - 1 ] + 1 ) % ( choice == 4 ? 2/*3*/ : 2);
reflect_iterate_fields( node_type(n), n->v.ptr, reflect_ui, NULL ); // @fixme v.ptr
}
}
ui_collapse_end();
}
if(n->next) node_edit(n->next,root);
}
// ---
struct editor_t {
unsigned frame;
// root nodes
node init;
node tick;
node draw;
node edit;
node quit;
} editor;
enum { EDITOR_BUCKETS = 5 }; // init+tick+draw+edit+quit
void editor_reset() {
node_quit(&editor.quit);
editor.frame = 0;
}
void editor_frame() {
editor_init(); // old editor interface
editor_tick(); // old editor interface
editor_menubar(); // old editor interface
if( input_down(KEY_F5) ) {
editor_reset();
}
if( editor.frame++ == 0 ) {
node_init(&editor.init);
}
node_tick(&editor.tick);
node_draw(&editor.draw);
// content browser
if( ui_window("File Browser", 0) ) {
const char *file = 0;
if( ui_browse(&file, NULL) ) {
const char *sep = ifdef(win32, "\"", "'");
app_exec(va("%s %s%s%s", ifdef(win32, "start \"\"", ifdef(osx, "open", "xdg-open")), sep, file, sep));
}
ui_window_end();
}
// console/terminal window
if( 0 && ui_window("Console", 0) ) { // @fixme half-working
ui_console();
ui_window_end();
}
if( ui_window("Outliner", 0) ) {
#if 1
static char *filter = 0;
{
static int do_filter = 0;
int choice = ui_toolbar(ICON_MD_SEARCH ";" ICON_MD_REFRESH ";" ICON_MD_SD_CARD);
if( choice == 1 ) do_filter = 1;
if( do_filter ) {
ui_string(ICON_CANCEL " Filter " ICON_MD_SEARCH, &filter);
if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on CANCEL icon (1st icon)
do_filter = 0;
}
} else {
if( filter ) filter[0] = '\0';
}
char *filter_mask = filter && filter[0] ? va("*%s*", filter) : "*";
}
#endif
for( int c = ui_collapse(ICON_MD_FOLDER_SPECIAL " Art/", "ART"); c; ui_collapse_end(), c = 0) {
static const char *file;
static bool show_browser = 1;
if( ui_browse(&file, &show_browser) ) {
app_exec(va("%s %s", ifdef(win32, "start", ifdef(osx, "open", "xdg-open")), file));
//puts(file);
show_browser = 1;
}
}
for( int c = ui_collapse(ICON_MD_BOOKMARK " Bookmarks/", "BOOK"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_BUBBLE_CHART/*ICON_MD_SCATTER_PLOT*/ " Entities/", "ENT"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_TUNE " Components/", "COM"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_PRECISION_MANUFACTURING " Systems/", "SYS"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Hierarchy/", "ORD"); c; ui_collapse_end(), c = 0) {
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Init/", "ORD1"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Draw/", "ORD2"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Tick/", "ORD3"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Edit/", "ORD4"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Quit/", "ORD5"); c; ui_collapse_end(), c = 0) {}
// node_edit(&editor.init,&editor.init);
// node_edit(&editor.draw,&editor.draw);
// node_edit(&editor.tick,&editor.tick);
// node_edit(&editor.edit,&editor.edit);
// node_edit(&editor.quit,&editor.quit);
}
for( int c = ui_collapse(ICON_MD_PUBLIC " World/", "WORLD"); c; ui_collapse_end(), c = 0) {
node_edit(editor.edit.down,&editor.edit);
}
ui_window_end();
}
}
unsigned editor_spawn(const char *path_id, const char *keytype, void *val) {
do_once {
node_set_nametype(&editor.init, "Init Group", "init_nodes"); // @leak
node_set_nametype(&editor.tick, "Tick Group", "tick_nodes"); // @leak
node_set_nametype(&editor.draw, "Draw Group", "draw_nodes"); // @leak
node_set_nametype(&editor.edit, "Edit Group", "edit_nodes"); // @leak
node_set_nametype(&editor.quit, "Quit Group", "quit_nodes"); // @leak
}
array(char*) tokens = strsplit(path_id, "/");
char *keyname = *array_back(tokens);
array(node) n = 0;
array_resize(n, EDITOR_BUCKETS);
for( int i = 0; i < EDITOR_BUCKETS; ++i ) {
node_set_nametype(&n[i], keyname, keytype); // @leak
n[i].v.ptr = val;
}
array_pop(tokens);
char *joint = array_count(tokens) ? strjoin(tokens, "/") : "/";
node *p = 0;
p = node_find_recurse(&editor.init, joint), node_attach_child(p ? p : &editor.init, n+0);
p = node_find_recurse(&editor.tick, joint), node_attach_child(p ? p : &editor.tick, n+1);
p = node_find_recurse(&editor.draw, joint), node_attach_child(p ? p : &editor.draw, n+2);
p = node_find_recurse(&editor.edit, joint), node_attach_child(p ? p : &editor.edit, n+3);
p = node_find_recurse(&editor.quit, joint), node_attach_child(p ? p : &editor.quit, n+4);
return 0;
}
// demo -----------------------------------------------------------------------
typedef struct my_sprite {
char *filename;
vec3 position;
float tilt;
vec4 tint;
// --- private
texture_t texture_;
unsigned bgra_;
} my_sprite;
void my_sprite_ctor(my_sprite *obj) {
obj->texture_ = texture(obj->filename, TEXTURE_RGBA);
obj->bgra_ = bgraf( obj->tint.r/255.0, obj->tint.g/255.0, obj->tint.b/255.0, obj->tint.a/255.0 );
}
void my_sprite_draw(my_sprite *obj) {
obj->bgra_ = bgraf( obj->tint.r/255.0, obj->tint.g/255.0, obj->tint.b/255.0, obj->tint.a/255.0 ); // @fixme: del me
sprite( obj->texture_, &(obj->position.x), obj->tilt, obj->bgra_ );
}
int main() {
typedef char* string;
typedef vec4 color;
STRUCT( vec3, float, x, "X" );
STRUCT( vec3, float, y, "Y" );
STRUCT( vec3, float, z, "Z" );
// STRUCT( texture_t, unsigned, flags, "Flags");
// STRUCT( texture_t, string, filename, "Filename");
STRUCT( my_sprite, string, filename, "Filename" );
STRUCT( my_sprite, vec3, position, "Position" );
STRUCT( my_sprite, float, tilt, "Tilt degrees" );
STRUCT( my_sprite, color, tint, "Tint color" );
STRUCT_CTOR( my_sprite, my_sprite_ctor );
PRINTF("pod:%d, var:%d, node:%d warn\n", (int)sizeof(pod), (int)sizeof(var), (int)sizeof(node));
PRINTF("reflected:%d bytes vs real:%d bytes warn\n", reflect_sizeof("my_sprite"), (int)sizeof(my_sprite));
// cook_config("../../tools/cook.ini");
window_create(0.80, 0);
struct my_sprite spr1 = {0}, spr2 = {0}, spr3 = {0};
obj_make(&spr1,
"[my_sprite]\n"
"filename=cat.png\n"
"position=5 2 100\n"
"tilt=45 + 45 -90\n"
"tint=255 255 0\n"
);
obj_make(&spr2,
"[my_sprite]\n"
"filename=cat.png\n"
"position=1 2 100\n"
"tilt=45 + 45 -90\n"
"tint=255 0 0\n"
);
obj_make(&spr3,
"[my_sprite]\n"
"filename=cat.png\n"
"position=1 2 100\n"
"tilt=45\n"
"tint=0 0 255\n"
);
int hero1 = editor_spawn("/hero1", "my_sprite", &spr1);
int hero2 = editor_spawn("/hero2", "my_sprite", &spr2);
int hero3 = editor_spawn("/hero1/heroB", "my_sprite", &spr3);
camera_t cam = camera();
camera_enable(&cam);
while( window_swap() ) {
editor_frame();
// @fixme: this should be drawn by game, not editor!
my_sprite_draw(&spr1);
my_sprite_draw(&spr2);
my_sprite_draw(&spr3);
// spr1.tilt = 5 * sin(time_ss());
}
}

View File

@ -0,0 +1,343 @@
#define EDITOR_VERSION "2022.7"
#if 1
#define EDITOR_PRINTF PRINTF
#else
#define EDITOR_PRINTF(...) do {} while(0)
#endif
#define ICON_PLAY ICON_MD_PLAY_ARROW
#define ICON_PAUSE ICON_MD_PAUSE
#define ICON_STOP ICON_MD_STOP
#define ICON_CANCEL ICON_MD_CLOSE
#define ICON_WARNING ICON_MD_WARNING
#define ICON_BROWSER ICON_MD_FOLDER_SPECIAL
#define ICON_OUTLINER ICON_MD_VIEW_IN_AR
#define ICON_BUILD ICON_MD_BUILD
#define ICON_SCREENSHOT ICON_MD_PHOTO_CAMERA
#define ICON_CAMERA_ON ICON_MD_VIDEOCAM
#define ICON_CAMERA_OFF ICON_MD_VIDEOCAM_OFF
#define ICON_GAMEPAD_ON ICON_MD_VIDEOGAME_ASSET
#define ICON_GAMEPAD_OFF ICON_MD_VIDEOGAME_ASSET_OFF
#define ICON_AUDIO_ON ICON_MD_VOLUME_UP
#define ICON_AUDIO_OFF ICON_MD_VOLUME_OFF
#define ICON_WINDOWED ICON_MD_FULLSCREEN_EXIT
#define ICON_FULLSCREEN ICON_MD_FULLSCREEN
#define ICON_LIGHTS_ON ICON_MD_LIGHTBULB
#define ICON_LIGHTS_OFF ICON_MD_LIGHTBULB_OUTLINE
#define ICON_RENDER_BASIC ICON_MD_IMAGE_SEARCH
#define ICON_RENDER_FULL ICON_MD_INSERT_PHOTO
#define ICON_SIGNAL ICON_MD_SIGNAL_CELLULAR_ALT
#define ICON_DISK ICON_MD_STORAGE
#define ICON_RATE ICON_MD_SPEED
#define ICON_CLOCK ICON_MD_TODAY
#define ICON_CHRONO ICON_MD_TIMELAPSE
#define ICON_SETTINGS ICON_MD_SETTINGS
#define ICON_LANGUAGE ICON_MD_G_TRANSLATE
#define ICON_PERSONA ICON_MD_FACE
#define ICON_SOCIAL ICON_MD_MESSAGE
#define ICON_GAME ICON_MD_ROCKET_LAUNCH
#define ICON_KEYBOARD ICON_MD_KEYBOARD
#define ICON_MOUSE ICON_MD_MOUSE
#define ICON_GAMEPAD ICON_MD_GAMEPAD
#define ICON_MONITOR ICON_MD_MONITOR
#define ICON_WIFI ICON_MD_WIFI
#define ICON_BUDGET ICON_MD_SAVINGS
#define ICON_NEW_FOLDER ICON_MD_CREATE_NEW_FOLDER
#define ICON_PLUGIN ICON_MD_EXTENSION
#define ICON_RESTART ICON_MD_REPLAY
#define ICON_QUIT ICON_MD_CLOSE
#define ICON_POWER ICON_MD_BOLT // ICON_MD_POWER
#define ICON_BATTERY_CHARGING ICON_MD_BATTERY_CHARGING_FULL
#define ICON_BATTERY_LEVELS \
ICON_MD_BATTERY_ALERT, \
ICON_MD_BATTERY_0_BAR,ICON_MD_BATTERY_1_BAR, \
ICON_MD_BATTERY_2_BAR,ICON_MD_BATTERY_3_BAR, \
ICON_MD_BATTERY_4_BAR,ICON_MD_BATTERY_5_BAR, \
ICON_MD_BATTERY_6_BAR,ICON_MD_BATTERY_FULL
// state - retained mode
typedef int property; // @fixme
typedef struct editor_state_t {
array(property) properties;
array(char) buffer;
array(vec2i) history;
unsigned cursor;
} editor_state_t;
typedef map(char*, char*) editor_dict_t;
static map(void*, editor_state_t) editor_state; // world
static map(void*, editor_dict_t) editor_dicts;
static set(void*) editor_world;
static set(void*) editor_selection; // objects selected in scene
// editor controls
//static int editor_attached = 1;
static int editor_enabled = 1;
static void* editor_selected_obj = 0;
static int editor_key = 0;
static vec2 editor_mouse = {0}; // 2d coord for ray/picking
static bool editor_gamepad = 1;
static int editor_hz = 60;
static int editor_hz_mid = 18;
static int editor_hz_low = 5;
static bool editor_power_saving = 0;
static double editor_t = 0, editor_dt = 0;
static bool editor_lit = 1;
static bool editor_ddraw = 1;
static
void editor_init() {
do_once {
map_init_ptr(editor_state);
map_init_ptr(editor_dicts);
set_init_ptr(editor_world);
set_init_ptr(editor_selection);
profile_enable( false );
window_pause( true );
}
}
void editor_tick() {
// timing
editor_dt = window_delta() * !window_has_pause(); if(editor_dt > 1/60.f) editor_dt = 1/60.f;
}
bool editor_active() {
return ui_hover() || ui_active() || gizmo_active() ? editor_enabled : 0;
}
double editor_ss() {
return 1000 + editor_t;
}
double editor_delta() {
return editor_dt;
}
void editor_select_all() {}
void editor_select_none() {}
enum editor_keys {
key_none,
key_pause,
key_reload,
key_browser,
key_recording,
key_fullscreen,
key_screenshot, // @todo: add meta-info in exif or invisibile pixels (cam details, player details, map level, map location, level state, etc)
key_quit,
key_mute,
key_battery,
key_profiler,
key_stop,
key_outliner,
key_undo,
key_redo,
key_save_mem,
key_save_disk,
key_load_disk,
key_reset,
key_debugger,
key_gamepad,
key_lit,
key_ddraw,
};
void editor_menubar() {
do_once editor_init();
int alts = input(KEY_LALT) || input(KEY_RALT); // @todo: move to fwk.c
int ctrls = input(KEY_LCTRL) || input(KEY_RCTRL); // @todo: move to fwk.c
int shifts = input(KEY_LSHIFT) || input(KEY_RSHIFT); // @todo: move to fwk.c
int mods = alts || ctrls || shifts; // @todo: move to fwk.c
if( input_down(KEY_F5) ) editor_key = key_reload;
if( input_down(KEY_F11) ) editor_key = key_fullscreen;
if( input_down(KEY_PAUSE) ) editor_key = key_pause;
if( input_down(KEY_PRINT) ) editor_key = (mods ? key_recording : key_screenshot);
// if( input_down(KEY_W) && input_held(KEY_LCTRL) ) editor_key = key_quit;
if( ctrls ) {
/**/ if( input_down(KEY_Z) ) editor_key = key_undo;
else if( input_down(KEY_Y) ) editor_key = key_redo;
else if( input_down(KEY_S) ) editor_key = key_save_disk;
else if( input_down(KEY_A) ) editor_select_all();
else if( input_down(KEY_D) ) editor_select_none();
}
if( !editor_key && editor_selected_obj ) {
if( input_up(MOUSE_L) ) editor_key = key_save_mem;
if( input_down(MOUSE_R) ) ui_show("Properties", true);
}
// @fixme: send all editor keys to game?
// if( input_repeat(KEY_ESC, 300)) {}
// if( input_repeat(KEY_F1, 300)) {}
// etc...
// menubar
if( ui_menu( ICON_SETTINGS "@Preferences;"
ICON_LANGUAGE " Language;"
ICON_PERSONA " Profile;" // editor account, but also fake profile and 1st party credentials
ICON_SOCIAL " Social;"
ICON_GAME " Game;" //
ICON_KEYBOARD " Keyboard;"
ICON_MOUSE " Mouse;"
ICON_GAMEPAD " Gamepad;"
ICON_MONITOR " Display;" // @todo: RENDER settings, AUDIO settings
ICON_WIFI " Network;"
ICON_BUDGET " Budget;" // mem,gfx,net,hdd,... also logging
ICON_NEW_FOLDER " Folders;" // including project folders
ICON_PLUGIN " Plugins;" // including VCS
ICON_RESTART " Restart;"
ICON_QUIT " Quit;"
"-" ICON_MD_RECYCLING " Reset all preferences;" ICON_MD_SAVE_AS " Save all preferences"
) ) {
if( ui_item() == 3 ) {} // key mappings
if( ui_item() == 4 ) {} // sensitivity, invert xylrw
if( ui_item() == 5 ) {} // sensitivity, invert xy,ab, deadzones
if( ui_item() == 7 ) {} // name,email,icon,link,github
if( ui_item() == 13) editor_key = key_reload;
if( ui_item() == 14) editor_key = key_quit;
}
static char game_args[16] = "--game-args"; // @fixme @todo remove '_' special char to signal that ui_menu() is writeable (inputbox)
if( ui_menu_editbox( game_args, 16 ) ) {}
if( ui_menu( ICON_CANCEL "@Cancel" ) ) {}
if( ui_menu( window_has_pause() ? ICON_PLAY "@Tap to Play Game" : ICON_PAUSE "@Tap to Pause Game" )) editor_key = key_pause;
if( ui_menu( ICON_MD_SKIP_NEXT "@Next frame") ) {}
if( ui_menu( ICON_MD_FAST_FORWARD "@Fast forward") ) {}
//if( ui_menu( ICON_STOP "@Stop game" )) editor_key = key_stop;
if( ui_menu( va(ICON_BUILD "@Build game"))) ui_notify("Build", ICON_WARNING " Not implemented.");
if( ui_menu( va(ICON_MD_ROCKET_LAUNCH "@Launch game"))) ui_notify("Launch", ICON_WARNING " Not implemented.");
// ICON_MD_TROUBLESHOOT -> PROFILER
// ICON_MD_SCHEMA -> GRAPHNODES
// ICON_MD_ACCOUNT_TREE -> GRAPHNODES
// ICON_MD_TIPS_AND_UPDATES -> BULB
// if( ui_menu( ICON_MD_MENU )) {}
// if( ui_menu( ICON_BROWSER "@Content browser" )) editor_key = key_browser;
// if( ui_menu( va(ICON_OUTLINER " %d/%d@World outliner", set_count(editor_selection), map_count(editor_state) ))) editor_key = key_outliner;
if( ui_menu( ICON_SCREENSHOT "@Take Screenshot" )) editor_key = key_screenshot; // MD_SCREENSHOT_MONITOR
if( ui_menu( record_active() ? ICON_CAMERA_OFF "@Stop video recording" : ICON_CAMERA_ON "@Start video recording" )) { if(record_active()) record_stop(); else editor_key = key_recording; }
if( ui_menu( editor_gamepad ? ICON_GAMEPAD_ON "@Gamepad is enabled. Tap to disable." : ICON_GAMEPAD_OFF "@Gamepad is disabled. Tap to enable." )) editor_key = key_gamepad;
if( ui_menu( audio_volume_master(-1) > 0 ? ICON_AUDIO_ON "@Audio is enabled. Tap to mute." : ICON_AUDIO_OFF "@Audio is muted. Tap to enable." )) editor_key = key_mute;
if( ui_menu( window_has_fullscreen() ? ICON_WINDOWED "@Fullscreen. Tap to go Windowed." : ICON_FULLSCREEN "@Windowed. Tap to go Fullscreen." )) editor_key = key_fullscreen;
if( ui_menu( editor_ddraw ? ICON_RENDER_BASIC "@Debug renderer. Tap to go Retail Renderer." : ICON_RENDER_FULL "@Retail renderer. Tap to go Debug Renderer." )) editor_key = key_ddraw; // ICON_MD_ADD_PHOTO_ALTERNATE
if( ui_menu( editor_lit ? ICON_LIGHTS_ON "@Lit. Tap to disable lighting." : ICON_LIGHTS_OFF "@Unlit. Tap to enable lighting." )) editor_key = key_lit;
// logic: either plug icon (power saving off) or one of the following ones (power saving on):
// if 0% batt (no batt): battery alert
// if discharging: battery levels [alert,0..6,full]
// if charging: battery charging
int battery_read = app_battery();
int battery_level = abs(battery_read);
int battery_discharging = battery_read < 0 && battery_level < 100;
const char *battery_levels[] = { // @todo: remap [7%..100%] -> [0..1] ?
ICON_BATTERY_LEVELS
};
if( ui_menu( !editor_power_saving ? ICON_POWER"@Full power. Tap to save power." :
va("%s@Power saving. Tap to go full power. %3d%% battery.",
battery_read == 0 ? battery_levels[0] :
battery_discharging ? battery_levels[(int)((countof(battery_levels)-1)*clampf(battery_level/100.f,0,1))] :
ICON_BATTERY_CHARGING, battery_level) ))
editor_key = key_battery;
// @todo: combine-in-1? cycle mem -> cpu/profiler -> network mon -> debugger
// bug report, signal status, disk status, framerate status
if( ui_menu(va(ICON_SIGNAL " 0/0KiB" ))) {} // SIGNAL_CELLULAR_1_BAR SIGNAL_CELLULAR_2_BAR
if( ui_menu(va(ICON_DISK " %s", xstats() ))) {} // 012/136MB
if( ui_menu(va(ICON_RATE " %5.2f/%d", window_fps(), (int)window_fps_target()))) editor_key = key_profiler; // 012/136MB
// bug report, profile, warnings, time/chrono (@todo: alarm/snooze? calendar?)
if( ui_menu( ICON_MD_BUG_REPORT /*"^"*/ "0" ) ) {}
if( ui_menu( ICON_MD_FACE /*"^"*/ "3" ) ) {} // @todo: do both messaging/warnings + profile settings here
{
static double base = 0, tap = 0;
if( tap == 0 ) tap = time_ss();
double delta = time_ss() - tap;
tap = time_ss();
base += delta * !window_has_pause();
if( ui_menu( base == 0 ?
va(ICON_CLOCK "%02d:%02d", (int)((date() / 10000) % 100), (int)((date() / 100) % 100))
:
va(ICON_CHRONO "%03dm:%02ds:%02df@Tap to reset chrono.",((int)(base/60))%60,((int)base)%60,(int)((base - (int)base)*window_fps_target())))
|| editor_key == key_stop
) {
base = 0, tap = 0;
}
}
for each_map_ptr(editor_state, void *, o, editor_state_t, ed) {
profile_incstat("Editor.num_objects", +1);
void *obj = *o;
#if 1
#elif 0
// auto-load from disk during init. @fixme kvdb database
if( array_count(ed->history) == 0 )
if( editor_load_disk(obj, editor_obj_string(obj, ".path")) )
{}
// auto-save in-mem during first edit
if( array_count(ed->history) == 0 )
editor_save_mem(obj);
#endif
// @todo: continue if obj not found in selection set
if( obj != editor_selected_obj )
continue;
if( editor_key == key_debugger ) { breakpoint("User requested breakpoint on this object"); }
#if 1
#elif 0
if( editor_key == key_reset ) { const char *ok = editor_reset(obj) ? "ok" : "err"; EDITOR_PRINTF("reset: %s\n", ok); }
if( editor_key == key_save_mem ) { const char *ok = editor_save_mem(obj) ? "ok" : "err"; EDITOR_PRINTF("mem saved: %s\n", ok); }
if( editor_key == key_undo ) { const char *ok = editor_undo(obj) ? "ok" : "err"; EDITOR_PRINTF("undo: %s\n", ok); }
if( editor_key == key_redo ) { const char *ok = editor_redo(obj) ? "ok" : "err"; EDITOR_PRINTF("redo: %s\n", ok); }
if( editor_key == key_save_disk ) { const char *ok = editor_save_disk(obj, editor_obj_string(obj, ".path")) ? "ok" : "err"; EDITOR_PRINTF("save: %s\n", ok); }
if( editor_key == key_load_disk ) { const char *ok = editor_load_disk(obj, editor_obj_string(obj, ".path")) ? "ok" : "err"; EDITOR_PRINTF("load: %s\n", ok); }
#endif
}
char *name;
switch( editor_key ) {
default:
break; case key_quit: record_stop(), exit(0);
break; case key_stop: window_pause(1);
break; case key_mute: audio_volume_master( 1 ^ !!audio_volume_master(-1) );
break; case key_pause: window_pause( window_has_pause() ^ 1 );
break; case key_reload: window_reload();
break; case key_battery: editor_power_saving ^= 1;
break; case key_browser: ui_show("File Browser", ui_visible("File Browser") ^ true);
break; case key_outliner: ui_show("Outliner", ui_visible("Outliner") ^ true);
break; case key_recording: name = file_counter(va("%s.mp4",app_name())), window_record(name), ui_notify(va("Video capturing: %s", name), date_string());
break; case key_screenshot: name = file_counter(va("%s.png",app_name())), window_screenshot(name), ui_notify(va("Screenshot: %s", name), date_string());
break; case key_profiler: ui_show("Profiler", profile_enable(ui_visible("Profiler") ^ true));
break; case key_fullscreen: record_stop(), window_fullscreen( window_has_fullscreen() ^ 1 ); // framebuffer resizing corrupts video stream, so stop any recording beforehand
break; case key_gamepad: editor_gamepad ^= 1;
break; case key_lit: editor_lit ^= 1;
break; case key_ddraw: editor_ddraw ^= 1;
}
editor_key = 0;
}

View File

@ -0,0 +1,328 @@
// @todo fsave: fputs(DNA), then fwrite
// @todo fread: fgets(DNA), abort if DNA != read; then fread
#include <stdio.h>
// load/save whole struct acording to its DNA structure
int fload(FILE *infile, void *structure, const char *dna);
int fsave(FILE *outfile, const void *structure, const char *dna);
// set alignment for next fload/fsave call. resets back to 0 after every fload/fsave call.
// value: 0 (auto, default), 1,2,4,8,16 bytes [...]
void falign(unsigned alignment);
// override default DNA handlers and switch to a different schema and/or serialization format
typedef int (*size_operator)(char fmt, void *addr, int readmode);
typedef int (*call_operator)(void *out, void *addr, char fmt, int count, int readmode);
void foverride(size_operator size, call_operator call);
#if 0
Example Usage
-------------
struct fat_bootsector {
uint8_t jump_instruction[3];
uint8_t oem_name[8];
uint16_t bytes_per_sector;
uint8_t sectors_per_cluster;
uint16_t reserved_sectors;
uint8_t fat_copies;
uint16_t max_dirs;
uint16_t sector_count;
uint8_t media_descriptor;
uint16_t sectors_per_fat;
uint16_t sectors_per_head;
uint16_t heads;
uint32_t hidden_sectors;
uint32_t sector_count2;
} fat_struct;
Now we can read a binary image of the MBR into this structure:
mreadf(mbr, "i3c8chchchhchhhdd", &fat_struct);
Directives
----------
Supported directives:
regex meaning
(blank) ignored
b read / write uint8_t
w read / write uint16_t (uppercase: use vli encoding; as uint8? )
u read / write uint32_t (uppercase: use vli encoding; as uint8,16? )
q read / write uint64_t (uppercase: use vli encoding; as uint8,16,32?)
m read / write micro
h read / write half (uppercase: use smallest representation; as micro?)
f read / write float (uppercase: use smallest representation; as micro,half?)
d read / write double (uppercase: use smallest representation; as micro,half,float?)
s read / write string (uppercase: use smallest representation; quarks?)
[] read / write buffer
< switch to Intel (little endian) byte order
> switch to Motorola (big endian) byte order
( begin of tight group
) end of tight group
[0-9]+ next item repeated n times
z skip one byte of input / emit \0
* consume next structure item but do not read / write
#endif
// ----------------------------------------------------------------------------
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifndef __thread
#define __thread __declspec(thread)
#endif
static __thread int pragma_pack_alignment = 0;
void falign(unsigned alignment) {
pragma_pack_alignment = alignment;
}
static
int size_fn(char fmt, uint8_t* addr8, int rd) {
if(addr8) {
// sizeof pointee data & align operator
/**/ if(fmt == 'c' || fmt == 'b') return 1;
else if(fmt == 'w' ) return 2;
else if(fmt == 'i' || fmt == 'u') return 4;
else if(fmt == 'l' || fmt == 'q') return 8;
else if(fmt == 'f' ) return 4;
else if(fmt == 'd' ) return 8;
else if(fmt == 's' ) return !*(char**)addr8 ? 0+1 : !rd ? strlen(*(char**)addr8) + 1 : strcspn(*(char**)addr8,"\x1") + 1;
return -1;
} else {
// sizeof member
/**/ if(fmt == 'w' ) return 2;
else if(fmt == 'i' || fmt == 'u') return 4;
else if(fmt == 'l' || fmt == 'q') return 8;
else if(fmt == 'f' ) return 4;
else if(fmt == 'd' ) return 8;
else if(fmt == 's' ) return sizeof(void*);
return 1;
}
}
static
int call_fn(void *out, uint8_t *addr8, char fmt, int count, int rd) { // rd/wr operator
FILE *fp = (FILE*)out;
int iterated_bytes = 0, processed_bytes = 0;
while( --count >= 0 ) {
int slot = size_fn(fmt, 0, rd);
int size = size_fn(fmt, addr8, rd);
if(rd)
switch (fmt) {
default: return -1;
break; case 'c': case 'b': fscanf(fp, "%c,", (uint8_t*)addr8);
break; case 'w': fscanf(fp, "%#04llx,", (uint16_t*)addr8);
break; case 'i': case 'u': fscanf(fp, "%#08llx,", (uint32_t*)addr8);
break; case 'l': case 'q': fscanf(fp, "%#16llx,", (uint64_t*)addr8);
break; case 'f': fscanf(fp, "%f,", (float*)addr8);
break; case 'd': fscanf(fp, "%llf,", (double*)addr8);
break; case 's': fscanf(fp, "%[^\x1],", (char*)addr8);
}
else
switch(fmt) {
default: return -1;
break; case 'c': case 'b': fprintf(fp, "%c,", (int)*(uint8_t*)addr8);
break; case 'w': fprintf(fp, "%#04llx,", (uint64_t)*(uint16_t*)addr8);
break; case 'i': case 'u': fprintf(fp, "%#08llx,", (uint64_t)*(uint32_t*)addr8);
break; case 'l': case 'q': fprintf(fp, "%#16llx,", (uint64_t)*(uint64_t*)addr8);
break; case 'f': fprintf(fp, "%f,", (float)*(float*)addr8);
break; case 'd': fprintf(fp, "%f,", (double)*(double*)addr8);
break; case 's': fprintf(fp, "%s\x1,", *(char**)addr8);
}
addr8 += slot;
iterated_bytes += slot;
processed_bytes += size;
}
return iterated_bytes;
}
static size_operator fsize_fn = size_fn;
static call_operator fcall_fn = call_fn;
void foverride(size_operator size, call_operator call) {
fsize_fn = size;
fcall_fn = call;
}
int fdump(FILE *out, void *addr, const char *dna, int rd) {
unsigned pragma_pack = pragma_pack_alignment; pragma_pack_alignment = 0; // reset alignment
uint8_t *addr8 = (uint8_t*)addr;
int size = 0, count = 1, skip = 0;
int last_type = 0;
int written = 0;
int align = 0;
for( int i = 0; dna[i]; ++i) {
char fmt = dna[i];
/**/ if(fmt <= 32) continue;
else if(fmt >= '0' && fmt <= '9') continue;
else if(fmt == 'z') skip = 1;
else {
// member alignment
if( last_type != fmt ) { // check if next struct member was found (may need re-alignment)
if( pragma_pack != 1 ) { // forced (>1) or auto-alignment (0)?
//printf("*%p ->", addr8);
align = pragma_pack == 0 ? fsize_fn(fmt, 0, rd) : pragma_pack;
// Round up to N-byte boundary
addr8 = (uint8_t*)(((uintptr_t)(addr8) + ((align) - 1)) & -(align));
//printf(" %p\n", addr8);
}
}
last_type = fmt;
size = fsize_fn(fmt, addr8, rd);
if( size < 0 ) {
fprintf(stderr, "parse error, unknown dna sequence '%c'!\n", fmt);
return -i-1;
}
}
if( skip ) { skip = 0; continue; }
char next = dna[i+1];
if( next >= '0' && next <= '9' ) {
count = next - '0';
}
int bytes = skip || count == 0 ? 0 : call_fn(out, addr8, dna[i], count, rd);
if( bytes < 0 ) {
fprintf(stderr, "stream fail. rc: %d\n", bytes);
return bytes;
}
written += bytes;
addr8 += bytes;
count = 1;
fprintf(out, "\n");
}
return written;
}
int fsave(FILE *out, const void *structure, const char *dna) {
return fdump(out, (void*)structure, dna, 0);
}
int fload(FILE *inf, void *structure, const char *dna) {
return fdump(inf, structure, dna, 1);
}
int fsavefile(const char *outfile, const void *structure, const char *dna) {
FILE *fp = fopen(outfile, "wb");
if( !fp ) return 0;
int bytes = fdump(fp, (void*)structure, dna, 0);
fclose(fp);
return bytes;
}
int floadfile(const char *infile, void *structure, const char *dna) {
FILE *fp = fopen(infile, "rb");
if( !fp ) return 0;
int bytes = fdump(fp, (void*)structure, dna, 1);
fclose(fp);
return bytes;
}
// ---
#include <assert.h>
// #pragma pack(1)
struct fat_mbr {
uint8_t jmp[3]; // b3
uint8_t oem[8]; // b8
const char* str; // s
uint16_t bytes_per_sector; // w
uint8_t sectors_per_cluster; // b
uint16_t reserved_sectors; // w
uint8_t fat_copies; // b
uint16_t max_dirs; // w
uint16_t sector_count; // w
uint8_t media_descriptor; // b
uint16_t sectors_per_fat; // w
uint16_t sectors_per_head; // w
uint16_t heads; // w
uint32_t hidden_sectors; // u
uint32_t sector_count2; // u
float pi;
char break_alignment; // b
double phi;
};
#define FAT_MBR_DNA "b3b8s wb wb w wb www uu fbd"
// #pragma pack(pop)
int main() {
// foverride(size_fn, write_fn);
struct fat_mbr mbr = {
{'a','b','c'},
{'d','e','f','g','h','i','j','k'},
"hello 'escaped' \"world\"",
0x100,'l',
0x101,'m',
0x102,
0x103,'n',
0x104,
0x105,
0x106,
0x01234567,
0x89abcdef,
3.14159f,'o',
1.6069,
};
// fdump(stdout, &mbr, FAT_MBR_DNA); exit(0);
// printf("%p\n", &mbr.jmp);
// printf("%p\n", &mbr.oem);
// printf("%p\n", &mbr.str);
// printf("%p\n", &mbr.sector_count);
// falign(0);
int bytes = fsave(stdout, &mbr, FAT_MBR_DNA);
printf("%d bytes written\n", bytes);
typedef struct entitystate {
struct pos {
short trTime;
float trBase[3];
} pos;
} entitystate;
entitystate e = { 123, 3.14159f, 4.14159f, 5.14159f };
bytes = fsave(stdout, &e, "wfff");
printf("%d bytes written\n", bytes);
// exit(0);
struct fat_mbr zero = {0};
struct fat_mbr src = mbr;
struct fat_mbr dst = zero;
dst = src;
assert(0 == memcmp(&src,&dst,sizeof(struct fat_mbr)));
dst = zero;
assert(0 != memcmp(&src,&dst,sizeof(struct fat_mbr)));
int sbytes = fsavefile(".temp", &src, FAT_MBR_DNA);
int lbytes = floadfile(".temp", &dst, FAT_MBR_DNA);
assert( sbytes == lbytes );
assert(0 != memcmp(&src,&dst,sizeof(struct fat_mbr)));
assert(!puts("Ok"));
}

View File

@ -0,0 +1,292 @@
//#define META_DEMO
#include "fwk.h"
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdio.h>
static
char *reformat(const char *text, const char *blacklist, const char *separator) {
char **list = strsplit(text, blacklist);
char *joint = strjoin(list, separator);
return joint;
}
const char *c_symbols = "{},>&/+=;:"; // "(){}[].,><&*/+=;:!~";
const char *c_symbols_extra = "{},>&/+=;:[]()"; // "(){}[].,><&*/+=;:!~";
const char *c_keywords[] = { "const","long","signed","unsigned","typedef" }; int num_keywords = 4;
typedef struct meta {
const char *name;
const char *tags;
} meta;
meta metas[2048] = {
/*00*/ {"typedef", ""},
/*01*/ {"enum", ""},
/*02*/ {"struct", ""},
/*03*/ {"function", ""},
/*04*/ {"bool", "default 0"},
/*05*/ {"false", "default 0 bits 1"},
/*06*/ {"true", "default 1 bits 1"},
/*07*/ {"char", "default 0 bits 8"},
/*08*/ {"int", "default 0"},
/*09*/ {"float", "default 0 bits 32"},
/*10*/ {"double", "default 0 bits 64"},
/*11*/ {"int8_t", "default 0 bits 8"},
/*12*/ {"int16_t", "default 0 bits 16"},
/*13*/ {"int32_t", "default 0 bits 32"},
/*14*/ {"int64_t", "default 0 bits 64"},
/*15*/ {"uint8_t", "default 0 bits 8 signed 0"},
/*16*/ {"uint16_t", "default 0 bits 16 signed 0"},
/*17*/ {"uint32_t", "default 0 bits 32 signed 0"},
/*18*/ {"uint64_t", "default 0 bits 64 signed 0"},
};
int meta_count = 19;
const char *meta_last_name = 0;
const char *meta_last_struct = 0;
meta* meta_find(const char *name) {
for( int i = 0; i < meta_count; ++i ) {
if( 0 == strcmp(name, metas[i].name) ) return &metas[i];
}
return 0;
}
meta* meta_add(const char *name, const char *subtype, const char *tags ) {
{ meta *t = meta_find(name); if( t ) return t; }
if( meta_count >= 2048 ) return 0;
meta t = { STRDUP(name), reformat(STRDUP(tags), " ", " ") };
metas[meta_count] = t;
meta_last_struct = !strcmp(subtype, "struct") ? name : meta_last_struct;
meta_last_name = !strcmp(subtype, "struct") || !strcmp(subtype, "enum") || !strcmp(subtype, "typedef") ? name : subtype;
return &metas[meta_count++];
}
char* meta_find_value(const char *name, const char *prop) {
meta *t = meta_find(name);
if( !t ) return 0;
char *found = strstr(t->tags, prop);
if( !found ) return 0;
static char copy[256] = {0}; // @fixme
strcpy(copy, found + strlen(prop) + 1);
for(int i=0; copy[i]; ++i) if(copy[i]==' ') { copy[i] = 0; break; }
return copy;
}
void meta_inspectf(FILE *fp) {
if( fp ) {
char buf[255+1];
while(fgets(buf, 255, fp)) {
char *mark = strstr(buf, "//""M");
if( !mark ) continue;
char *tags = mark + 3;
// must reset last_struct
if( strstr(buf, "typedef") ) {
meta_last_struct = 0; // typedef is a special case
}
// remove symbols
for( int i = 0; buf[i]; ++i ) {
// symbols off
if(strchr(&buf[i] < tags ? c_symbols : c_symbols_extra, buf[i])) { buf[i] = ' '; continue; }
}
// remove leading whitespaces
char *spc = buf;
while( spc && spc[0] && spc[0] <= 32 ) ++spc;
// remove trailing linefeeds and whitespaces
int len = strlen(buf);
while( len > 0 && buf[len-1] <= 32 ) buf[--len] = 0;
// split buffer into left|right buffers
char *left = spc, *right = tags; right[-1] = 0;
// remove left keywords
for( int i = 0; left[i]; ++i ) {
for(int j = 0; j < sizeof(c_keywords)/sizeof(0[c_keywords]); ++j) {
int kb = strlen(left+i);
int kl = strlen(c_keywords[j]);
if( kl <= kb && !memcmp(left+i, c_keywords[j], kl) ) {
memset(left+i, ' ', kl);
i += kl;
break;
}
}
}
// debug
// printf("%s <--> %s\n", left, right);
// add type
const char *meta_name = 0;
const char *meta_t = 0;
const char *meta_val = "";
array(char *) tokens = strsplit(left, " \t");
if(1) for( int i = 0, end = array_count(tokens); i < end; ++i ) {
/**/ if( strstr(tokens[i],"enum") ) meta_last_name = 0, meta_last_struct = 0, meta_t = "enum";
else if( strstr(tokens[i],"struct") ) meta_last_name = 0, meta_last_struct = 0, meta_t = "struct";
else if( strstr(tokens[i],"typedef") ) meta_last_name = 0, meta_last_struct = 0, meta_t = "typedef"; // trimmed. never happens
else if( strchr(tokens[i],'(') ) meta_last_name = 0, meta_last_struct = 0, meta_name = tokens[i], meta_t = "function";
else if( !meta_name ) meta_name = tokens[i];
else if( !meta_t ) meta_t = tokens[i];
}
if( meta_find(meta_name) ) {
const char *swap = meta_name;
meta_name = meta_t;
meta_t = swap;
}
// if !meta_find defer(meta_eval) && emit(warning) && keep(iterating);
bool is_pointer = strstr(meta_name, "*");
meta_name = (char *)reformat(meta_name, "*", "");
char *found_sqr = strstr(meta_name, "[");
int array_len = found_sqr ? atoi(found_sqr+1) : 0;
if( found_sqr ) *found_sqr = 0;
bool is_number = false;
char conv[32] = {0};
double dbl = atof(meta_t);
if(!is_number) { sprintf(conv, "%f", dbl); is_number = !strcmp(conv, meta_t); }
if(!is_number) { sprintf(conv, "%.f", dbl); is_number = !strcmp(conv, meta_t); }
if(is_number) {
meta_val = meta_t;
meta_t = meta_last_name;
}
char hints[256] = {0}, *p = hints;
if( 1 ) p += sprintf(p, "type %s ", meta_t);
if( is_pointer || array_len ) p += sprintf(p, "pointer %d ", 1);
if( array_len ) p += sprintf(p, "count %d ", array_len);
if( 1 ) p += sprintf(p, "%s ", tags);
if( meta_last_struct ) p += sprintf(p, "struct %s ", meta_last_struct);
if( meta_val[0] ) p += sprintf(p, "default %s ", meta_val);
meta_val = hints;
// inherited tags (from super type) at the very end, so overriden tags can be found at first place
if( meta_find(meta_t) )
p += sprintf(p, "%s ", meta_find(meta_t)->tags );
if( !meta_find(meta_name) ) {
meta_add(meta_name, meta_t, meta_val);
meta *t = meta_find(meta_name);
//printf("%s,%s\n", t->name, t->tags);
}
}
// clean state
meta_last_name = 0;
meta_last_struct = 0;
}
}
bool meta_save(FILE *fp) {
if( fp )
for( int i = 0; i < meta_count; ++i ) {
meta *t = &metas[i];
fprintf(fp, "%s %s\n", t->name, t->tags);
}
return !!fp;
}
bool meta_load(FILE *fp) {
if( fp ) {
meta_count = 0;
meta_last_name = 0;
meta_last_struct = 0;
char buf[2048+1];
while(fgets(buf, 2048, fp)) {
int bl = strlen(buf); while( bl && buf[bl] <= 32) buf[bl--] = 0;
char id[128];
sscanf(buf, "%s", id);
metas[ meta_count ].name = strdup(id);
metas[ meta_count ].tags = strdup(buf+strlen(id)+1);
++meta_count;
}
}
return !!fp;
}
void meta_inspect(const char *name) {
FILE *fp = fopen(name, "rb");
if( !fp ) return;
meta_inspectf(fp);
fclose(fp);
}
bool meta_savefile(const char *name) {
FILE *fp = fopen(name, "wb");
if( !fp ) return false;
meta_save(fp); fclose(fp);
return true;
}
bool meta_loadfile(const char *name) {
FILE *fp = fopen(name, "rb");
if( !fp ) return false;
meta_load(fp); fclose(fp);
return true;
}
#ifdef META_DEMO
#pragma once
typedef int my_array[8]; //M
typedef const char *string; //M
typedef enum Fruit { //M bits:8
Banana = -1, //M
Orange = 42 //M
} Fruit;
struct myObject { //M version:100
Fruit meal; //M default:Banana
int32_t hitpoints; //M default:100 zigzag:yes
float shininess; //M default:+3.5e2 min:0 max:1 fixed:yes
string text; //M default:"DEMO"
bool visible; //M bits:1 deprecated:1 version:001
char test; //M serialize:off
};
int print(int); //M const:true
typedef int dummy; //M extra=separators allowed(too)
int main(int argc, const char **argv) {
#ifndef NDEBUG
unlink(__FILE__ ".meta");
#endif
if( meta_loadfile(__FILE__ ".meta") ) {
puts("Loaded .meta file");
} else {
puts("Cannot read .meta file. regenerating...");
meta_inspect(__FILE__);
puts("Saving .meta file...");
meta_savefile(__FILE__ ".meta");
}
meta_save( stdout );
puts(meta_find_value("Orange", "type"));
puts(meta_find_value("Orange", "bits"));
puts(meta_find_value("Orange", "default"));
puts(meta_find_value("visible", "type"));
puts(meta_find_value("shininess", "fixed"));
}
#define main main__
#endif

View File

@ -0,0 +1,386 @@
// missing //M(eta), DNA,
// meta_parse(file), meta_load, meta_save
#define REFLECT_C
//#define REFLECT_DEMO
// C reflection: enums, functions, structs, nested structs, members and pointers.
// - rlyeh, public domain
#ifndef REFLECT_H
#define REFLECT_H
// # reflection api
//
// - reflected symbol struct
//
// - define reflected symbol (3 quick macros).
// - define reflected symbol (complete function).
//
// - size of reflected symbol.
// - find reflected function (by name).
// - find reflected enum (by name).
// - find reflected field in struct (by name).
//
// - iterate all reflected fields in struct.
// - iterate all reflected symbols in registry.
//
// - @todo: reflect* reflect_find()
// - @todo: code annotations? "display-name", "min", "max", "range", "default"
// - @todo: declare TYPEDEF(vec3, float[3]), TYPEDEF(mat4, vec4[4]/*float[16]*/)
typedef struct reflect {
union {
void *any;
int offs;
};
const char *type, *name, *base, *info;
unsigned size:23, is_pod:1, is_ptr:6, internal_type:2;
} reflect;
#define ENUM(type, name, ...) reflect_add(0, (void*)name, #type, #name, "", "" #__VA_ARGS__ "\0", sizeof(enum type))
#define FUNCTION(type, name, ...) reflect_add(1, &name, #type, #name, "", "" #__VA_ARGS__ "\0", sizeof(void *))
#define STRUCT(struct, type, name, ...) reflect_add(2, &(((struct *)0)->name), #type, #name, #struct, "" #__VA_ARGS__ "\0", (int)sizeof(type) )
void reflect_add( int internal_type, void *any, const char *type, const char *name, const char *base, const char *info, int size );
int reflect_sizeof( const char *type );
void* reflect_function( const char *name );
int reflect_enum( const char *name );
void* reflect_field( const char *type, void *obj, const char *name, const char **opt_type );
bool reflect_has_fields( const char *base, void *obj );
void reflect_iterate_fields( const char *type, void *obj, void (*callback)( const reflect *r, void *value, void *userdata ), void *userdata );
void reflect_iterate_registry( void (*callback)( const reflect *r, void *userdata ), void *userdata );
#endif
// ----------------------------------------------------------------------------
#ifdef REFLECT_C
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef REFLECT_REGISTRY_LIMIT
#define REFLECT_REGISTRY_LIMIT 0 // 0 for unlimited registry size (using heap), or N for fixed max entries (using stack)
#endif
#if REFLECT_REGISTRY_LIMIT > 0
static reflect *registry[REFLECT_REGISTRY_LIMIT] = {0}, registry_[REFLECT_REGISTRY_LIMIT] = {0};
#else
static reflect **registry = 0;
#endif
enum {
REFLECT_TYPE_ENUM,
REFLECT_TYPE_FUNCTION,
REFLECT_TYPE_FIELD,
};
static int reflect_counter = 0;
void reflect_add( int internal_type, void *any, const char *type, const char *name, const char *base, const char *info, int size ) {
++reflect_counter;
#if REFLECT_REGISTRY_LIMIT > 0
registry[reflect_counter-1] = &registry_[reflect_counter-1];
#else
registry = (reflect **)realloc( registry, reflect_counter * sizeof(reflect *));
registry[reflect_counter-1] = (reflect *)malloc( sizeof(reflect) );
#endif
reflect s = { any, type, name, base, info, size, 1, 0, internal_type };
reflect *r = registry[reflect_counter-1];
*r = s;
// evaluate is_ptr
for( int i = strlen(r->type); !r->is_ptr && --i; ) {
r->is_ptr = r->type[i] == '*' ? i : 0;
}
for( int i = r->is_ptr; i >= 0; --i ) {
r->is_ptr = r->type[i] == ' ' ? i : r->is_ptr;
}
// @fixme: try to avoid dynamic allocs
// future me: removing this will break some strcmps(type) below
if( r->is_ptr ) {
char buf[128]; sprintf(buf, "%.*s", r->is_ptr, r->type);
r->type = (const char *)strdup(buf);
}
// evaluate is_pod. kind of bubble sort.
for( int i = 0; i < reflect_counter; ++i ) {
for( int j = 0; j < reflect_counter; ++j ) {
if( !strcmp(registry[i]->base, registry[j]->type) ) {
registry[j]->is_pod = 0;
}
}
}
}
int reflect_sizeof( const char *base ) {
int size = 0;
if( base ) for( int i = 0; i < reflect_counter; ++i ) {
if( !strcmp(registry[i]->base, base) ) size += registry[i]->size;
}
return size;
}
void *reflect_field( const char *base, void *obj, const char *name, const char **opt_type ) {
if( base ) for( int i = 0; i < reflect_counter; ++i ) {
if( registry[i]->internal_type == REFLECT_TYPE_FIELD && strcmp(registry[i]->base, base) ) continue;
if(!strcmp(registry[i]->name, name) ) return (opt_type ? *opt_type = registry[i]->type : NULL), (char*)obj + (long long int)registry[i]->any;
}
return 0;
}
int reflect_enum( const char *name ) {
if( name ) for( int i = 0; i < reflect_counter; ++i ) {
if( registry[i]->internal_type == REFLECT_TYPE_ENUM && !strcmp( registry[i]->name, name ) ) {
return (int)(long long int)registry[i]->any;
}
}
return 0;
}
// do not use void(void) signature here
static void* reflect_dummy_call() {
return 0;
}
void* reflect_function( const char *name ) {
if( name ) for( int i = 0; i < reflect_counter; ++i ) {
if( registry[i]->internal_type == REFLECT_TYPE_FUNCTION && !strcmp( registry[i]->name, name ) ) {
return registry[i]->any;
}
}
return &reflect_dummy_call; // return NULL instead?
}
void reflect_iterate_registry( void (*callback)( const reflect *r, void *userdata ), void *userdata ) {
for( int i = 0; i < reflect_counter; ++i ) {
callback( registry[i], userdata );
}
}
#ifdef _MSC_VER
__declspec(thread)
#else
__thread
#endif
struct reflect_context {
const char *name;
const char *info;
} reflect_ctx = { "", "" };
void reflect_iterate_fields( const char *base, void *obj, void (*callback)( const reflect *r, void *value, void *userdata ), void *userdata ) {
if( base ) for( int i = 0, nb = strlen(base); i < reflect_counter; ++i ) {
if( registry[i]->internal_type == REFLECT_TYPE_FIELD && !strncmp(registry[i]->base, base, nb) ) {
for( ; i < reflect_counter && !strncmp(registry[i]->base, base, nb); ++i ) {
void *any = ((char*)obj + (long long int)registry[i]->any);
if( any && registry[i]->is_ptr ) {
any = (void*)*((long long int *)any);
}
if( any ) {
struct reflect_context copy = reflect_ctx;
char buf1[128]; sprintf(buf1, "%s.%s", reflect_ctx.name, registry[i]->name);
char buf2[128] = {0}; if( registry[i]->info[0] ) sprintf(buf2, "%s%s%.*s", reflect_ctx.info, reflect_ctx.info[0] ? " > " : "", (int)(strlen(registry[i]->info) - 2), 1+registry[i]->info);
reflect_ctx.name = buf1 + (buf1[0] == '.');
reflect_ctx.info = buf2;
if( registry[i]->is_pod ) {
reflect m = *registry[i];
m.name = reflect_ctx.name;
m.info = reflect_ctx.info;
callback( &m, any, userdata );
} else {
char buf3[128], *rebase = (char*)registry[i]->type;
if(registry[i]->is_ptr) sprintf( rebase = buf3, "%.*s", registry[i]->is_ptr, registry[i]->type );
reflect_iterate_fields( rebase, any, callback, userdata );
}
reflect_ctx = copy;
}
}
return;
}
}
}
bool reflect_has_fields( const char *base, void *obj ) {
if( base ) for( int i = 0, nb = strlen(base); i < reflect_counter; ++i ) {
if( registry[i]->internal_type == REFLECT_TYPE_FIELD && !strncmp(registry[i]->base, base, nb) ) {
for( ; i < reflect_counter && !strncmp(registry[i]->base, base, nb); ++i ) {
return true;
}
}
}
return false;
}
void reflect_dump_registry(FILE *fp) {
fprintf( fp, "%s {\n", __FUNCTION__ );
fprintf( fp, "\tsizeof(reflect)=%d\n", (int)sizeof(reflect) );
for( int i = 0; i < reflect_counter; ++i ) {
reflect *r = registry[i];
/**/ if( r->internal_type == REFLECT_TYPE_FUNCTION )
fprintf(fp, "\tfunction %s = %s; // %d@[%p] %s\n", r->name, r->type, r->size, r->any, r->info);
else if( r->internal_type == REFLECT_TYPE_ENUM )
fprintf(fp, "\tenum %s%s%s = %d; // %d %s\n", r->type[0] ? r->type : "", r->type[0] ? "." : "", r->name, (int)(long long int)r->any, r->size, r->info );
else if( r->internal_type == REFLECT_TYPE_FIELD )
fprintf(fp, "\t%s%s%s %s.%s; // %d@%d %s\n", r->is_pod ? "field " : "struct ", r->type, r->is_ptr ? "*" : "", r->base, r->name, r->size, (int)(long long int)r->any, r->info );
}
fprintf( fp, "} %s\n", __FUNCTION__ );
}
#endif
// ----------------------------------------------------------------------------
#ifdef META_DEMO_IMMEDIATE
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
void echo( const reflect *r, void *value, void *userdata ) {
FILE *fp = (FILE*)userdata;
if( fp ) {
/**/ if( !strcmp(r->type, "int") ) fprintf(fp, "%6s %-32s = %d;\t// %s\n", r->type, r->name, *(int*)value, r->info);
else if( !strcmp(r->type, "string") ) fprintf(fp, "%6s %-32s = %s;\t// %s\n", r->type, r->name, *(char**)value, r->info);
else if( !strcmp(r->type, "float") ) fprintf(fp, "%6s %-32s = %f;\t// %s\n", r->type, r->name, *(float*)value, r->info);
else if( !strcmp(r->type, "double") ) fprintf(fp, "%6s %-32s = %f;\t// %s\n", r->type, r->name, *(double*)value, r->info);
}
}
enum MY_ENUM {
TEXTURE = 101,
IMAGE = 102,
};
typedef char* string;
typedef struct MyVec3 {
double x,y,z;
} MyVec3;
typedef struct MyTransform {
MyVec3 location;
MyVec3 rotation;
float scale;
} MyTransform;
typedef struct MyObject {
struct MyObject *parent;
string id;
int hash;
MyTransform transform;
} MyObject;
int MyAddFunction( int a,int b ) {
return a + b;
}
int main( int argc, char **argv ) {
// # enums
// - register
// - reflect
ENUM( MY_ENUM, TEXTURE );
ENUM( MY_ENUM, IMAGE );
assert( 101 == reflect_enum("TEXTURE") );
assert( 102 == reflect_enum("IMAGE") );
// # functions
// - register
// - find
// - call
// - try call (undefined functions are also safe to call)
FUNCTION( int(int a, int b), MyAddFunction, "Function adding two numbers" );
FUNCTION( int(const char *fmt, ...), printf, "Print text to console" );
int (*add_hook)() = reflect_function("MyAddFunction");
int (*print_hook)(const char *, ...) = reflect_function("printf");
assert( 123 == add_hook(100,23) );
print_hook("hello from reflected function %d\n", 123);
print_hook = reflect_function("undefined_symbol_here$(·!!");
print_hook("this call should never print\n");
puts("---");
// # structs
// - register simple
// - register nested
// - register nested with pointers
// - print struct sizes
// - iterate simple
// - iterate nested
// - iterate nested with pointers
STRUCT( MyVec3, double, x, "Right" );
STRUCT( MyVec3, double, y, "Forward" );
STRUCT( MyVec3, double, z, "Up" );
STRUCT( MyTransform, MyVec3, location, "World location (absolute)" );
STRUCT( MyTransform, MyVec3, rotation, "Local rotation (in degrees)" );
STRUCT( MyTransform, float, scale, "Local scale (in centimeters)" );
STRUCT( MyObject, int, hash, "Actor hash" );
STRUCT( MyObject, string, id, "Actor name" );
STRUCT( MyObject, MyTransform, transform, "Actor transform" );
STRUCT( MyObject, MyObject *, parent, "Actor parent" );
printf("reflect_sizeof(MyVec3)=%d\n", reflect_sizeof("MyVec3"));
printf("reflect_sizeof(MyTransform)=%d\n", reflect_sizeof("MyTransform"));
printf("reflect_sizeof(MyObject)=%d\n", reflect_sizeof("MyObject"));
puts("---");
MyVec3 vec = {1.1,2.2,3.3};
reflect_iterate_fields( "MyVec3", &vec, echo, stdout );
puts("---");
MyTransform tf = { {1.1,2.2,3.3}, {45,90,180}, 10 };
reflect_iterate_fields( "MyTransform", &tf, echo, stdout );
puts("---");
MyObject root = { NULL, "Scene root", 0, { {0,0,0}, {0,0,0}, 1 } };
MyObject obj = { &root, "Name identifier", 123, { {1.1,2.2,3.3}, {45,90,180}, 10 } };
reflect_iterate_fields( "MyObject", &obj, echo, stdout );
puts("---");
// # dump internals
reflect_dump_registry(stdout);
puts("---");
// # benchmark
#ifndef N
#define N (argc > 1 ? atoi(argv[1]) : 500 * 1000)
#endif
clock_t t0 = clock();
for( int i = 0; i < N; ++i) {
reflect_iterate_fields( "MyObject", &obj, echo, NULL );
}
clock_t t1 = clock();
double t = (t1 - t0) / (double)CLOCKS_PER_SEC;
printf("Benchmark: processed %.f members in %5.2fs = %5.2f members/sec\n", N*9.0, t, (N*9.0)/t ); // 9 total members in MyObject
}
#endif

View File

@ -0,0 +1,46 @@
#define FWK_C
#include "fwk.h"
bool parse_struct(const char *line) {
return strstr(line, "s""truct ");
}
bool parse_union(const char *line) {
return strstr(line, "u""nion");
}
bool parse_enum(const char *line) {
return strstr(line, "e""num ");
}
bool parse_typedef(const char *line) {
return strstr(line, "t""ypedef ");
}
bool parse_function(const char *line) {
return strstr(line, "(") && strstr(line, ");");
}
bool parse_comment(const char *line) {
return strstr(line, "//") || (strstr(line, "/*") && strstr(line, "*/"));
}
bool parse_variable(const char *line) {
return strstr(line, "=");
}
bool parse_member(const char *line) {
return strstr(line, ";");
}
const char* parse_any(const char *line) {
if(parse_struct(line)) return "STRUCT";
if(parse_union(line)) return "UNION";
if(parse_enum(line)) return "ENUM";
if(parse_typedef(line)) return "TYPEDEF";
if(parse_variable(line)) return "VARIABLE";
if(parse_function(line)) return "FUNCTION";
if(parse_member(line)) return "MEMBER";
return 0;
}
int main() {
char *data = file_read(__FILE__);
for each_substring(data, "\r\n", line) {
const char *type = parse_any(line);
printf("%s%s%s\n", line, type ? " -> " : "", type ? type : "");
}
}

View File

@ -0,0 +1,42 @@
@if "%1" == "tidy" (
del *.zip
del *.mem
del *.exp
del *.lib
del *.exe
del *.obj
del *.o
del *.a
del *.pdb
del *.ilk
del *.def
del *.dll
del oscedit.ini
rd /q /s .vs
exit /b
)
cl ..\editor2.c -I ..\..\tools -DCOOK_ON_DEMAND
pushd ..\.. && call make amalgamation && popd
taskkill /im "oscedit.exe" > nul 2> nul
call ..\..\tools\tcc oscgame.c -I ..\.. -DFWK_IMPLEMENTATION -DCOOK_ON_DEMAND %*
call ..\..\tools\tcc oscsend.c -I ..\.. -DFWK_IMPLEMENTATION -DCOOK_ON_DEMAND %*
call ..\..\tools\tcc oscedit.c -I ..\.. -DFWK_IMPLEMENTATION -DCOOK_ON_DEMAND %* && start oscedit.exe
timeout 3
:: showcase UI widgets creation on demand, which are requested from this very same batch file
oscsend /player/time 5.5
oscsend /player/name "john doe"
oscsend /player/health 100
oscsend /player/is_active true
oscsend /player2/integer 123
oscsend /player2/string world
oscsend /player1/string hello
:: simulate a game running sending OSC commands to our editor
rem oscgame

View File

@ -0,0 +1,22 @@
#include "fwk.h"
#include "oscedit.h"
// demo
int main() {
do_once map_init(client_vars, less_str, hash_str);
do_once map_init(server_vars, less_str, hash_str);
int server_socket = osc_listen("0.0.0.0", OSC_EDIT_PORT);
if( server_socket >= 0 ) {
if( !strcmp("--launch", argv(1)) ) system("oscgame"); // launch game
cook_config("../../tools/cook.ini");
window_create(0.80, WINDOW_SQUARE);
window_title("EDITOR");
while( window_swap() ) {
osc_edit_sync(server_socket, -1, 4);
}
}
}

View File

@ -0,0 +1,221 @@
// public api
int osc_edit(const char *hierarchy_descriptor, char type, void *ptr); // descriptor format: [ifsTF]/path/name
void osc_edit_sync(int server_fd, int client_fd, unsigned timeout_ms);
int osc_edit_load(const char *mask);
int osc_edit_save(const char *mask);
int osc_edit_reset(const char *mask);
#define OSC_EDIT_PORT "2023" // maybe use portname("OSC_EDITOR_V1", 000) ?
// --- impl
#pragma once
#include "oscpack.h"
#include "oscsend.h"
#include "oscrecv.h"
#ifndef OSC_EDIT_INI
#define OSC_EDIT_INI "oscedit.ini"
#endif
typedef struct osc_variant {
char *key; // key address
char type; // variant type
union osc_variant_ {
int64_t i;
char *s;
double f;
uintptr_t up;
} live[1], offline[1];
bool edited;
} osc_variant;
map(char*, void*) client_vars;
array(char*) client_outgoing;
map(char*, osc_variant) server_vars;
array(char*) server_outgoing;
int osc_edit(const char *hierarchy_descriptor, char type, void *ptr) {
*((void**)map_find_or_add(client_vars, (char*)hierarchy_descriptor, ptr)) = ptr;
array_push(client_outgoing, stringf("%c%s", type, hierarchy_descriptor)); // @leak
return 0;
}
int osc_edit_load(const char *mask) {
ini_t map = ini(OSC_EDIT_INI);
if( !map ) return 0;
map_clear(server_vars); // @todo: @leak: iterate variant strings and free() them
for each_map_ptr_sorted(map, char *, key, char *, val) {
if( strmatchi(*key + 2, mask ) ) { // skip initial char_type+dot
printf("%s=%s\n", *key, *val);
osc_variant variant = {0};
variant.type = 0[*key];
/**/ if( variant.type == 'i' ) variant.live[0].i = atoi(*val);
else if( variant.type == 'f' ) variant.live[0].f = atof(*val);
else if( variant.type == 's' ) variant.live[0].s = STRDUP(*val);
else if( strchr("bTF",variant.type) ) variant.live[0].i = (*val)[3] == 't';
variant.key = STRDUP(*key + 2);
variant.offline[0] = variant.live[0];
map_insert(server_vars, STRDUP(*key + 2), variant); // @todo: no need to STRDUP() here. we got variant.key already allocated.
}
}
map_free(map);
ui_notify("Loaded.", NULL);
return 1;
}
int osc_edit_save(const char *mask) {
unlink( OSC_EDIT_INI );
for each_map_ptr(server_vars, char *, title, osc_variant, msg) {
if( strmatchi(*title, mask) ) {
msg->edited = false;
if( msg->type == 's' ) msg->offline[0].s = STRDUP(msg->live[0].s); // @leak
else memcpy( &msg->offline[0], &msg->live[0], sizeof(msg->offline[0]) );
if( msg->type == 'i' ) ini_write(OSC_EDIT_INI, "i", msg->key, va("%d", (int)msg->live[0].i ));
if( msg->type == 'f' ) ini_write(OSC_EDIT_INI, "f", msg->key, va("%f", (float)msg->live[0].f ));
if( msg->type == 's' ) ini_write(OSC_EDIT_INI, "s", msg->key, va("%s", msg->live[0].s ));
if( strchr("bTF",msg->type) ) ini_write(OSC_EDIT_INI, "b", msg->key, va("%s", msg->live[0].i ? "true":"false" ));
}
}
ui_notify("Saved.", NULL);
return 1;
}
int osc_edit_reset(const char *mask) {
for each_map_ptr(server_vars, char *, title, osc_variant, msg) {
if( strmatchi(*title, mask) ) {
msg->edited = false;
if( msg->type == 's' ) msg->live[0].s = STRDUP(msg->offline[0].s); // @leak
else memcpy( &msg->live[0], &msg->offline[0], sizeof(msg->live[0]) );
}
}
return 1;
}
void osc_edit_sync(int server_fd, int client_fd, unsigned timeout_ms) {
// client logic
if( client_fd >= 0 ) {
// 1. send outgoing vars (widget requests at server)
// 2. recv modified vars (from user tweaking widgets), wait till timeout_ms
// received vars are removed from outgoing queue, so it's never submitted again until it gets modified.
// [1]
for(int i = 0; i < array_count(client_outgoing); ++i) {
char *out = 0;
/**/ if( client_outgoing[i][0] == 'i' ) out = osc_pack_va(client_outgoing[i]+1, "i", *(int*)*((void**)map_find(client_vars, client_outgoing[i]+1)) );
else if( client_outgoing[i][0] == 'f' ) out = osc_pack_va(client_outgoing[i]+1, "f", *(float*)*((void**)map_find(client_vars, client_outgoing[i]+1)) );
else if( client_outgoing[i][0] == 's' ) out = osc_pack_va(client_outgoing[i]+1, "s", *(char**)*((void**)map_find(client_vars, client_outgoing[i]+1)) );
else if( client_outgoing[i][0] == 'b' ) out = osc_pack_va(client_outgoing[i]+1, *(bool*)*((void**)map_find(client_vars, client_outgoing[i]+1)) ? "T":"F" );
else if( client_outgoing[i][0] == 'T' ) out = osc_pack_va(client_outgoing[i]+1, "T" );
else if( client_outgoing[i][0] == 'F' ) out = osc_pack_va(client_outgoing[i]+1, "F" );
else if( client_outgoing[i][0] == '\0') out = osc_pack_va(client_outgoing[i]+1, "" );
else ASSERT(0, "unsupported osc_edit command `%c`", client_outgoing[i][0]);
if( out ) osc_send(client_fd, out+4, *(int*)out);
}
for(int i = 0; i < array_count(client_outgoing); ++i) {
FREE(client_outgoing[i]);
}
array_clear(client_outgoing);
// [2]
sleep_ms(timeout_ms);
}
// server logic
if( server_fd >= 0 ) {
// 1. recv vars from client
// 2. if they do not exist: instantiate UI controls for them
// 3. if they do exist: update values from client
// 4. when UI controls are modified, update our local copies and send its values back to client
osc_update(server_fd, timeout_ms); // call every frame. reads the udp port and parse all messages found there
const osc_message *begin;
for( int it = 0, end = osc_list(&begin); it < end; ++it ) {
const osc_message *msg = begin + it;
osc_variant oscv, zero = {0};
oscv.key = STRDUP(msg->pattern);
oscv.type = msg->types[0];
if(oscv.type == 's') oscv.live[0].s = STRDUP(msg->v[0].s), oscv.offline[0].s = STRDUP(msg->v[0].s);
else memcpy(&oscv.live[0], &msg->v[0], sizeof(oscv.live[0])), memcpy(&oscv.offline[0], &msg->v[0], sizeof(oscv.live[0]));
*map_find_or_add_allocated_key(server_vars, STRDUP(msg->pattern), zero) = oscv;
}
static int on = 1;
if( ui_window("editor", &on) ) {
int num_messages = osc_list(&begin);
int prev_title_len = 0; char *prev_title = 0;
if( !map_count(server_vars) ) {
// create section header
int choice = ui_label2_toolbar("New", ICON_MD_LOOP);
if( choice == 1 ) osc_edit_load("*");
}
else
for each_map_ptr_sorted(server_vars, char *, title, osc_variant, msg) {
char *second_slash = strchr(*title + 1, '/');
int title_len = (int)(second_slash - *title) - 1; // -> /player/title -> player
int different_size = title_len != prev_title_len;
int new_section = different_size || (prev_title && strncmp(prev_title, *title, title_len+1));
// create section header
if( new_section ) {
if( prev_title ) ui_separator();
char *caption = va("*%.*s", title_len + 2, *title);
int choice = ui_label2_toolbar(caption, prev_title ? "" : va(">%d" ICON_MD_EMAIL " " ICON_MD_UNDO " " ICON_MD_LOOP " " ICON_MD_SD_CARD, num_messages)); // ICON_MD_UNDO " " ICON_MD_UPLOAD " " ICON_MD_DOWNLOAD
if( choice == 3 ) osc_edit_reset("*");
if( choice == 2 ) osc_edit_load("*");
if( choice == 1 ) osc_edit_save("*");
prev_title_len = title_len, prev_title = va("%s", *title);
} else {
prev_title_len = title_len;
}
ui_label_icon_highlight = !!msg->edited;
// vec2 ui_label_icon_clicked_L; // left
// vec2 ui_label_icon_clicked_R; // right
char *title_copy = va(ICON_MD_UNDO " " "%s", *title + 1 + title_len + 1); // /player/titlexx -> UNDO_ICON titlexx
// create gui elements
/**/ if( msg->type == '\0') { ui_label(title_copy); }
else if( msg->type == 'i' ) { int i = (int)msg->live[0].i; msg->edited |= ui_int(title_copy, &i); msg->live[0].i = i; }
else if( msg->type == 'f' ) { float f = msg->live[0].f; msg->edited |= ui_float(title_copy, &f); msg->live[0].f = f; }
else if( msg->type == 's' ) { char s[128] = {0}; strncpy(s, msg->live[0].s, sizeof(s)); uint64_t old = hash_str(s); ui_buffer(title_copy, s, sizeof(s)); uint64_t mod = hash_str(s); if(mod != old) msg->edited |= 1, FREE(msg->live[0].s), msg->live[0].s = stringf("%s",s); }
else if( strchr("bTF",msg->type) ) { bool b = !!msg->live[0].i; msg->edited |= ui_bool(title_copy, &b); msg->live[0].i = b; }
else ASSERT(0, "unsupported osc_edit command `%c`", msg->type);
if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on UNDO icon (1st icon)
osc_edit_reset(*title);
}
}
ui_window_end();
}
ui_demo();
}
}

View File

@ -0,0 +1,39 @@
#include "fwk.h"
#include "oscedit.h"
// game
struct player {
char *name;
int health;
bool visible;
float time;
};
void render_game(struct player *p) {
printf("%*.s\rgame: %s health=%d time=%f visible=%d", 30, "", p->name, p->health, p->time, p->visible);
}
// demo
int main() {
do_once map_init(client_vars, less_str, hash_str);
do_once map_init(server_vars, less_str, hash_str);
int client_socket = osc_open("127.0.0.1", OSC_EDIT_PORT);
if( client_socket >= 0 ) {
struct player P1 = { STRDUP("Player 1"), 100, true };
while( GetAsyncKeyState(VK_ESCAPE) & 0x8000 ^ 0x8000 ) {
P1.time = time_ss(); // << this is the "game", it just advances "time"
osc_edit("/player/name", 's', &P1.name);
osc_edit("/player/health", 'i', &P1.health);
osc_edit("/player/time", 'f', &P1.time);
osc_edit("/player/visible", 'b', &P1.visible);
osc_edit_sync(-1, client_socket, 4);
render_game(&P1);
}
}
}

View File

@ -0,0 +1,74 @@
#include "fwk.h"
#define OSCPACK_C
#define OSCRECV_C
#define OSCSEND_C
#include "oscpack.h"
#include "oscrecv.h"
#include "oscsend.h"
// networked gui, public api
void ui_netconfig(unsigned port);
void ui_netupdate();
// networked gui, private api
static int ui_netsocket = -1, ui_netclient, ui_netserver;
static map(char*,int) ui_netvalues_i;
void ui_netconfig(unsigned port) {
do_once map_init(ui_netvalues_i, less_str, hash_str);
if( ui_netsocket < 0 ) {
ui_netclient = !(tcp_bind("0.0.0.0",va("%d",port+1), 1) >= 0);
ui_netserver = !ui_netclient;
ui_netsocket = ui_netclient ? osc_open("127.0.0.1", va("%d",port)) : osc_listen("0.0.0.0", va("%d",port));
if( ui_netsocket >= 0 ) ui_notify("UI Network config", va("Connected (%s).", ui_netclient ? "client (editor)" : "server (game)"));
if( ui_netsocket >= 0 ) window_title(ui_netclient ? "UI client (editor)" : "UI server (game)");
if( ui_netsocket < 0 ) ui_netclient = ui_netserver = 0;
}
}
void ui_netupdate() {
if( ui_netserver ) {
// map_clear(ui_netvalues_i);
osc_update(ui_netsocket, 16);
}
if( ui_netclient ) {
for each_map_ptr(ui_netvalues_i, char *, name, int, value) {
char msg[4096];
int msglen = osc_pack(msg, va("/%s", *name), "i", *value);
osc_send(ui_netsocket, msg + 4, msglen - 4);
}
// map_clear(ui_netvalues_i);
}
}
int ui_netint(const char *name, int *value) {
const osc_message *found = osc_find(va("/%s", name)); // search in reverse order, so newest wins
if( found ) *value = (int)found->v[0].i;
int changed = ui_int(name, value);
if( changed ) *map_find_or_add(ui_netvalues_i, (char*)name, *value) = *value;
return changed;
}
// demo
int main() {
window_create(0.66, WINDOW_SQUARE);
ui_netconfig(1234);
while( window_swap() ) {
ui_netupdate();
static int r = 0, g = 0, b = 0;
if( ui_panel("test1", 0) ) {
ui_netint("Color R", &r); r = clampi(r, 0, 255);
ui_netint("Color G", &g); g = clampi(g, 0, 255);
ui_netint("Color B", &b); b = clampi(b, 0, 255);
ui_panel_end();
}
viewport_color3(vec3(r/255.0,g/255.0,b/255.0));
}
}

View File

@ -0,0 +1,106 @@
// networked gui demo
// - rlyeh, public domain
#include "fwk.h"
#define OSCPACK_C
#define OSCRECV_C
#define OSCSEND_C
#include "oscpack.h"
#include "oscrecv.h"
#include "oscsend.h"
int main() {
// window (80% sized, MSAA x2 flag)
window_create(80, WINDOW_MSAA2);
window_title(__FILE__);
// scene loading
#define SCENE(...) #__VA_ARGS__
const char *my_scene = SCENE([
{
skybox: 'cubemaps/stardust/',
},
{
position:[-5.0,-2.0,2.0],
rotation: [90.0,0.0,180.0],
scale:0.20,
mesh:'models/witch/witch.obj',
texture:'models/witch/witch_diffuse.tga.png',
flipuv:false,
},
{
position:[-5.0,-2.0,2.0],
rotation: [90.0,0.0,180.0],
scale:0.20,
mesh:'models/witch/witch_object.obj',
texture:'models/witch/witch_object_diffuse.tga.png',
flipuv:false,
},
]);
int num_spawned = scene_merge(my_scene);
object_t *obj1 = scene_index(0);
object_t *obj2 = scene_index(1);
// camera
camera_t cam = camera();
cam.speed = 0.2f;
// demo loop
while (window_swap())
{
// input
if( input_down(KEY_ESC) ) break;
// fps camera
bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R);
window_cursor( !active );
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed);
camera_move(&cam, wasdecq.x,wasdecq.y,wasdecq.z);
camera_fps(&cam, mouse.x,mouse.y);
// queue model scale bounces
float t = fmod(window_time(), 0.3) / 0.3;
float s = 0.01f * ease_ping_pong(t, ease_in_cubic,ease_out_cubic);
object_scale(obj1, vec3(0.20f - s,0.20f + s,0.20f - s));
object_scale(obj2, vec3(0.20f - s,0.20f + s,0.20f - s));
// flush render scene (background objects: skybox)
profile("Scene background") {
scene_render(SCENE_BACKGROUND);
}
// queue debug drawcalls
profile("Debugdraw") {
ddraw_grid(0);
ddraw_color(YELLOW);
ddraw_text(vec3(+1,+1,-1), 0.04f, va("(%f,%f,%f)", cam.position.x,cam.position.y,cam.position.z));
ddraw_color(YELLOW);
ddraw_flush();
}
// render scene (foreground objects) with post-effects
profile("Scene foreground") {
int scene_flags = 0;
scene_render(SCENE_FOREGROUND | scene_flags);
}
#if 0
do_once ui_netconfig(1234);
ui_netupdate();
static int r = 0, g = 0, b = 0;
viewport_color3(vec3(r/255.0,g/255.0,b/255.0));
if( ui_panel("net test1", 0) ) {
ui_netint("Color R", &r); r = clampi(r, 0, 255);
ui_netint("Color G", &g); g = clampi(g, 0, 255);
ui_netint("Color B", &b); b = clampi(b, 0, 255);
ui_panel_end();
}
#endif
}
}

View File

@ -0,0 +1,162 @@
// OSC buffer packing,
// - rlyeh, public domain.
//
// pack format: (i)nt, (h)int64, (t)ime, (f)loat, (s)tring, (S)ymbol, (c)har, (r)gba, (m)idi,
// (T)true, (N|F)nil+false, (I)nfinity, (b)lob, (d)ouble, @todo: ([)array.
//
// warning: osc_pack() generates OSC compliant messages, however,
// every osc_pack_va() call generates a 32-bit length prefix (in machine dependant order)
API int osc_bundle( char *buf, uint64_t ts );
API int osc_pack( char *buf, const char *addr, const char *fmt, ... );
API char* osc_pack_va( const char *addr, const char *fmt, ... );
#pragma once
/*
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32")
#else
#include <arpa/inet.h>
#endif
*/
int osc__buffer_vl( char *buf, const char *fmt, va_list vl ) {
// if `buf` is NULL, just calc needed space
if( !buf ) {
int bytes = 0;
while( *fmt++ ) {
switch( fmt[-1] ) {
default: // bypass
break; case 'T': case 'F': case 'N': case 'I': bytes += 4;
break; case 'i': case 'c': case 'r': case 'm': bytes += 4; (void)va_arg(vl, uint32_t);
break; case 't': case 'h': bytes += 8; (void)va_arg(vl, uint64_t);
break; case 'd': bytes += 8; (void)va_arg(vl, double);
break; case 'f': bytes += 4; (void)va_arg(vl, double);
break; case 'b': bytes += va_arg(vl, uint32_t); (void)va_arg(vl, const char *);
break; case 's': case 'S': bytes += strlen( va_arg(vl, const char *) ) + 1;
}
}
return bytes;
}
char *src = buf;
while( *fmt++ ) {
switch( fmt[-1] ) {
default: *buf++ = fmt[-1]; // bypass
break; case 'T': case 'F': case 'N': case 'I':
{}
break; case 'i': case 'c': case 'r': case 'm':
{ uint32_t i = va_arg(vl, uint32_t); i = ntohl(i); memcpy(buf, &i, 4); buf += 4; }
break; case 't': case 'h':
{ uint64_t l = va_arg(vl, uint64_t); l = ntohll(l); memcpy(buf, &l, 8); buf += 8; }
break; case 'd':
{ union { double f; uint64_t i; } u; u.f = va_arg(vl, double); u.i = ntohll(u.i); memcpy(buf, &u.i, 8); buf += 8; }
break; case 'f':
{ union { float f; uint32_t i; } u; u.f = (float)va_arg(vl, double); u.i = ntohl(u.i); memcpy(buf, &u.i, 4); buf += 4; }
break; case 'b':
{ uint32_t l = va_arg(vl, uint32_t), ll = ntohl(l); memcpy(buf, &ll, 4); buf += 4; /*}*/
/*{*/ const char *s = va_arg(vl, const char *); int32_t i = 0;
memcpy(buf, s, l); memcpy(buf + l, &i, 4); buf += l; buf += 3 - (((intptr_t)buf+3) & 3); }
break; case 's': case 'S':
{ const char *s = va_arg(vl, const char *); int32_t i = 0, l = (int32_t)strlen(s) + 1;
memcpy(buf, s, l); memcpy(buf + l, &i, 4); buf += l; buf += 3 - (((intptr_t)buf+3) & 3); }
}
}
return buf - src;
}
int osc__buffer( char *buf, const char *fmt, ... ) {
va_list vl;
va_start(vl, fmt);
int l = osc__buffer_vl(buf, fmt, vl);
va_end(vl);
return l;
}
int osc_pack( char *buf, const char *addr, const char *fmt, ... ) {
char tmp[8192];
va_list vl;
va_start(vl, fmt);
int l2 = osc__buffer_vl(tmp, fmt, vl);
va_end(vl);
int l1 = osc__buffer(buf, "s,s", addr, fmt);
memcpy(buf+l1, tmp, l2);
return l1+l2;
}
char* osc_pack_va( const char *addr, const char *fmt, ... ) { // @todo: optimize me
char buf[1024];
char tmp[8192];
va_list vl;
va_start(vl, fmt);
int l2 = osc__buffer_vl(tmp, fmt, vl);
va_end(vl);
int l1 = osc__buffer(buf, "s,s", addr, fmt);
memcpy(buf+l1, tmp, l2);
int total = l1+l2;
char *out = va("%*.s", 4+total, "");
memcpy(out, &total, 4);
memcpy(out+4, buf, total);
return out;
}
int osc_bundle( char *buf, uint64_t ts ) {
return osc__buffer( buf, "sh", "#bundle", ts );
}
#ifdef OSCPACK_DEMO
int main() {
// OSC message
{
char buf[4096];
int l = osc_pack(buf, "/foo", "iisff", 1000, -1, "hello", 1.234f, 5.678f);
//use as: udp_send(socket, buf+4, l-4);
hexdump(buf, l);
assert( 0 == memcmp( buf,
"\x2f\x66\x6f\x6f\x00\x00\x00\x00\x2c\x69\x69\x73\x66\x66\x00\x00"
"\x00\x00\x03\xe8\xff\xff\xff\xff\x68\x65\x6c\x6c\x6f\x00\x00\x00"
"\x3f\x9d\xf3\xb6\x40\xb5\xb2\x2d", l-4));
}
// OSC message (w/ initial 4-bytes payload)
{
char *buf = osc_pack_va("/foo", "iisff", 1000, -1, "hello", 1.234f, 5.678f);
int l = *(int*)buf;
printf("---------------%x\n", l);
hexdump(buf, l);
assert( 0 == memcmp( buf+4, //"\x28\x00\x00\x00"
"\x2f\x66\x6f\x6f\x00\x00\x00\x00\x2c\x69\x69\x73\x66\x66\x00\x00"
"\x00\x00\x03\xe8\xff\xff\xff\xff\x68\x65\x6c\x6c\x6f\x00\x00\x00"
"\x3f\x9d\xf3\xb6\x40\xb5\xb2\x2d", l-4));
}
// OSC bundle
{
// OSC bundle test taken from Julien Pommier's oscpkt.hh
// wr.startBundle();
// wr.addMessage("/foo").pushInt32(1000,-1).pushStr("hello").pushFloat(1.234f,5.678f);
// wr.endBundle();
char buf[4096];
int h = osc_bundle(buf, 1ULL);
int m = osc_pack(buf+h, "/foo", "iisff", 1000, -1, "hello", 1.234f, 5.678f);
int l = h+m;
hexdump(buf, h+m);
assert( l == 0x3c-4 );
assert( 0 == memcmp( buf,
"\x23\x62\x75\x6e\x64\x6c\x65\x00\x00\x00\x00\x00\x00\x00\x00\x01"
"\x2f\x66\x6f\x6f\x00\x00\x00\x00\x2c\x69\x69\x73"
"\x66\x66\x00\x00\x00\x00\x03\xe8\xff\xff\xff\xff\x68\x65\x6c\x6c"
"\x6f\x00\x00\x00\x3f\x9d\xf3\xb6\x40\xb5\xb2\x2d", l) );
}
}
#endif

View File

@ -0,0 +1,299 @@
// simple osc server. designed for easy integration with a gameloop / immediate mode style.
// - rlyeh, public domain. forked from original code by @mmalex (public domain).
//
// @todo: add support for // wildcard
// @todo: add support for [ array parameters ]
API int osc_listen( const char *mask, const char *port ); // creates a listening socket
API int osc_update(int s, int timeout_ms); // call every frame. reads the udp port and parse all messages found there
API int osc_list(const struct osc_message **first); // returns number of received messages, also set arg pointer to first item
API int osc_debug( FILE *out, const char *inmsg, int len ); // debugs raw osc buffer to stream
API const struct osc_message *osc_find(const char *addr); // finds most recent message matching 'addr'
// OSC types from the spec.
enum {
OSC_INT = 'i',
OSC_FLOAT = 'f',
OSC_STRING = 's',
OSC_SYMBOL = 'S',
OSC_BLOB = 'b',
OSC_INT64 = 'h',
OSC_TIME = 't',
OSC_DOUBLE = 'd',
OSC_CHAR = 'c',
OSC_RGBA = 'r',
OSC_MIDI = 'm',
OSC_TRUE = 'T',
OSC_FALSE = 'F',
OSC_NIL = 'N',
OSC_INFINITY = 'I',
// OSC_ARRAY = '[', // @todo
};
// OSC message
typedef struct osc_message {
const char *pattern;// address in osc message
const char *types; // string of characters taken from the OSC types enum
const char *data; // pointer to raw osc data. for debugging purposes only (?)
#if 0
int64_t i[8]; // integer interpretation of first 8 params (for blobs & strings, is length)
const char *s[8]; // for blobs and strings
float f[8]; // floating point interpretation of first 8 params
#else
union variant {
int64_t i;
const char *s;
double f;
uintptr_t up;
} v[8];
#endif
} osc_message;
#pragma once
enum { OSC_MAX_BUF = 8*1024*1024 }; // was: 65536
enum { OSC_MAX_MESSAGES = 65536 }; // was: 1024
static int osc_match_(const char *pat, const char *addr) {
for (int n=0;*pat;addr++,pat++) switch (*pat) {
default: if (*pat!=*addr) return 0; break;
case '?': break;
case '[': n=(*++pat=='!'); for (pat+=n; *pat!=']' && *pat;++pat) { if (pat[1]=='-') { if (*addr>=*pat && *addr<=pat[2]) { n=!n; break; } pat+=pat[2] ? 2 : 1; } else if (*pat==*addr) { n=!n; break; } }
if (!n) return 0; while (*pat && *pat!=']') pat++; break;
case '{': n=0; for (const char *p=++pat; *p && *p!='}' && *p!='/'; pat=++p) { while (*p && *p!='}' && *p!='/' && *p!=',') p++; if (!strncmp(pat,addr,p-pat)) { addr+=p-pat; n=1; break; } }
while (*pat && *pat!='}') pat++; if (!n) return 0; break;
case '*': while (pat[1]=='*') pat++; n=0; if (pat[1]=='/' || pat[1]==0) { while (*addr && *addr!='/') ++addr; n=1; } else for (;*addr!='/' && *addr; ++addr) if (osc_match_(pat + 1, addr)) { n=1; break; }
if (!n) return 0; addr--; break;
// @todo: add // wildcard support
}
return *addr== 0;
}
static float osc_asfloat_(uint32_t x) { union { float f; uint32_t i; } u; u.i=x; return u.f; }
static double osc_asdouble_(uint64_t x) { union { double f; uint64_t i; } u; u.i=x; return u.f; }
static int osc_parse_i32_(const char **s, const char *e) {
if (*s+4>e) { *s=e; return 0; }
int rv=htonl(*(uint32_t*)*s);
*s+=4;
return rv;
}
static int64_t osc_parse_i64_(const char **s, const char *e) {
if (*s+8>e) { *s=e; return 0; }
int64_t rv=htonll(*(uint64_t*)*s);
*s+=8;
return rv;
}
static const char *osc_parse_str_(const char **s, const char *e) {
int len=(int)strlen(*s);
const char *rv=*s;
*s=rv+((len+4)&~3);
if (*s>e) *s=e;
return rv;
}
static const char *osc_parse_bin_(const char **s, const char *e, int *len) {
*len=osc_parse_i32_(s,e);
int maxlen=(int)(e-*s);
if (*len>maxlen) *len=maxlen;
if (*len<0) *len=0;
const char *rv=*s;
*s=rv+((*len+3)&~3);
if (*s>e) *s=e;
return rv;
}
static int osc_parse_(osc_message *out, int maxmsg, const char *s, const char *e) {
if (maxmsg<=0 || s>=e) return 0;
if (*(uint64_t*)s==*(uint64_t*)"#bundle\0") { // bundle is #bundle, uint64_t time, uint32_t length, <osc packet>
osc_parse_i64_(&s,e); // @todo: skipped time for now
int msgcount=0;
while (s<e && msgcount<maxmsg) {
int len=osc_parse_i32_(&s,e);
int maxlen=(int)(e-s);
if (len>maxlen) len=maxlen;
if (len<0) len=0;
int n=osc_parse_(out+msgcount, maxmsg-msgcount, s, s+len);
msgcount+=n;
s+=((len+3)&~3);
}
return msgcount;
}
// single message
memset(out,0,sizeof(osc_message));
out->pattern=osc_parse_str_(&s,e);
if (!out->pattern)
return 0;
out->types=(*s==',')?osc_parse_str_(&s,e):",f";
if (!out->types)
return 0;
out->types++;
out->data=s;
for (int param=0;param<8;++param) {
switch (out->types[param]) {
default: return 1; // done!
case OSC_CHAR: case OSC_RGBA: // all int32...
case OSC_MIDI: case OSC_INT: out->v[param].i=osc_parse_i32_(&s,e); break;
case OSC_TIME: case OSC_INT64: out->v[param].i=osc_parse_i64_(&s,e); break;
case OSC_STRING: case OSC_SYMBOL: out->v[param].s=osc_parse_str_(&s,e); /*out->v[param].i=strlen(out->v[param].s);*/ break;
case OSC_FLOAT: out->v[param].f=osc_asfloat_(osc_parse_i32_(&s,e)); break;
case OSC_DOUBLE: out->v[param].f=osc_asdouble_(osc_parse_i32_(&s,e)); break;
case OSC_BLOB: {int len=0; out->v[param].s=osc_parse_bin_(&s,e,&len); /*out->v[param].i=len;*/ break; } // @todo: important to signal len in variant somewhere
case OSC_INFINITY: out->v[param].f=INFINITY; break;
case OSC_TRUE: out->v[param].i=1; break;
case OSC_FALSE: case OSC_NIL: out->v[param].i=0; break;
// case OSC_ARRAY: @todo
}
}
return 1;
}
static struct osc_message *msg = 0; //[OSC_MAX_MESSAGES];
static int msgpos;
int osc_listen( const char *mask, const char *port ) {
do_once udp_init();
int fd = swrapSocket(SWRAP_UDP,SWRAP_BIND,SWRAP_NOBLOCK,mask,port);
return fd;
}
int osc_update(int fd, int timeout_ms /*= -1*/) {
do_once udp_init();
static char *buf = 0; // [OSC_MAX_BUF];
static int bufpos;
if( !buf ) {
buf = CALLOC( 1, OSC_MAX_BUF );
msg = CALLOC( 1, OSC_MAX_MESSAGES * sizeof(struct osc_message) );
}
if(fd<0) return 0;
/* check if something is available */
msgpos=0;
if( timeout_ms >= 0 ) {
int ret = swrapSelect(fd, timeout_ms / 1000.0);
if (ret <= 0) { // error, or timeout
return 0;
}
}
for (msgpos=0,bufpos=0;msgpos<OSC_MAX_MESSAGES && bufpos<OSC_MAX_BUF-8;) {
int n = swrapReceive(fd, buf+bufpos, OSC_MAX_BUF-bufpos-1);
if( n <= 0 ) {
#ifdef _WIN32
//if (n == -1 && WSAGetLastError() == WSAEINTR) continue;
if( WSAGetLastError() != WSAEINTR && /*WSAGetLastError() != WSAEWOULDBLOCK &&*/
WSAGetLastError() != WSAECONNRESET && WSAGetLastError() != WSAECONNREFUSED ) {
// error: %d, WSAGetLastError();
return 0;
}
#else
//if (n == -1 && errno == EINTR) continue;
if( errno != EAGAIN && errno != EINTR && /*errno != EWOULDBLOCK &&*/
errno != ECONNRESET && errno != ECONNREFUSED ) {
// error: %s, strerror(errno));
return 0;
}
#endif
continue;
}
char *s=buf+bufpos;
s[n]=0; // null terminate packet always, for easier c handling
msgpos+=osc_parse_(msg+msgpos,OSC_MAX_MESSAGES-msgpos,s,s+n);
bufpos+=n+1;
}
return 1;
}
int osc_list(const osc_message **first) {
*first = msg;
return msgpos;
}
const osc_message *osc_find(const char *addr) { // search in reverse order, so newest wins
for (int i=msgpos;i-->0;) if (osc_match_(msg[i].pattern, addr)) return &msg[i];
return 0;
}
int osc_debug(FILE *fp, const char *buf, int len) {
osc_message m[16];
int nn = osc_parse_(m, 16, buf, buf+len);
for( int n = 0; n < nn; ++n ) {
fprintf(fp, "%s [%s]", m[n].pattern, m[n].types);
// @todo #bundle @%lld
for(int i = 0; m[n].types[i]; ++i) {
char f = m[n].types[i];
/**/ if (f == 'T' || f == 'F' || f == 'N') fprintf( fp, ",%s", (f=='T'?"True":f=='F'?"False":"Null"));
else if (f == 'h' || f == 't') fprintf( fp, ",%lld", m[n].v[i].i);
else if (f == 'f' || f == 'd') fprintf( fp, ",%f", m[n].v[i].f);
else if (f == 'i' || f == 'c' || f == 'r' || f == 'm' ) fprintf( fp, ",%d", (int)m[n].v[i].i);
else if (f == 's' || f == 'S' ) fprintf( fp, ",%s", m[n].v[i].s);
else if (f == 'b') fprintf( fp, ",%d bytes", (int)m[n].v[i].i);
else fprintf(fp, ",%s", "?");
}
fprintf(fp, "%s\n", "");
}
return 1;
}
#ifdef OSCRECV_DEMO
int main() {
// @mmalex's tests
assert( osc_match_("/[a-c]/?/[abc]/*/fish*/*food/f*/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/bar") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fish*/*food/f*/{fog,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fith*/*food/f*/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fish*/*good/f*/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( !osc_match_("/[a-c]/?/[abc]/*/fish*/*food/g/{foo,bar,baz}","/a/b/c//fishfood/monkeyfood/f/foo") );
assert( osc_match_("/[fa-cd]/?/[abc]/*/fish*/*food/f*/{foo,bar,baz}","/d/b/c//fishfood/monkeyfood/f/bar") );
// Julien Pommier's oscpkt tests
assert( !osc_match_("//bar", "bar"));
// assert( osc_match_("//bar", "/bar"));
// assert( osc_match_("//bar", "/foo/plop/bar"));
// assert( osc_match_("/foo//", "/foo/plop/df/"));
// assert( osc_match_("/foo///////bar", "/foo/plop/baz/bar"));
assert( osc_match_("*", "bar"));
assert( osc_match_("/foo/*", "/foo/bar"));
// assert( osc_match_("/{bar,fo}/b[aA]r", "/fo/bar"));
// assert( !osc_match_("/{bar,fo}/b[aA]r", "/foo/bar"));
// assert( osc_match_("/fo{bar,}/ba[e-t]", "/fo/bar"));
assert( !osc_match_("/fo{bar,}/ba[t-z]", "/fo/bar"));
// assert( osc_match_("/f{,ioio,bar}o/?a[!a]", "/fo/bar"));
assert( osc_match_("/foo/bar", "/foo/bar"));
assert( osc_match_("/f*o/bar", "/foo/bar"));
assert( osc_match_("/fo*o/bar", "/foo/bar"));
// assert( osc_match_("/*//bar", "/foo/bar"));
assert( osc_match_("/*/bar", "/foo/bar"));
assert( osc_match_("/*o/bar", "/foo/bar"));
assert( osc_match_("/*/*/*/*a***/*/*/*/*/", "/foo/bar/foo/barrrr/foo/bar/foo/barrrr/"));
assert( !osc_match_("/*/*/*/**/*/*/*/*/q", "/foo/bar/foo/barrrr/foo/bar/foo/barrrr/p"));
assert( osc_match_("[-]", "-"));
// assert( osc_match_("[a-]", "a"));
// assert( osc_match_("[a-]", "-"));
int fd = osc_listen( "127.0.0.1", "9000" );
if( fd ) for(;;) {
static unsigned char counter = 0;
printf("\r127.0.0.1:9000 %c", "\\|/-"[ counter = (counter+1) & 3 ]);
Sleep(100);
osc_update(fd);
const osc_message *begin;
for( int it = 0, end = osc_list(&begin); it < end; ++it ) {
const osc_message *msg = begin + it;
printf("> %s [%s]\n", msg->pattern, msg->types);
}
}
}
#endif

View File

@ -0,0 +1,28 @@
#include "fwk.h"
#include "oscsend.h"
#include "oscedit.h"
int main(int argc, char **argv) {
if( argc > 1 ) {
char *address = argv[1];
char *arg = 0; strcatf(&arg, "%s", ""); for(int i = 2; i < argc; ++i) strcatf(&arg, "%s", argv[i]);
char *message = 0;
if( !arg[0] ) message = osc_pack_va(address, "");
else if( strchr("\'\"`", arg[0]) ) message = osc_pack_va(address, "s", (arg[strlen(arg)-1] = '\0', arg+1) );
else if( !strcmp(arg, "true") || !strcmp(arg, "false") ) message = osc_pack_va(address, arg[0] == 't' ? "T" : "F");
else if( strchr(".+-0123456789", arg[0]) && strpbrk(arg, ".fe") ) message = osc_pack_va(address, "f", atof(arg));
else if( strchr( "+-0123456789", arg[0]) && strspn(arg+1, "0123456789") == strlen(arg)-1 ) message = osc_pack_va(address, "i", atoi(arg));
else message = osc_pack_va(address, "s", arg );
hexdump( message+4, *(int*)message );
int socket = osc_open("127.0.0.1", OSC_EDIT_PORT);
printf("%d bytes sent\n", osc_send( socket, message+4, *(int*)message ));
} else {
printf("%s /osc/address/ [argtype]\n", argv[0]);
printf("argtypes: [false][true][int][float][string]\n\n","\t");
printf("example: %s /player/time 5.5\n", argv[0]);
printf("example: %s /player/health 100\n", argv[0]);
printf("example: %s /player/active true\n", argv[0]);
printf("example: %s /player/fullname \"john doe\"\n", argv[0]);
}
}

View File

@ -0,0 +1,121 @@
#pragma once
#define osc_open udp_open
#define osc_send udp_send
#define osc_close udp_close
// rgb_formats:
// - 888 (RGB 3bytes/pixel),
// - 332 (RGB 1byte/pixel),
// - 242 (RGB 1byte/pixel),
// - 7755 (YCoCg 3bytes/2pixels)
API void osc_send_fb( void *pixels, int w, int h, int comp, int rgb_format );
// usage:
// void* rgb = render_capture(3);
// int rgbmode = 332; // 7755,242,332,888 safest+slowest
// osc_send_fb(rgb, window_width(), window_height(), 3, rgbmode); // 242 crash); 7755 green);
//viewport_clear(false, true);
//ddraw_frame(NULL);
//ddraw_axis(10);
#pragma once
#include "oscpack.h"
void osc_send_fb( void *pixels, int w, int h, int comp, int rgb_format ) {
static int s, *init = 0;
if( !init ) { init = &s; s = osc_open("127.0.0.1", "9000"); }
if( s <= 0 ) return;
if( !pixels ) return;
if( (w * h * comp) <= 0 ) return;
unsigned char* line_a = (unsigned char *)pixels;
unsigned char* line_b = (unsigned char *)pixels + (0 + (h - 1) * w) * comp;
unsigned char* line;
static unsigned char activity = 0;
static unsigned num_sent_packets = 0;
static char *oscbuf = 0;
if( !oscbuf ) oscbuf = (char*)MALLOC( 8192 * 4 + 64 ); // 8K RGBA32 max + some room for osc headers
bool sending = 0;
int stride = w * comp;
line = line_b + stride;
if( rgb_format == 888 ) for( int y = 0; y < h; ++y ) { line -= stride;
int osclen = osc_pack( oscbuf, "/render/", "iiiib", w,h,rgb_format, y, w*3,line);
bool sent = osc_send( s, oscbuf + 4, osclen - 4 );
num_sent_packets += sent; sending |= sent;
}
if( rgb_format == 332 ) for( int y = 0; y < h; ++y ) { line -= stride;
for( int x = 0; x < w; ++x ) {
unsigned char r = line[x*3+0], g = line[x*3+1], b = line[x*3+2];
line[x] = (( r >> 5 ) << 5) | (( g >> 5 ) << 2) | (( b >> 6 ) << 0);
}
int osclen = osc_pack( oscbuf, "/render/", "iiiib", w,h,rgb_format, y, w,line);
bool sent = osc_send( s, oscbuf + 4, osclen - 4 );
num_sent_packets += sent; sending |= sent;
}
if( rgb_format == 242 ) for( int y = 0; y < h; ++y ) { line -= stride;
for( int x = 0; x < w; ++x ) {
unsigned char r = line[x*3+0], g = line[x*3+1], b = line[x*3+2];
line[x] = (( r >> 6 ) << 6) | (( g >> 4 ) << 2) | (( b >> 6 ) << 0);
}
int osclen = osc_pack( oscbuf, "/render/", "iiiib", w,h,rgb_format, y, w,line);
bool sent = osc_send( s, oscbuf + 4, osclen - 4 );
num_sent_packets += sent; sending |= sent;
}
if( rgb_format == 7755 ) for( int y = 0; y < h; ++y ) { line -= stride;
/**/ if( (w % 2) == 0 ) {}
else if( (w % 2) == 1 ) w -= 1;
assert( (w % 2) == 0 );
uint8_t *out = line;
for( int x = 0, i = 0; i < w/2; ++i, ++x ) {
// (/2>>1 /4>>2 /8>>3 /16>>4 /32>>5 /64>>6 /128>>7)
// Y Y CoCg (7 7 5 5) : 3bytes == 2px !!wow!!
int32_t r,g,b,y0,y1,co0,co1,cg0,cg1;
r = line[x*6+0], g = line[x*6+1], b = line[x*6+2];
y0 = r/4 + g/2 + b/4 + 1; // +1 to avoid some ycocg2rgb overflows
co0 = r/2 - b/2;
cg0 = g/2 - r/4 - b/4;
r = line[x*6+3], g = line[x*6+4], b = line[x*6+5];
y1 = r/4 + g/2 + b/4 + 1; // +1 to avoid some ycocg2rgb overflows
co1 = r/2 - b/2;
cg1 = g/2 - r/4 - b/4;
int32_t co = (co0+co1)/2 + 128, cg = (cg0+cg1)/2 + 128;
// 7 7 5 5
uint8_t y0_7 = y0 / 2, y1_7 = y1 / 2, co_5 = co / 8, cg_5 = cg / 8;
uint32_t pack24 = (y0_7<<17)|(y1_7<<10)|(co_5<<5)|(cg_5<<0);
*out++ = ( pack24 >> 16 ) & 255;
*out++ = ( pack24 >> 8 ) & 255;
*out++ = ( pack24 >> 0 ) & 255;
// other packed options
// YCoCg (4 4 4) : 12bits == 1px
// YCoCg (2 3 3) : 1byte == 1px
// Y CoCg (8 4 4) : 2bytes == 1px
// Y Y CoCg (5 5 3 3) : 2bytes == 2px (meh)
// Y Y CoCg (8 8 4 4) : 3bytes == 2px
// Y Y CoCg (7 7 5 5) : 3bytes == 2px (wow!)
// Y Y Y CoCg (8 8 8 4 4) : 4bytes == 3px
// Y Y Y CoCg (7 7 7 5 6) : 4bytes == 3px
// Y Y Y CoCg (6 6 6 7 7) : 4bytes == 3px
// Y Y Y Y CoCg (7 7 7 7 6 6) : 5bytes == 4px
// Y Y Y Y CoCg (6 6 6 6 8 8) : 5bytes == 4px
}
int osclen = osc_pack( oscbuf, "/render/", "iiiib", w,h,rgb_format, y, w*2,line);
bool sent = osc_send( s, oscbuf + 4, osclen - 4 );
num_sent_packets += sent; sending |= sent;
}
activity += sending;
// printf("\r%c netsend %d", "\\|/-"[ activity % 4 ], num_sent_packets);
// stbi_write_png("out.png", w, h, 3, pixels, w * 3);
}

View File

@ -0,0 +1,425 @@
// fast simple ecs
// - rlyeh, public domain
//
// features:
// - mostly heap allocation free
// - 2^64 systems max
// - 2^64 entities max
//
// cons:
// - 64 components max
// - memory requirements may be suboptimal. ie, using unions for the components: all components will equally size like the largest component.
//
// @note:
// - best perf when compiled with `/DNDEBUG /openmp /arch:AVX2 /Os /Ox /Gw /GL /MT`
// - runs 86M ops/sec on my old laptop: updates 50K entities out of 100K in 0.58ms/frame. 1000 frames in 0.57s
//
// @todo:
// - thread-safe
// - world/context management
// - dont use arrays: sequential access is killing performances when doing huge amount of entities (10M). worthy?
// compromise: use islands. 10K entities per island seems reasonable & best perf. the lower the better.
// we could parallelize islands as well (openmp? threads?).
// - file format spec:
// ; ecs data file format (.ini)
// [entity]
// components = mesh aabb
// position = 0 0 0 ; common
// rotation = 0 0 0 ; common
// scale = 2 2 2 ; common
// aabb.static = 1
// aabb.size = 3 3 3
// aabb.offset = 0 0 0
// mesh.model = cube
// mesh.texture = wood_03
// mesh.texture.tiling = 0.5 0.5
// - file format parser:
// for each_map_sorted_ptr(ini("ecs.ini"), char *, k, char *, v)
// printf("'%s'='%s'\n", *k, *v += strspn(*v, " "));
//
/* api */
#ifdef _OPENMP
# ifdef _MSC_VER
# define parallel __pragma(omp parallel for)
# define has_parallel 1
# else // __GNUC__
# define parallel _Pragma("omp parallel for") // C99
# define has_parallel 1
# endif
#else
# define parallel
# define has_parallel 0
#endif
void ecs_max_components(int max_components);
#define ecs_dump_world(fp, ...) ecs_dump_world(fp,ecs_mask64(__VA_ARGS__,-1))
#define ecs_add_entity(...) ecs_add_entity(ecs_mask64(__VA_ARGS__,-1))
union component_t* ecs_get_component( int eid, int cid );
union component_t** ecs_get_components( int eid );
#define ecs_has_component(eid,...) ecs_has_component(eid,ecs_mask64(__VA_ARGS__,-1))
#define ecs_add_component(eid,...) ecs_add_component(eid,ecs_mask64(__VA_ARGS__,-1))
#define ecs_del_component(eid,...) ecs_del_component(eid,ecs_mask64(__VA_ARGS__,-1))
#define ecs_use_component(eid,...) ecs_use_component(eid,ecs_mask64(__VA_ARGS__,-1))
#define ecs_off_component(eid,...) ecs_off_component(eid,ecs_mask64(__VA_ARGS__,-1))
#define each_ecs_component(obj, ...) \
( uint64_t sys_ = ecs_mask64(__VA_ARGS__,-1), ent_ = 0; ent_ < world.ne; ++ent_ ) \
for( component_t **obj = sys_ == (sys_ & world.entities[ent_ * 2 + 0]) ? ecs_get_components(ent_) : 0; obj ; obj = 0 )
#if has_parallel
#undef each_ecs_component
#define each_ecs_component(obj, ...) \
( ent_ = (sys_ = ecs_mask64(__VA_ARGS__,-1), 0); ent_ < world.ne; ++ent_ ) \
for( component_t **obj = sys_ == (sys_ & world.entities[ent_ * 2 + 0]) ? ecs_get_components(ent_) : 0; obj ; obj = 0 )
static int64_t ent_;
static uint64_t sys_;
#endif
/* internals */
typedef union component_t {
struct dummy { int dummy; };
#ifdef COMPONENT_HEADER
#include COMPONENT_HEADER
#endif
#ifdef COMPONENT_DATAS
COMPONENT_DATAS
#endif
#if defined ECS_DEMO || defined ECS_BENCH
struct position { float x,y,z; }; // c1
struct velocity { float vx,vy,vz; }; // c2
struct color { float r,g,b; }; // c3
struct health { float health; }; // c4
char *name; // c5
#endif
} component_t;
struct world_t {
int ne, nc; // ne: number of entities, nc: number of components (stride) (cN)
array(uint64_t) entities; // vtable entities (2 entries/entity) [ e1(cflags,offset) e2(cflags,offset) .. eN(cflags,offset) ]
array(component_t) components; // instanced components (nc entries/entity) [ e1(c1,c2..) e2(c1,c2..) .. ]
};
uint64_t ecs_mask64( unsigned id1, ... );
int (ecs_add_entity)( uint64_t component_mask );
bool (ecs_has_component)( int eid, uint64_t flags );
bool (ecs_add_component)( int eid, uint64_t flags );
bool (ecs_del_component)( int eid, uint64_t flags );
bool (ecs_use_component)( int eid, uint64_t flags );
bool (ecs_off_component)( int eid, uint64_t flags );
void (ecs_dump_world)( FILE *fp, uint64_t component_mask );
extern struct world_t world;
// impl
// static
struct world_t world = {0};
uint64_t ecs_mask64(unsigned id1, ... ) {
uint64_t flags = 0;
// update flags and entities of components
va_list ap;
va_start(ap, id1);
for( uint64_t id = id1; id != ((unsigned)-1); id = va_arg(ap, unsigned) ) {
flags |= 1ull << id;
}
va_end(ap);
return flags;
}
int (ecs_add_entity)( uint64_t component_mask ) {
component_t c = {0};
// add mask+offset into entities
array_push(world.entities, component_mask);
array_push(world.entities, array_count(world.components));
// add components into entity
for (uint64_t cid = 0; cid < world.nc; ++cid) {
if( (1ull << cid) & component_mask ) {
array_push( world.components, c );
}
}
int eid = world.ne;
return world.ne++;
}
void ecs_max_components(int max_comps) {
world.nc = max_comps;
}
component_t* ecs_get_component( int eid, int target_cid ) {
uint64_t sys = world.entities[ eid * 2 + 0 ];
uint64_t off = world.entities[ eid * 2 + 1 ];
for( uint64_t cid = 0; cid < target_cid; ++cid ) {
off += !!((1ull << cid) & sys);
}
bool has_cid = !!((1ull << target_cid) & sys);
return (component_t*)(has_cid * (uintptr_t)(&world.components[ off ]));
}
component_t** ecs_get_components( int eid ) {
static __thread component_t* local[8][64] = {0};
static __thread int counter = 0; counter = (counter + 1) % 8;
uint64_t sys = world.entities[ eid * 2 + 0 ];
uint64_t off = world.entities[ eid * 2 + 1 ];
for( uint64_t cid = 0, idx = 0; cid < world.nc; ++cid ) {
bool has_cid = !!((1ull << cid) & sys);
local[counter][ cid ] = (component_t*)(has_cid * (uintptr_t)(&world.components[ off ] + idx));
idx += has_cid;
}
return local[counter];
}
bool (ecs_has_component)( int eid, uint64_t flags ) {
uint64_t sys = world.entities[ eid * 2 + 0 ];
return flags == (sys & flags);
}
bool (ecs_add_component)( int eid, uint64_t flags ) {
uint64_t sys = world.entities[ eid * 2 + 0 ];
uint64_t off = world.entities[ eid * 2 + 1 ];
int eid2 = (ecs_add_entity)( sys | flags );
uint64_t sys2 = world.entities[ eid2 * 2 + 0 ];
uint64_t off2 = world.entities[ eid2 * 2 + 1 ];
for( uint64_t cid = 0, idx = 0, idx2 = 0; cid < world.nc; ++cid) {
if( (1ull << cid) & sys ) {
if( (1ull << cid) & sys2 ) {
memcpy( &world.components[ off2 ] + idx2, &world.components[ off ] + idx, sizeof(component_t));
}
}
if( (1ull << cid) & sys ) ++idx;
if( (1ull << cid) & sys2 ) ++idx2;
}
world.entities[ eid * 2 + 0 ] = sys2;
world.entities[ eid * 2 + 1 ] = off2;
world.entities[ eid2 * 2 + 0 ] = 0;
world.entities[ eid2 * 2 + 1 ] = 0;
return true;
}
bool (ecs_del_component)( int eid, uint64_t flags ) { // clr_component?
uint64_t sys = world.entities[ eid * 2 + 0 ];
uint64_t off = world.entities[ eid * 2 + 1 ];
for( uint64_t cid = 0, idx = 0; cid < world.nc; ++cid) {
if( (1ull << cid) & flags ) {
memset( &world.components[ off ] + idx, 0, sizeof(component_t));
}
if( (1ull << cid) & sys ) ++idx;
}
world.entities[ eid * 2 + 0 ] &= ~flags;
return true;
}
bool (ecs_use_component)( int eid, uint64_t flags ) {
world.entities[ eid * 2 + 0 ] |= flags;
return true;
}
bool (ecs_off_component)( int eid, uint64_t flags ) {
world.entities[ eid * 2 + 0 ] &= ~flags;
return true;
}
void (ecs_dump_world)( FILE *fp, uint64_t sys_mask ) {
if( sys_mask ) for( int eid = 0; eid < world.ne; ++eid ) {
uint64_t sys = world.entities[ eid * 2 + 0 ];
uint64_t off = world.entities[ eid * 2 + 1 ];
if( sys_mask != (sys & sys_mask)) continue;
fprintf(fp, "eid:%d sys:%#x ", eid, (unsigned)sys);
for( uint64_t cid = 0, idx = 0; cid < world.nc; ++cid ) {
if( (1ull << cid) & sys ) {
fprintf(fp, "cid:%d,%p+%d ", (int)cid, &world.components[ off ], (int)idx );
++idx;
}
}
fputc('\n', fp);
}
}
#ifdef ECS_BENCH
#include <stdio.h>
#include <assert.h>
int main(int argc, char **argv) {
int player;
/* entities to spawn */
#ifndef ECS_N
const int ECS_N = argc > 1 ? atoi(argv[1]) : 100000;
#endif
/* frames to benchmark */
#ifndef ECS_F
const int ECS_F = argc > 2 ? atoi(argv[2]) : 1000;
#endif
// declare components: c1, c2, ...
enum { POSITION, VELOCITY, COLOR, HEALTH, INPUT, NAME, TOTAL };
ecs_max_components(TOTAL);
// spawn entities
{
double start = time_ss();
player = ecs_add_entity(NAME, POSITION, VELOCITY, HEALTH, INPUT);
ecs_get_component(player, VELOCITY)->vx = 1;
ecs_get_component(player, VELOCITY)->vy = 2;
for (int i = 0; i < ECS_N; ++i) {
switch (i & 3) {
break; case 0:; /* static enemy */
int enemy0 = ecs_add_entity(NAME, POSITION, COLOR, HEALTH);
break; case 1:; /* dynamic enemy */
int enemy1 = ecs_add_entity(NAME, POSITION, COLOR, HEALTH, VELOCITY);
break; case 2:; /* static light */
int light0 = ecs_add_entity(NAME, POSITION, COLOR);
break; case 3:; /* dynamic light */
int light1 = ecs_add_entity(NAME, POSITION, COLOR, VELOCITY);
}
}
double end = time_ss();
double t = (end - start);
int T = 1 * ECS_N;
printf("%17s: %d frame(s) * %d num_entities = %d total ops, in %.3fs => %.3fM ops/s, %.2fms/frame\n",
"spawn benchmark", 1, ECS_N, T, t, (T / 1000000.0) / t, (t * 1000 / 1) );
}
// process & benchmark
{
double start = time_ss();
for( int frame = 0; frame < ECS_F; ++frame ) {
parallel
for each_ecs_component(obj, POSITION, VELOCITY) {
component_t *p = obj[POSITION];
component_t *v = obj[VELOCITY];
p->x += v->vx;
p->y += v->vy;
p->z += v->vz;
}
}
double end = time_ss();
double t = (end - start);
// stats
int num_iterated_entities = ECS_N;
int num_processed_entities = 0;
for each_ecs_component(obj, POSITION, VELOCITY) {
++num_processed_entities;
}
int T = ECS_F * num_processed_entities;
printf("%17s: %d frame(s) * %d num_entities = %d total ops, in %.3fs => %.3fM ops/s, %.2fms/frame\n",
"process benchmark", ECS_F, num_processed_entities, T, t, (T / 1000000.0) / t, (t * 1000 / ECS_F) );
}
{component_t *p = ecs_get_component(player, POSITION);
printf("eid:%d (position: %f,%f,%f)\n", player, p->x, p->y, p->z );}
assert( ecs_get_component(player, POSITION)->x == (ECS_F * 1));
assert( ecs_get_component(player, POSITION)->y == (ECS_F * 2));
assert( ~puts("Ok") );
}
#endif
#ifdef ECS_DEMO
#include <stdio.h>
#include <assert.h>
int main(int argc, char **argv) {
// declare components: c1, c2, ...
enum { POSITION, VELOCITY, COLOR, HEALTH, INPUT, TOTAL };
ecs_max_components(TOTAL);
// spawn entities
int player = ecs_add_entity(POSITION, VELOCITY, HEALTH, INPUT);
ecs_get_component(player, VELOCITY)->vx = 1;
ecs_get_component(player, VELOCITY)->vy = 2;
// some more
int enemy0, enemy1, light0, light1;
for (int i = 0; i < 10; ++i) {
if( 0 == (i&3)) /* static enemy */ enemy0 = ecs_add_entity(POSITION, COLOR, HEALTH);
if( 1 == (i&3)) /* dynamic enemy */ enemy1 = ecs_add_entity(POSITION, COLOR, HEALTH, VELOCITY);
if( 2 == (i&3)) /* static light */ light0 = ecs_add_entity(POSITION, COLOR);
if( 3 == (i&3)) /* dynamic light */ light1 = ecs_add_entity(POSITION, COLOR, VELOCITY), ecs_get_component(light1, VELOCITY)->vy = 2;
}
{
component_t *p = ecs_get_component(player, POSITION);
printf("eid:%d (position: %f,%f,%f)\n", player, p->x, p->y, p->z );
p = ecs_get_component(player, VELOCITY);
printf("eid:%d (velocity: %f,%f)\n", player, p->vx, p->vy );
p = ecs_get_component(light1, POSITION);
printf("eid:%d (position: %f,%f,%f)\n", light1, p->x, p->y, p->z );
p = ecs_get_component(light1, VELOCITY);
printf("eid:%d (velocity: %f,%f)\n", light1, p->vx, p->vy );
}
// simulate system processing
int frames = 1000;
for( int frame = 0; frame < frames; ++frame ) {
for each_ecs_component(obj, POSITION, VELOCITY) {
component_t *p = obj[POSITION];
component_t *v = obj[VELOCITY];
p->x += v->vx;
p->y += v->vy;
p->z += v->vz;
}
}
// verify (should display only player entity)
ecs_dump_world(stdout, INPUT);
{
component_t *p = ecs_get_component(player, POSITION);
printf("eid:%d (position: %f,%f,%f)\n", player, p->x, p->y, p->z );
p = ecs_get_component(light1, POSITION);
printf("eid:%d (position: %f,%f,%f)\n", light1, p->x, p->y, p->z );
}
assert( ecs_get_component(player, POSITION)->x == (frames * 1));
assert( ecs_get_component(player, POSITION)->y == (frames * 2));
assert( ecs_has_component(player, POSITION) );
assert(!ecs_has_component(player, COLOR) );
assert( ecs_add_component(player, COLOR) );
assert( ecs_has_component(player, COLOR) );
assert( ecs_off_component(player, POSITION) );
assert(!ecs_has_component(player, POSITION) );
assert( ecs_get_component(player, POSITION) == NULL);
assert( ecs_use_component(player, POSITION) );
assert( ecs_has_component(player, POSITION) );
assert( ecs_get_component(player, POSITION)->x == (frames * 1));
assert( ecs_del_component(player, POSITION) );
assert(!ecs_has_component(player, POSITION) );
assert( ecs_get_component(player, POSITION) == NULL );
assert( ~puts("Ok") );
}
#endif

View File

@ -0,0 +1,25 @@
# Fair hybrid P2P model (rlyeh, public domain)
## Abstract
In gamedev, the problem with p2p is that decision-making falls to individuals, and they can unbalance the balance in their very own favor. For this reason, the client/server model is usually preferred, with the servers (and $maintenance$) being handled by the company that hosts the game.
## Theory
Following hybrid p2p system would be one in which a game is made up of many clusters of clients that play different games simultaneously, but in which the peers that act as referees actually belong to a different game, in such a way that a peer cannot Referee their own game. Basically if a peer is in a game-A with A-peers, it can only arbitrate a game-B with B-peers, all this while peer is also playing their game with other A peers.
In addition, in order for peer A to arbitrate and initiate a game-B of B-peers, all nodes of B must send the source code of the server (with the rules of the game) to peer A for further validation. Once the source code is validated (sources must match for all the involved nodes), server-A will process the traffic of game-B, but note that server does not care about the rules of B, neither what the traffic of B looks like, and in fact, server-A does not know or care what peers in game-B are doing... what peer A does know as a player, however, is everything about game-A, whose refereers are in fact random external players to A set.
## Appendix and notes
```
game A game B
[ player12 player16* player45 server33* player08 ] [ player49 player09 player33* server16* ]
```
- 16 is a player in A, and a refeerer in B. 33 is a player in B, and a refeerer in A.
- 33 agreed to arbitrate game A after checking data & code consensus for players { 12, 16, 45, 08 }.
- 16 agreed to arbitrate game B after checking data & code consensus for players { 49, 09, 33 }.
- traffic could be encrypted. public key could be a deterministic hash of all code+datas involved.
- peers could use an anonymous karma score system: positive for peers completing game sessions, negative for those peers cheating and/or with communication issues.

View File

@ -0,0 +1,92 @@
// minimalist network proto/scheme that resembles a .ini file.
// - rlyeh, public domain
## features
```json
types:
floating
integer
string
signed:
yes/no
sizes:
1,8,12,15,16,24,32,64,...
containers:
lists
maps (can be simulated by using two contiguous lists of keys and values)
blocks:
enum
union (can be extended)
struct (can be extended)
options:
zigzag, decay (double->half), packed, default,
messaging:
numbered fields
optional
required
repeated
extensions
```
## binary format
```ini
[cellsize in bytes:8]
[num key-values:cellsize]
[key id:cellsize][value id:cellsize]
[...]
[num types:cellsize]
[type class:cellsize][num items:cellsize][item1][item2][...]
[...] // would be nice to put strings at the very end, because of their length-variable nature
```
## text format
```ini
// namespace
[[Demo]]
// free-standing options
package = "tutorial"
version = 123
pi = 3.14159
[PhoneType] // enumeration: all members are uppercase + no need for explicit values.
HOME
WORK
OTHER = HOME
MOBILE
[PhoneNumber] // struct: variables never share same field number (1!=2)
1: number = 0 // .field 1, .id number, .value 0, .type Integer
2: phonetype = OTHER? // .field 2, .id phonetype, .value OTHER, .type PhoneType, .optional
[Name] // union: some variables may share a very same field number (1==1)
1: name = "" // .field 1, .id name, .value "", .type String
1: nickname = "" // .field 1, .id nickname, .value "", .type String
[Profile] // struct
1: name = Name... // .field 1, .id name, .type Name, .repeat
2: icon = "guest.png"? // .field 2, .id icon, .type String, .value = "guest.png", .optional
[Contact] // struct
1: person = Profile
2: card_id = 0 bits:15 // .default = 0, .bits = 15
3: email = ""? // email is an optional string located in the third message field that defaults to an empy string
4: phone = PhoneType... // .repeat
5: calls = 0... bits:64 packed // .default = 0, .repeat, .bits = 64, .packed
10..max: // user-defined, reserved extensions
[ContactExt <- Contact] // struct that extends from Contact
1: phone_ext_number = 0?
[AddressBook] // struct
1: contact = ContactExt...
```

View File

View File

@ -0,0 +1,12 @@
[Outliner]
x=0.000000
y=0.045390
w=0.250208
h=0.953120
visible=1
[Properties]
x=0.780381
y=0.045390
w=0.219619
h=0.953154
visible=1

22
tools/lua/LICENSE 100644
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Dmitry Ivanov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,55 @@
# premake-ninja
[Premake](https://github.com/premake/premake-core) extension to support [Ninja](https://github.com/martine/ninja), because it's awesome. [![Build Status](https://travis-ci.org/jimon/premake-ninja.svg?branch=master)](https://travis-ci.org/jimon/premake-ninja) [![Build status](https://ci.appveyor.com/api/projects/status/3d2vot6y72nfo24y/branch/master?svg=true)](https://ci.appveyor.com/project/jimon/premake-ninja/branch/master)
### Implementation
For each project - configuration pair we create separate .ninja file. For solution we create build.ninja file which imports other .ninja files with subninja command.
Build.ninja file sets phony targets for configuration names so you can build them from command line. And default target is a first configuration name in your project (usually default).
### TODO
- Resources are not supported
- Makefile not supported
- Bundles of any sort are not supported
- Clear methods are not supported
- C# not supported
- D not supported
### Tested on
- msvc / win
- ConsoleApp works
- WindowedApp works
- StaticLib works
- SharedLib works
- win64 not tested
- clang / win
- not tested
- clang / linux
- not tested
- clang / osx
- ConsoleApp works
- WindowedApp works
- StaticLib works
- SharedLib works
- x86_64 tested, x86 not tested
- gcc / win
- ConsoleApp works
- WindowedApp works
- StaticLib works
- SharedLib works
- x86_64 tested, x86 not tested
- gcc / linux (tested on Ubuntu 15.04)
- ConsoleApp works
- WindowedApp works
- StaticLib works
- SharedLib works
- PCH works
- Custom build rules works
- x86_64 tested, x86 not tested
### Extra Tests
Part of integration tests of several generators in https://github.com/Jarod42/premake-sample-projects

View File

@ -0,0 +1,4 @@
return {
"_preload.lua",
"ninja.lua",
}

View File

@ -0,0 +1,62 @@
--
-- Name: premake-ninja/_preload.lua
-- Purpose: Define the ninja action.
-- Author: Dmitry Ivanov
-- Created: 2015/07/04
-- Copyright: (c) 2015 Dmitry Ivanov
--
local p = premake
newaction
{
-- Metadata for the command line and help system
trigger = "ninja",
shortname = "ninja",
description = "Ninja is a small build system with a focus on speed",
-- The capabilities of this action
valid_kinds = {"ConsoleApp", "WindowedApp", "SharedLib", "StaticLib", "None"}, -- Not supported: Makefile, Packaging, SharedItems, Utility
valid_languages = {"C", "C++"},
valid_tools = {cc = { "gcc", "clang", "msc" }},
toolset = "gcc",
-- Workspace and project generation logic
onWorkspace = function(wks)
p.eol("\r\n")
p.indent(" ")
p.escaper(p.modules.ninja.esc)
p.generate(wks, "build.ninja", p.modules.ninja.generateWorkspace)
end,
onProject = function(prj)
p.eol("\r\n")
p.indent(" ")
p.escaper(p.modules.ninja.esc)
p.modules.ninja.generateProject(prj)
end,
onBranch = function(prj)
p.eol("\r\n")
p.indent(" ")
p.escaper(p.modules.ninja.esc)
p.modules.ninja.generateProject(prj)
end,
onCleanSolution = function(sln)
-- TODO
end,
onCleanProject = function(prj)
-- TODO
end,
onCleanTarget = function(prj)
-- TODO
end,
}
--
-- Decide when the full module should be loaded.
--
return function(cfg)
return (_ACTION == "ninja")
end

View File

@ -0,0 +1,23 @@
version: 0.1.{build}
environment:
global:
PYTHON: "C:\\Python35"
install:
- set PATH=%cd%/.bins;%PYTHON%;%PYTHON%/Scripts;%PATH%
- python --version
- mkdir -p .bins
- cd .bins
- powershell "(New-Object Net.WebClient).DownloadFile('https://github.com/premake/premake-core/releases/download/v5.0.0-alpha6/premake-5.0.0-alpha6-windows.zip','premake-5.0.0-alpha6-windows.zip')"
- powershell "(new-object -com shell.application).namespace($pwd.Path).CopyHere((new-object -com shell.application).namespace(\"$pwd\premake-5.0.0-alpha6-windows.zip\").Items(),16)"
- powershell "(New-Object Net.WebClient).DownloadFile('https://github.com/martine/ninja/releases/download/v1.6.0/ninja-win.zip','ninja.zip')"
- powershell "(new-object -com shell.application).namespace($pwd.Path).CopyHere((new-object -com shell.application).namespace(\"$pwd\ninja.zip\").Items(),16)"
- premake5 --version
- ninja --version
- cd ..
build: off
test_script:
- call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
- cd tests
- python run_tests.py
- cd ..

Some files were not shown because too many files have changed in this diff Show More