Merge remote-tracking branch 'fwk/main'
commit
8550b12757
85
MAKE.bat
85
MAKE.bat
|
@ -70,7 +70,7 @@ while [ $# -ge 1 ]; do
|
|||
echo sh MAKE.bat [amalgamation]
|
||||
echo sh MAKE.bat [prep]
|
||||
echo sh MAKE.bat [cook]
|
||||
echo sh MAKE.bat [sln]
|
||||
echo sh MAKE.bat [proj]
|
||||
exit
|
||||
fi
|
||||
if [ "$1" = "dll" ]; then
|
||||
|
@ -100,7 +100,7 @@ while [ $# -ge 1 ]; do
|
|||
if [ "$1" = "tcc" ]; then
|
||||
export cc="tcc -D__STDC_NO_VLA__"
|
||||
fi
|
||||
if [ "$1" = "sln" ]; then
|
||||
if [ "$1" = "proj" ]; then
|
||||
if [ "$(uname)" != "Darwin" ]; then
|
||||
chmod +x tools/premake5.linux
|
||||
tools/premake5.linux gmake
|
||||
|
@ -174,15 +174,14 @@ if [ "$(uname)" != "Darwin" ]; then
|
|||
|
||||
# demos
|
||||
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 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 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-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 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 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 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 04-actor && $cc -o 04-actor demos/04-actor.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 07-network && $cc -o 07-network demos/07-network.c -lm -ldl -lpthread -lX11 -w -Iengine/ $flags $import $args
|
||||
fi
|
||||
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
|
@ -229,15 +228,14 @@ if [ "$(uname)" = "Darwin" ]; then
|
|||
|
||||
# demos
|
||||
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 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 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-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 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 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 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 04-actor && cc -o 04-actor demos/04-actor.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 07-network && cc -o 07-network demos/07-network.c -w -Iengine/ $import $flags $args -framework cocoa -framework iokit -framework audiotoolbox
|
||||
fi
|
||||
|
||||
exit
|
||||
|
@ -289,7 +287,7 @@ if "%1"=="help" (
|
|||
echo static \ link v4k as static library
|
||||
echo dll / link v4k as dynamic library (dll^) (default^)
|
||||
echo nov4k \ do not compile framework
|
||||
echo nodemos ^| do not compile demos
|
||||
echo demos ^| do compile demos
|
||||
echo editor / do compile editor
|
||||
echo vis ^> visualize invokation cmdline.
|
||||
echo args ^> after `--` separator is found, pass all remaining arguments to compiler as-is
|
||||
|
@ -527,10 +525,11 @@ set build=dev
|
|||
set args=-Iengine
|
||||
set other=
|
||||
set v4k=yes
|
||||
set demos=yes
|
||||
set hello=yes
|
||||
set demos=no
|
||||
set editor=no
|
||||
set vis=no
|
||||
set sln=no
|
||||
set proj=no
|
||||
set rc=0
|
||||
|
||||
:parse_args
|
||||
|
@ -554,8 +553,10 @@ set rc=0
|
|||
|
||||
if "%1"=="nov4k" set "v4k=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"=="editor" set "editor=yes" && goto loop
|
||||
if "%1"=="editor" set "editor=yes" && set "hello=no"&& goto loop
|
||||
if "%1"=="all" set "v4k=yes" && set "demos=yes" && set "editor=yes" && set "hello=yes" && goto loop
|
||||
|
||||
if "%1"=="tcc" set "cc=%1" && goto loop
|
||||
if "%1"=="cl" set "cc=%1" && goto loop
|
||||
|
@ -565,7 +566,7 @@ set rc=0
|
|||
if "%1"=="clang" 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"
|
||||
|
||||
|
@ -604,9 +605,10 @@ if "!cc!"=="" (
|
|||
)
|
||||
|
||||
rem solution. @todo: lin/osx
|
||||
if "!sln!"=="yes" if not "%vs%"=="" pushd tools && premake5 vs20%vs% & popd
|
||||
if "!sln!"=="yes" pushd tools && premake5 ninja & popd
|
||||
if "!sln!"=="yes" pushd tools && premake5 gmake & popd & exit /b
|
||||
if "!proj!"=="yes" if not "%vs%"=="00" pushd tools && premake5 vs20%vs% & popd
|
||||
if "!proj!"=="yes" if "%vs%"=="00" pushd tools && premake5 vs2013 & popd
|
||||
if "!proj!"=="yes" pushd tools && premake5 ninja & popd
|
||||
if "!proj!"=="yes" pushd tools && premake5 gmake & popd & exit /b
|
||||
|
||||
rem --- pipeline
|
||||
rem cl tools/ass2iqe.c /Fetools/ass2iqe.exe /nologo /openmp /O2 /Oy /MT /DNDEBUG assimp.lib
|
||||
|
@ -760,20 +762,19 @@ if "!vis!"=="yes" echo !cc! !o! editor.exe tools\editor\editor.c !edit! !impor
|
|||
|
||||
rem demos
|
||||
if "!demos!"=="yes" (
|
||||
rem !echo! hello && !cc! !o! hello.exe hello.c !args! || set rc=1
|
||||
!echo! 99-syncdemo && !cc! !o! 99-syncdemo.exe demos\99-syncdemo.c !import! !args! || set rc=1
|
||||
!echo! 99-shadertoy && !cc! !o! 99-shadertoy.exe demos\99-shadertoy.c !import! !args! || set rc=1
|
||||
!echo! 99-material && !cc! !o! 99-material.exe demos\99-material.c !import! !args! || set rc=1
|
||||
!echo! 99-video && !cc! !o! 99-video.exe demos\99-video.c !import! !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
|
||||
!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
|
||||
)
|
||||
|
||||
rem user-defined apps
|
||||
|
|
22
README.md
22
README.md
|
@ -9,7 +9,7 @@
|
|||
|
||||
## Features ᕦ(ᐛ)ᕤ
|
||||
- [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] Linkage: Both static linkage and dynamic .dll/.so/.dylib support.
|
||||
- [x] Platform: Windows, Linux and OSX. Partial HTML5/Web support.
|
||||
|
@ -195,8 +195,9 @@ end
|
|||
```
|
||||
|
||||
## Quickstart
|
||||
- Double-click `MAKE.bat` (Win) or `sh MAKE.bat` (Linux/OSX) to compile everything.
|
||||
- `MAKE.bat sln` (Win) or `sh MAKE.bat sln` (Linux/OSX) to generate solutions/makefiles.
|
||||
- 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,
|
||||
|
@ -221,7 +222,7 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
- 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/`](engine/art/import/) folder.
|
||||
- Update the gamepad controller database by upgrading the [`gamecontrollerdb.txt`](engine/art/input/) file.
|
||||
- Depending on your IDE, you might need to browse to [`engine/split/`](engine/split/) sources when debugging V4K.
|
||||
- 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.
|
||||
- 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).
|
||||
|
@ -233,7 +234,10 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
## Credits
|
||||
**Artwork**
|
||||
[Dean Evans, Raijin](https://youtu.be/RRvYkrrpMKo?t=147 "for the Map song (c)"),
|
||||
[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)"),
|
||||
|
@ -285,6 +289,7 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
[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)"),
|
||||
|
@ -297,23 +302,14 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
[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)"),
|
||||
[Vassvik](https://github.com/vassvik/mv_easy_font "for mv_easy_font (Unlicense)"),
|
||||
[Wolfgang Draxinger](https://github.com/datenwolf "for their math library (WTFPL2)"),
|
||||
|
||||
<!--
|
||||
- [DavidLam](https://en.wikipedia.org/wiki/Tokamak_(software) "for tokamak physics engine (ZLIB)")
|
||||
- [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)")
|
||||
- [ID Software, David St-Louis](https://github.com/Daivuk/PureDOOM "for PureDOOM (Doom License)")
|
||||
- [Miloslav Číž](https://codeberg.org/drummyfish/Anarch "for Anarch (CC0)")
|
||||
- [Quaternius](https://www.patreon.com/quaternius "for the lovely 3D robots (CC0)")
|
||||
- [Rxi](https://github.com/rxi/autobatch "for lovely sprites & cats demo (MIT)")
|
||||
-->
|
||||
|
||||
## 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.
|
||||
|
||||
## Links
|
||||
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/V4K/issues"><img alt="Issues" src="https://img.shields.io/github/issues-raw/r-lyeh/V4K.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>
|
||||
|
|
|
@ -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\")" );
|
||||
}
|
|
@ -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!");
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -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
|
|
@ -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)) {}//
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
));
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash 2>nul || goto :windows
|
||||
|
||||
sh ../MAKE.bat demos
|
||||
|
||||
exit
|
||||
|
||||
:windows
|
||||
|
||||
pushd ..
|
||||
call MAKE.bat demos %*
|
||||
popd
|
|
@ -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.
|
@ -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 |
|
@ -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/
|
||||
|
|
@ -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 |
|
@ -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));
|
||||
}
|
|
@ -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')
|
|
@ -347876,7 +347876,7 @@ int ui_label_(const char *label, int alignment) {
|
|||
|
||||
struct nk_style *style = &ui_ctx->style;
|
||||
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 ) {
|
||||
// set bold style and color if needed
|
||||
|
|
|
@ -1436,7 +1436,7 @@ int ui_label_(const char *label, int alignment) {
|
|||
|
||||
struct nk_style *style = &ui_ctx->style;
|
||||
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 ) {
|
||||
// set bold style and color if needed
|
||||
|
|
|
@ -18951,7 +18951,7 @@ int ui_label_(const char *label, int alignment) {
|
|||
|
||||
struct nk_style *style = &ui_ctx->style;
|
||||
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 ) {
|
||||
// set bold style and color if needed
|
||||
|
|
|
@ -596,7 +596,7 @@ details > summary::-webkit-details-marker {
|
|||
|Version: | 2023.7 |
|
||||
|:--------------|:------------|
|
||||
|Branch: | main |
|
||||
|Commit: | 13 |
|
||||
|Commit: | 17 |
|
||||
<!--| Documentation last modified | { {LAST_MODIFIED} } |-->
|
||||
|
||||
# [V·4·K 2023.7 ](https://dev.v4.games/zaklaus/v4k)
|
||||
|
@ -653,7 +653,7 @@ void assert_positive( int my_int ) { // lowercase snake_case everywhere
|
|||
|
||||
|
||||
- [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] Linkage: Both static linkage and dynamic .dll/.so/.dylib support.
|
||||
- [x] Platform: Windows, Linux and OSX. Partial HTML5/Web support.
|
||||
|
@ -851,8 +851,9 @@ end
|
|||
<details><summary>Quickstart</summary>
|
||||
|
||||
|
||||
- Double-click `MAKE.bat` (Win) or `sh MAKE.bat` (Linux/OSX) to compile everything.
|
||||
- `MAKE.bat sln` (Win) or `sh MAKE.bat sln` (Linux/OSX) to generate solutions/makefiles.
|
||||
- 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,
|
||||
|
@ -885,7 +886,7 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
- 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/`](engine/art/import/) folder.
|
||||
- Update the gamepad controller database by upgrading the [`gamecontrollerdb.txt`](engine/art/input/) file.
|
||||
- Depending on your IDE, you might need to browse to [`engine/split/`](engine/split/) sources when debugging V4K.
|
||||
- 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.
|
||||
- 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).
|
||||
|
@ -901,7 +902,10 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
|
||||
**Artwork**
|
||||
[Dean Evans, Raijin](https://youtu.be/RRvYkrrpMKo?t=147 "for the Map song (c)"),
|
||||
[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)"),
|
||||
|
@ -953,6 +957,7 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
[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)"),
|
||||
|
@ -965,16 +970,12 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
[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)"),
|
||||
[Vassvik](https://github.com/vassvik/mv_easy_font "for mv_easy_font (Unlicense)"),
|
||||
[Wolfgang Draxinger](https://github.com/datenwolf "for their math library (WTFPL2)"),
|
||||
|
||||
<!--
|
||||
- [DavidLam](https://en.wikipedia.org/wiki/Tokamak_(software) "for tokamak physics engine (ZLIB)")
|
||||
- [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)")
|
||||
- [ID Software, David St-Louis](https://github.com/Daivuk/PureDOOM "for PureDOOM (Doom License)")
|
||||
- [Miloslav Číž](https://codeberg.org/drummyfish/Anarch "for Anarch (CC0)")
|
||||
- [Quaternius](https://www.patreon.com/quaternius "for the lovely 3D robots (CC0)")
|
||||
- [Rxi](https://github.com/rxi/autobatch "for lovely sprites & cats demo (MIT)")
|
||||
-->
|
||||
|
||||
|
@ -984,15 +985,6 @@ echo osx && cc -ObjC hello.c -framework cocoa -framework iokit -fr
|
|||
|
||||
|
||||
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.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Links</summary>
|
||||
|
||||
|
||||
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/V4K/issues"><img alt="Issues" src="https://img.shields.io/github/issues-raw/r-lyeh/V4K.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>
|
||||
</details>
|
||||
|
||||
## config
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
return {
|
||||
"_preload.lua",
|
||||
"ninja.lua",
|
||||
}
|
|
@ -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
|
|
@ -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 ..
|
||||
|
|
@ -0,0 +1,725 @@
|
|||
--
|
||||
-- Name: premake-ninja/ninja.lua
|
||||
-- Purpose: Define the ninja action.
|
||||
-- Author: Dmitry Ivanov
|
||||
-- Created: 2015/07/04
|
||||
-- Copyright: (c) 2015 Dmitry Ivanov
|
||||
--
|
||||
|
||||
local p = premake
|
||||
local tree = p.tree
|
||||
local project = p.project
|
||||
local config = p.config
|
||||
local fileconfig = p.fileconfig
|
||||
|
||||
premake.modules.ninja = {}
|
||||
local ninja = p.modules.ninja
|
||||
|
||||
local function get_key(cfg)
|
||||
if cfg.platform then
|
||||
return cfg.project.name .. "_" .. cfg.buildcfg .. "_" .. cfg.platform
|
||||
else
|
||||
return cfg.project.name .. "_" .. cfg.buildcfg
|
||||
end
|
||||
end
|
||||
|
||||
local build_cache = {}
|
||||
|
||||
local function add_build(cfg, output, extra_outputs, command, args)
|
||||
local cached = build_cache[output]
|
||||
if cached ~= nil then
|
||||
if extra_outputs == cached.extra_outputs
|
||||
and command == cached.command
|
||||
and table.equals(args or {}, cached.args or {})
|
||||
then
|
||||
-- custom_command rule is identical for each configuration (contrary to other rules)
|
||||
-- So we can compare extra parameter
|
||||
if string.startswith(cached.command, "custom_command") then
|
||||
p.w("# INFO: Rule ignored, same as " .. cached.cfg_key)
|
||||
else
|
||||
local cfg_key = get_key(cfg)
|
||||
p.warn(cached.cfg_key .. " and " .. cfg_key .. " both generate (differently?) " .. output .. ". Ignoring " .. cfg_key)
|
||||
p.w("# WARNING: Rule ignored, using the one from " .. cached.cfg_key)
|
||||
end
|
||||
else
|
||||
local cfg_key = get_key(cfg)
|
||||
p.warn(cached.cfg_key .. " and " .. cfg_key .. " both generate differently " .. output .. ". Ignoring " .. cfg_key)
|
||||
p.w("# ERROR: Rule ignored, using the one from " .. cached.cfg_key)
|
||||
end
|
||||
p.w("# build " .. output .. ": " .. command)
|
||||
for i, arg in ipairs(args or {}) do
|
||||
p.w("# " .. arg)
|
||||
end
|
||||
return
|
||||
end
|
||||
p.w("build " .. output .. ": " .. command)
|
||||
for i, arg in ipairs(args or {}) do
|
||||
p.w(" " .. arg)
|
||||
end
|
||||
build_cache[output] = {
|
||||
cfg_key = get_key(cfg),
|
||||
extra_outputs = extra_outputs,
|
||||
command = command,
|
||||
args = args
|
||||
}
|
||||
end
|
||||
|
||||
function ninja.esc(value)
|
||||
value = value:gsub("%$", "$$") -- TODO maybe there is better way
|
||||
value = value:gsub(":", "$:")
|
||||
value = value:gsub("\n", "$\n")
|
||||
value = value:gsub(" ", "$ ")
|
||||
return value
|
||||
end
|
||||
|
||||
function ninja.quote(value)
|
||||
value = value:gsub("\\", "\\\\")
|
||||
value = value:gsub("'", "\\'")
|
||||
value = value:gsub("\"", "\\\"")
|
||||
|
||||
return "\"" .. value .. "\""
|
||||
end
|
||||
|
||||
-- in some cases we write file names in rule commands directly
|
||||
-- so we need to propely escape them
|
||||
function ninja.shesc(value)
|
||||
if type(value) == "table" then
|
||||
local result = {}
|
||||
local n = #value
|
||||
for i = 1, n do
|
||||
table.insert(result, ninja.shesc(value[i]))
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
if value:find(" ") then
|
||||
return ninja.quote(value)
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- generate solution that will call ninja for projects
|
||||
function ninja.generateWorkspace(wks)
|
||||
local oldGetDefaultSeparator = path.getDefaultSeparator
|
||||
path.getDefaultSeparator = function() return "/" end
|
||||
|
||||
p.w("# solution build file")
|
||||
p.w("# generated with premake ninja")
|
||||
p.w("")
|
||||
|
||||
p.w("# build projects")
|
||||
local cfgs = {} -- key is concatenated name or variant name, value is string of outputs names
|
||||
local key = ""
|
||||
local cfg_first = nil
|
||||
local cfg_first_lib = nil
|
||||
|
||||
for prj in p.workspace.eachproject(wks) do
|
||||
if p.action.supports(prj.kind) and prj.kind ~= p.NONE then
|
||||
for cfg in p.project.eachconfig(prj) do
|
||||
key = prj.name .. "_" .. cfg.buildcfg
|
||||
|
||||
if cfg.platform ~= nil then key = key .. "_" .. cfg.platform end
|
||||
|
||||
if not cfgs[cfg.buildcfg] then cfgs[cfg.buildcfg] = "" end
|
||||
cfgs[cfg.buildcfg] = cfgs[cfg.buildcfg] .. key .. " "
|
||||
|
||||
-- set first configuration name
|
||||
if (cfg_first == nil) and (cfg.kind == p.CONSOLEAPP or cfg.kind == p.WINDOWEDAPP) then
|
||||
cfg_first = key
|
||||
end
|
||||
if (cfg_first_lib == nil) and (cfg.kind == p.STATICLIB or cfg.kind == p.SHAREDLIB) then
|
||||
cfg_first_lib = key
|
||||
end
|
||||
|
||||
-- include other ninja file
|
||||
p.w("subninja " .. p.esc(ninja.projectCfgFilename(cfg, true)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if cfg_first == nil then cfg_first = cfg_first_lib end
|
||||
|
||||
p.w("")
|
||||
|
||||
p.w("# targets")
|
||||
for cfg, outputs in pairs(cfgs) do
|
||||
p.w("build " .. p.esc(cfg) .. ": phony " .. outputs)
|
||||
end
|
||||
p.w("")
|
||||
|
||||
p.w("# default target")
|
||||
p.w("default " .. p.esc(cfg_first))
|
||||
p.w("")
|
||||
|
||||
path.getDefaultSeparator = oldGetDefaultSeparator
|
||||
end
|
||||
|
||||
function ninja.list(value)
|
||||
if #value > 0 then
|
||||
return " " .. table.concat(value, " ")
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
local function shouldcompileasc(filecfg)
|
||||
if filecfg.compileas and filecfg.compileas ~= "Default" then
|
||||
return p.languages.isc(filecfg.compileas)
|
||||
end
|
||||
return path.iscfile(filecfg.abspath)
|
||||
end
|
||||
|
||||
local function shouldcompileascpp(filecfg)
|
||||
if filecfg.compileas and filecfg.compileas ~= "Default" then
|
||||
return p.languages.iscpp(filecfg.compileas)
|
||||
end
|
||||
return path.iscppfile(filecfg.abspath)
|
||||
end
|
||||
|
||||
local function getDefaultToolsetFromOs()
|
||||
local system_name = os.target()
|
||||
|
||||
if system_name == "windows" then
|
||||
return "msc"
|
||||
elseif system_name == "macosx" then
|
||||
return "clang"
|
||||
elseif system_name == "linux" then
|
||||
return "gcc"
|
||||
else
|
||||
p.warnOnce("unknown_system", "no toolchain set and unknown system " .. system_name .. " so assuming toolchain is gcc")
|
||||
return "gcc"
|
||||
end
|
||||
end
|
||||
|
||||
local function getToolsetExecutables(cfg, toolset, toolset_name)
|
||||
local cc = ""
|
||||
local cxx = ""
|
||||
local ar = ""
|
||||
local link = ""
|
||||
local rc = ""
|
||||
|
||||
if toolset_name == "msc" then
|
||||
-- TODO premake doesn't set tools names for msc, do we want to fix it ?
|
||||
cc = "cl"
|
||||
cxx = "cl"
|
||||
ar = "lib"
|
||||
link = "cl"
|
||||
rc = "rc"
|
||||
elseif toolset_name == "clang" or toolset_name == "gcc" then
|
||||
if not cfg.gccprefix then cfg.gccprefix = "" end
|
||||
cc = toolset.gettoolname(cfg, "cc")
|
||||
cxx = toolset.gettoolname(cfg, "cxx")
|
||||
ar = toolset.gettoolname(cfg, "ar")
|
||||
link = toolset.gettoolname(cfg, iif(cfg.language == "C", "cc", "cxx"))
|
||||
else
|
||||
p.error("unknown toolchain " .. toolset_name)
|
||||
end
|
||||
return cc, cxx, ar, link, rc
|
||||
end
|
||||
|
||||
local function getFileDependencies(cfg)
|
||||
local dependencies = {}
|
||||
if #cfg.prebuildcommands > 0 or cfg.prebuildmessage then
|
||||
dependencies = {"prebuild_" .. get_key(cfg)}
|
||||
end
|
||||
for i = 1, #cfg.dependson do
|
||||
table.insert(dependencies, cfg.dependson[i] .. "_" .. cfg.buildcfg)
|
||||
end
|
||||
return dependencies
|
||||
end
|
||||
|
||||
local function getcflags(toolset, cfg, filecfg)
|
||||
local buildopt = ninja.list(filecfg.buildoptions)
|
||||
local cppflags = ninja.list(toolset.getcppflags(filecfg))
|
||||
local cflags = ninja.list(toolset.getcflags(filecfg))
|
||||
local defines = ninja.list(table.join(toolset.getdefines(filecfg.defines), toolset.getundefines(filecfg.undefines)))
|
||||
local includes = ninja.list(toolset.getincludedirs(cfg, filecfg.includedirs, filecfg.externalincludedirs))
|
||||
local forceincludes = ninja.list(toolset.getforceincludes(cfg))
|
||||
|
||||
return buildopt .. cppflags .. cflags .. defines .. includes .. forceincludes
|
||||
end
|
||||
|
||||
local function getcxxflags(toolset, cfg, filecfg)
|
||||
local buildopt = ninja.list(filecfg.buildoptions)
|
||||
local cppflags = ninja.list(toolset.getcppflags(filecfg))
|
||||
local cxxflags = ninja.list(toolset.getcxxflags(filecfg))
|
||||
local defines = ninja.list(table.join(toolset.getdefines(filecfg.defines), toolset.getundefines(filecfg.undefines)))
|
||||
local includes = ninja.list(toolset.getincludedirs(cfg, filecfg.includedirs, filecfg.externalincludedirs))
|
||||
local forceincludes = ninja.list(toolset.getforceincludes(cfg))
|
||||
return buildopt .. cppflags .. cxxflags .. defines .. includes .. forceincludes
|
||||
end
|
||||
|
||||
local function getldflags(toolset, cfg)
|
||||
local ldflags = ninja.list(table.join(toolset.getLibraryDirectories(cfg), toolset.getldflags(cfg), cfg.linkoptions))
|
||||
|
||||
-- experimental feature, change install_name of shared libs
|
||||
--if (toolset_name == "clang") and (cfg.kind == p.SHAREDLIB) and ninja.endsWith(cfg.buildtarget.name, ".dylib") then
|
||||
-- ldflags = ldflags .. " -install_name " .. cfg.buildtarget.name
|
||||
--end
|
||||
return ldflags
|
||||
end
|
||||
|
||||
local function prebuild_rule(cfg)
|
||||
if #cfg.prebuildcommands > 0 or cfg.prebuildmessage then
|
||||
local commands = {}
|
||||
if cfg.prebuildmessage then
|
||||
commands = {os.translateCommandsAndPaths("{ECHO} " .. cfg.prebuildmessage, cfg.project.basedir, cfg.project.location)}
|
||||
end
|
||||
commands = table.join(commands, os.translateCommandsAndPaths(cfg.prebuildcommands, cfg.project.basedir, cfg.project.location))
|
||||
if (#commands > 1) then
|
||||
commands = 'sh -c ' .. ninja.quote(table.implode(commands,"","",";"))
|
||||
else
|
||||
commands = commands[1]
|
||||
end
|
||||
p.w("rule run_prebuild")
|
||||
p.w(" command = " .. p.esc(commands))
|
||||
p.w(" description = prebuild")
|
||||
p.w("")
|
||||
end
|
||||
end
|
||||
|
||||
local function postbuild_rule(cfg)
|
||||
if #cfg.postbuildcommands > 0 or cfg.postbuildmessage then
|
||||
local commands = {}
|
||||
if cfg.postbuildmessage then
|
||||
commands = {os.translateCommandsAndPaths("{ECHO} " .. cfg.postbuildmessage, cfg.project.basedir, cfg.project.location)}
|
||||
end
|
||||
commands = table.join(commands, os.translateCommandsAndPaths(cfg.postbuildcommands, cfg.project.basedir, cfg.project.location))
|
||||
if (#commands > 1) then
|
||||
commands = 'sh -c ' .. ninja.quote(table.implode(commands,"","",";"))
|
||||
else
|
||||
commands = commands[1]
|
||||
end
|
||||
p.w("rule run_postbuild")
|
||||
p.w(" command = " .. p.esc(commands))
|
||||
p.w(" description = postbuild")
|
||||
p.w("")
|
||||
end
|
||||
end
|
||||
|
||||
local function compilation_rules(cfg, toolset, toolset_name, pch)
|
||||
---------------------------------------------------- figure out toolset executables
|
||||
local cc, cxx, ar, link, rc = getToolsetExecutables(cfg, toolset, toolset_name)
|
||||
|
||||
local all_cflags = getcflags(toolset, cfg, cfg)
|
||||
local all_cxxflags = getcxxflags(toolset, cfg, cfg)
|
||||
local all_ldflags = getldflags(toolset, cfg)
|
||||
|
||||
if toolset_name == "msc" then
|
||||
-- for some reason Visual Studio add this libraries as "defaults" and premake doesn't tell us this
|
||||
local default_msvc_libs = " kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib"
|
||||
|
||||
p.w("rule cc")
|
||||
p.w(" command = " .. cc .. all_cflags .. " /nologo /showIncludes -c $in /Fo$out")
|
||||
p.w(" description = cc $out")
|
||||
p.w(" deps = msvc")
|
||||
p.w("")
|
||||
p.w("rule cxx")
|
||||
p.w(" command = " .. cxx .. all_cxxflags .. " /nologo /showIncludes -c $in /Fo$out")
|
||||
p.w(" description = cxx $out")
|
||||
p.w(" deps = msvc")
|
||||
p.w("")
|
||||
p.w("rule cc_flags")
|
||||
p.w(" command = " .. cc .. " $CFLAGS" .. " /nologo /showIncludes -c $in /Fo$out")
|
||||
p.w(" description = cc $out")
|
||||
p.w(" deps = msvc")
|
||||
p.w("")
|
||||
p.w("rule cxx_flags")
|
||||
p.w(" command = " .. cxx .. " $CXXFLAGS" .. " /nologo /showIncludes -c $in /Fo$out")
|
||||
p.w(" description = cxx $out")
|
||||
p.w(" deps = msvc")
|
||||
p.w("")
|
||||
p.w("rule rc")
|
||||
p.w(" command = " .. rc .. " /nologo /fo$out $in")
|
||||
p.w(" description = rc $out")
|
||||
p.w("")
|
||||
if cfg.kind == p.STATICLIB then
|
||||
p.w("rule ar")
|
||||
p.w(" command = " .. ar .. " $in /nologo -OUT:$out")
|
||||
p.w(" description = ar $out")
|
||||
p.w("")
|
||||
else
|
||||
p.w("rule link")
|
||||
p.w(" command = " .. link .. " $in" .. ninja.list(ninja.shesc(toolset.getlinks(cfg, true))) .. default_msvc_libs .. " /link" .. all_ldflags .. " /nologo /out:$out")
|
||||
p.w(" description = link $out")
|
||||
p.w("")
|
||||
end
|
||||
elseif toolset_name == "clang" then
|
||||
local force_include_pch = ""
|
||||
if pch then
|
||||
force_include_pch = " -include " .. p.esc(pch.placeholder)
|
||||
p.w("rule build_pch")
|
||||
p.w(" command = " .. iif(cfg.language == "C", cc .. all_cflags .. " -x c-header", cxx .. all_cxxflags .. " -x c++-header") .. " -H -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = build_pch $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
end
|
||||
p.w("rule cc")
|
||||
p.w(" command = " .. cc .. all_cflags .. force_include_pch .. " -x c -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cc $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
p.w("")
|
||||
p.w("rule cxx")
|
||||
p.w(" command = " .. cxx .. all_cxxflags .. force_include_pch .. " -x c++ -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cxx $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
p.w("")
|
||||
p.w("rule cc_flags")
|
||||
p.w(" command = " .. cc .. " $CFLAGS".. force_include_pch .. " -x c -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cc $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
p.w("")
|
||||
p.w("rule cxx_flags")
|
||||
p.w(" command = " .. cxx .. " $CXXFLAGS" .. force_include_pch .. " -x c++ -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cxx $out")
|
||||
p.w(" deps = msvc")
|
||||
p.w("")
|
||||
if cfg.kind == p.STATICLIB then
|
||||
p.w("rule ar")
|
||||
p.w(" command = " .. ar .. " rcs $out $in")
|
||||
p.w(" description = ar $out")
|
||||
p.w("")
|
||||
else
|
||||
p.w("rule link")
|
||||
p.w(" command = " .. link .. " -o $out $in" .. ninja.list(ninja.shesc(toolset.getlinks(cfg, true))) .. all_ldflags)
|
||||
p.w(" description = link $out")
|
||||
p.w("")
|
||||
end
|
||||
elseif toolset_name == "gcc" then
|
||||
local force_include_pch = ""
|
||||
if pch then
|
||||
force_include_pch = " -include " .. p.esc(pch.placeholder)
|
||||
p.w("rule build_pch")
|
||||
p.w(" command = " .. iif(cfg.language == "C", cc .. all_cflags .. " -x c-header", cxx .. all_cxxflags .. " -x c++-header") .. " -H -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = build_pch $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
end
|
||||
p.w("rule cc")
|
||||
p.w(" command = " .. cc .. all_cflags .. force_include_pch .. " -x c -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cc $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
p.w("")
|
||||
p.w("rule cxx")
|
||||
p.w(" command = " .. cxx .. all_cxxflags .. force_include_pch .. " -x c++ -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cxx $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
p.w("")
|
||||
p.w("rule cc_flags")
|
||||
p.w(" command = " .. cc .. " $CFLAGS".. force_include_pch .. " -x c -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cc $out")
|
||||
p.w(" depfile = $out.d")
|
||||
p.w(" deps = gcc")
|
||||
p.w("")
|
||||
p.w("rule cxx_flags")
|
||||
p.w(" command = " .. cxx .. " $CXXFLAGS" .. force_include_pch .. " -x c++ -MMD -MF $out.d -c -o $out $in")
|
||||
p.w(" description = cxx $out")
|
||||
p.w(" deps = msvc")
|
||||
p.w("")
|
||||
if cfg.kind == p.STATICLIB then
|
||||
p.w("rule ar")
|
||||
p.w(" command = " .. ar .. " rcs $out $in")
|
||||
p.w(" description = ar $out")
|
||||
p.w("")
|
||||
else
|
||||
p.w("rule link")
|
||||
p.w(" command = " .. link .. " -o $out $in" .. ninja.list(ninja.shesc(toolset.getlinks(cfg, true))) .. all_ldflags)
|
||||
p.w(" description = link $out")
|
||||
p.w("")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function custom_command_rule()
|
||||
p.w("rule custom_command")
|
||||
p.w(" command = $CUSTOM_COMMAND")
|
||||
p.w(" description = $CUSTOM_DESCRIPTION")
|
||||
p.w("")
|
||||
end
|
||||
|
||||
local function collect_generated_files(prj, cfg)
|
||||
local generated_files = {}
|
||||
tree.traverse(project.getsourcetree(prj), {
|
||||
onleaf = function(node, depth)
|
||||
function append_to_generated_files(filecfg)
|
||||
local output = project.getrelative(prj, filecfg.buildoutputs[1])
|
||||
table.insert(generated_files, p.esc(output))
|
||||
end
|
||||
local filecfg = fileconfig.getconfig(node, cfg)
|
||||
local rule = p.global.getRuleForFile(node.name, prj.rules)
|
||||
if fileconfig.hasCustomBuildRule(filecfg) then
|
||||
append_to_generated_files(filecfg)
|
||||
elseif rule then
|
||||
local environ = table.shallowcopy(filecfg.environ)
|
||||
|
||||
if rule.propertydefinition then
|
||||
p.rule.prepareEnvironment(rule, environ, cfg)
|
||||
p.rule.prepareEnvironment(rule, environ, filecfg)
|
||||
end
|
||||
local rulecfg = p.context.extent(rule, environ)
|
||||
append_to_generated_files(rulecfg)
|
||||
end
|
||||
end,
|
||||
}, false, 1)
|
||||
return generated_files
|
||||
end
|
||||
|
||||
local function pch_build(cfg, pch)
|
||||
local pch_dependency = ""
|
||||
if pch then
|
||||
pch_dependency = " | " .. pch.gch
|
||||
add_build(cfg, p.esc(pch.gch), "", "build_pch " .. p.esc(pch.input))
|
||||
end
|
||||
return pch_dependency
|
||||
end
|
||||
|
||||
local function custom_command_build(prj, cfg, filecfg, filename, file_dependencies)
|
||||
local output = project.getrelative(prj, filecfg.buildoutputs[1])
|
||||
local inputs = ""
|
||||
if #filecfg.buildinputs > 0 then
|
||||
inputs = table.implode(filecfg.buildinputs," ","","")
|
||||
end
|
||||
|
||||
local commands = {}
|
||||
if filecfg.buildmessage then
|
||||
commands = {os.translateCommandsAndPaths("{ECHO} " .. filecfg.buildmessage, prj.basedir, prj.location)}
|
||||
end
|
||||
commands = table.join(commands, os.translateCommandsAndPaths(filecfg.buildcommands, prj.basedir, prj.location))
|
||||
if (#commands > 1) then
|
||||
commands = 'sh -c ' .. ninja.quote(table.implode(commands,"","",";"))
|
||||
else
|
||||
commands = commands[1]
|
||||
end
|
||||
|
||||
add_build(cfg, p.esc(output), "", "custom_command | " .. p.esc(filename) .. inputs .. iif(#file_dependencies > 0, "||" .. ninja.list(file_dependencies), ""),
|
||||
{"CUSTOM_COMMAND = " .. commands, "CUSTOM_DESCRIPTION = custom build " .. p.esc(output)})
|
||||
end
|
||||
|
||||
local function compile_file_build(cfg, filecfg, toolset, pch_dependency, regular_file_dependencies, objfiles)
|
||||
local obj_dir = project.getrelative(cfg.workspace, cfg.objdir)
|
||||
local has_custom_settings = fileconfig.hasFileSettings(filecfg)
|
||||
|
||||
if shouldcompileasc(filecfg) then
|
||||
local objfilename = obj_dir .. "/" .. filecfg.objname .. iif(toolset_name == "msc", ".obj", ".o")
|
||||
objfiles[#objfiles + 1] = objfilename
|
||||
local cflags = {}
|
||||
if has_custom_settings then
|
||||
cflags = {"CFLAGS = " .. getcflags(toolset, cfg, filecfg)}
|
||||
end
|
||||
add_build(cfg, p.esc(objfilename), "", iif(has_custom_settings, "cc_flags ", "cc ") .. p.esc(filecfg.relpath) .. pch_dependency .. regular_file_dependencies, cflags)
|
||||
elseif shouldcompileascpp(filecfg) then
|
||||
local objfilename = obj_dir .. "/" .. filecfg.objname .. iif(toolset_name == "msc", ".obj", ".o")
|
||||
objfiles[#objfiles + 1] = objfilename
|
||||
local cxxflags = {}
|
||||
if has_custom_settings then
|
||||
cxxflags = {"CXXFLAGS = " .. getcxxflags(toolset, cfg, filecfg)}
|
||||
end
|
||||
add_build(cfg, p.esc(objfilename), "", iif(has_custom_settings, "cxx_flags ", "cxx ") .. p.esc(filecfg.relpath) .. pch_dependency .. regular_file_dependencies, cxxflags)
|
||||
elseif path.isresourcefile(filecfg.abspath) then
|
||||
local objfilename = obj_dir .. "/" .. filecfg.name .. ".res"
|
||||
objfiles[#objfiles + 1] = objfilename
|
||||
add_build(cfg, p.esc(objfilename), "", "rc " .. p.esc(filecfg.relpath))
|
||||
end
|
||||
end
|
||||
|
||||
local function files_build(prj, cfg, toolset, toolset_name, pch_dependency, regular_file_dependencies, file_dependencies)
|
||||
local objfiles = {}
|
||||
tree.traverse(project.getsourcetree(prj), {
|
||||
onleaf = function(node, depth)
|
||||
local filecfg = fileconfig.getconfig(node, cfg)
|
||||
local rule = p.global.getRuleForFile(node.name, prj.rules)
|
||||
if fileconfig.hasCustomBuildRule(filecfg) then
|
||||
custom_command_build(prj, cfg, filecfg, node.relpath, file_dependencies)
|
||||
elseif rule then
|
||||
local environ = table.shallowcopy(filecfg.environ)
|
||||
|
||||
if rule.propertydefinition then
|
||||
p.rule.prepareEnvironment(rule, environ, cfg)
|
||||
p.rule.prepareEnvironment(rule, environ, filecfg)
|
||||
end
|
||||
local rulecfg = p.context.extent(rule, environ)
|
||||
custom_command_build(prj, cfg, rulecfg, node.relpath, file_dependencies)
|
||||
else
|
||||
compile_file_build(cfg, filecfg, toolset, pch_dependency, regular_file_dependencies, objfiles)
|
||||
end
|
||||
end,
|
||||
}, false, 1)
|
||||
p.w("")
|
||||
|
||||
return objfiles
|
||||
end
|
||||
|
||||
local function generated_files_build(cfg, generated_files, key)
|
||||
local final_dependency = ""
|
||||
if #generated_files > 0 then
|
||||
p.w("# generated files")
|
||||
add_build(cfg, "generated_files_" .. key, "", "phony" .. ninja.list(generated_files))
|
||||
final_dependency = " || generated_files_" .. key
|
||||
end
|
||||
return final_dependency
|
||||
end
|
||||
|
||||
-- generate project + config build file
|
||||
function ninja.generateProjectCfg(cfg)
|
||||
local oldGetDefaultSeparator = path.getDefaultSeparator
|
||||
path.getDefaultSeparator = function() return "/" end
|
||||
|
||||
local prj = cfg.project
|
||||
local key = prj.name .. "_" .. cfg.buildcfg
|
||||
-- TODO why premake doesn't provide default name always ?
|
||||
local toolset_name = _OPTIONS.cc or cfg.toolset or ninja.getDefaultToolsetFromOs()
|
||||
local toolset = p.tools[toolset_name]
|
||||
|
||||
p.w("# project build file")
|
||||
p.w("# generated with premake ninja")
|
||||
p.w("")
|
||||
|
||||
-- premake-ninja relies on scoped rules
|
||||
-- and they were added in ninja v1.6
|
||||
p.w("ninja_required_version = 1.6")
|
||||
p.w("")
|
||||
|
||||
---------------------------------------------------- figure out settings
|
||||
local pch = nil
|
||||
if toolset_name ~= "msc" then
|
||||
pch = p.tools.gcc.getpch(cfg)
|
||||
if pch then
|
||||
pch = {
|
||||
input = pch,
|
||||
placeholder = project.getrelative(cfg.workspace, path.join(cfg.objdir, path.getname(pch))),
|
||||
gch = project.getrelative(cfg.workspace, path.join(cfg.objdir, path.getname(pch) .. ".gch"))
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---------------------------------------------------- write rules
|
||||
p.w("# core rules for " .. cfg.name)
|
||||
prebuild_rule(cfg)
|
||||
postbuild_rule(cfg)
|
||||
compilation_rules(cfg, toolset, toolset_name, pch)
|
||||
custom_command_rule()
|
||||
|
||||
---------------------------------------------------- build all files
|
||||
p.w("# build files")
|
||||
|
||||
local pch_dependency = pch_build(cfg, pch)
|
||||
|
||||
local generated_files = collect_generated_files(prj, cfg)
|
||||
local file_dependencies = getFileDependencies(cfg)
|
||||
local regular_file_dependencies = ""
|
||||
if #generated_files > 0 then
|
||||
regular_file_dependencies = " || generated_files_" .. key .. ninja.list(file_dependencies)
|
||||
elseif #file_dependencies > 0 then
|
||||
regular_file_dependencies = " ||" .. ninja.list(file_dependencies)
|
||||
end
|
||||
|
||||
local obj_dir = project.getrelative(cfg.workspace, cfg.objdir)
|
||||
local objfiles = files_build(prj, cfg, toolset, toolset_name, pch_dependency, regular_file_dependencies, file_dependencies)
|
||||
local final_dependency = generated_files_build(cfg, generated_files, key)
|
||||
|
||||
---------------------------------------------------- build final target
|
||||
if #cfg.prebuildcommands > 0 or cfg.prebuildmessage then
|
||||
p.w("# prebuild")
|
||||
add_build(cfg, "prebuild_" .. get_key(cfg), "", "run_prebuild")
|
||||
end
|
||||
if #cfg.postbuildcommands > 0 or cfg.postbuildmessage then
|
||||
p.w("# postbuild")
|
||||
add_build(cfg, "postbuild_" .. get_key(cfg), "", "run_postbuild | " .. ninja.outputFilename(cfg))
|
||||
end
|
||||
|
||||
-- we don't pass getlinks(cfg) through dependencies
|
||||
-- because system libraries are often not in PATH so ninja can't find them
|
||||
local libs = ninja.list(p.esc(config.getlinks(cfg, "siblings", "fullpath")))
|
||||
if cfg.kind == p.STATICLIB then
|
||||
p.w("# link static lib")
|
||||
add_build(cfg, p.esc(ninja.outputFilename(cfg)), "", "ar " .. table.concat(p.esc(objfiles), " ") .. libs .. final_dependency)
|
||||
|
||||
elseif cfg.kind == p.SHAREDLIB then
|
||||
local output = ninja.outputFilename(cfg)
|
||||
p.w("# link shared lib")
|
||||
|
||||
local extra_output = ""
|
||||
if ninja.endsWith(output, ".dll") then
|
||||
extra_output = " | " .. p.esc(ninja.noext(output, ".dll")) .. ".lib" .. " " .. p.esc(ninja.noext(output, ".dll")) .. ".exp"
|
||||
elseif ninja.endsWith(output, ".so") then
|
||||
extra_output = " | " .. p.esc(ninja.noext(output, ".so")) .. ".a"
|
||||
elseif ninja.endsWith(output, ".dylib") then
|
||||
-- in case of .dylib there are no corresponding .a file
|
||||
else
|
||||
p.error("unknown type of shared lib '" .. output .. "', so no idea what to do, sorry")
|
||||
end
|
||||
|
||||
add_build(cfg, p.esc(output), extra_output, "link " .. table.concat(p.esc(objfiles), " ") .. libs .. final_dependency)
|
||||
|
||||
elseif (cfg.kind == p.CONSOLEAPP) or (cfg.kind == p.WINDOWEDAPP) then
|
||||
p.w("# link executable")
|
||||
add_build(cfg, p.esc(ninja.outputFilename(cfg)), "", "link " .. table.concat(p.esc(objfiles), " ") .. libs .. final_dependency)
|
||||
|
||||
else
|
||||
p.error("ninja action doesn't support this kind of target " .. cfg.kind)
|
||||
end
|
||||
|
||||
p.w("")
|
||||
if #cfg.postbuildcommands > 0 or cfg.postbuildmessage then
|
||||
add_build(cfg, key, "", "phony postbuild_" .. get_key(cfg))
|
||||
else
|
||||
add_build(cfg, key, "", "phony " .. ninja.outputFilename(cfg))
|
||||
end
|
||||
p.w("")
|
||||
|
||||
path.getDefaultSeparator = oldGetDefaultSeparator
|
||||
end
|
||||
|
||||
-- return name of output binary relative to build folder
|
||||
function ninja.outputFilename(cfg)
|
||||
return project.getrelative(cfg.workspace, cfg.buildtarget.directory) .. "/" .. cfg.buildtarget.name
|
||||
end
|
||||
|
||||
-- return name of build file for configuration
|
||||
function ninja.projectCfgFilename(cfg, relative)
|
||||
if relative ~= nil then
|
||||
relative = project.getrelative(cfg.workspace, cfg.location) .. "/"
|
||||
else
|
||||
relative = ""
|
||||
end
|
||||
|
||||
local ninjapath = relative .. "build_" .. cfg.project.name .. "_" .. cfg.buildcfg
|
||||
|
||||
if cfg.platform ~= nil then ninjapath = ninjapath .. "_" .. cfg.platform end
|
||||
|
||||
return ninjapath .. ".ninja"
|
||||
end
|
||||
|
||||
-- check if string starts with string
|
||||
function ninja.startsWith(str, starts)
|
||||
return str:sub(0, starts:len()) == starts
|
||||
end
|
||||
|
||||
-- check if string ends with string
|
||||
function ninja.endsWith(str, ends)
|
||||
return str:sub(-ends:len()) == ends
|
||||
end
|
||||
|
||||
-- removes extension from string
|
||||
function ninja.noext(str, ext)
|
||||
return str:sub(0, str:len() - ext:len())
|
||||
end
|
||||
|
||||
-- generate all build files for every project configuration
|
||||
function ninja.generateProject(prj)
|
||||
if not p.action.supports(prj.kind) or prj.kind == p.NONE then
|
||||
return
|
||||
end
|
||||
for cfg in project.eachconfig(prj) do
|
||||
p.generate(cfg, ninja.projectCfgFilename(cfg), ninja.generateProjectCfg)
|
||||
end
|
||||
end
|
||||
|
||||
include("_preload.lua")
|
||||
|
||||
return ninja
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -85,7 +85,8 @@ function demos(...)
|
|||
end
|
||||
|
||||
demos(
|
||||
"bytebeat"
|
||||
"00-hello","01-sprite","02-ddraw","03-character","04-control",
|
||||
"04-scene","05-network","06-pbr"
|
||||
)
|
||||
|
||||
-- games
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue