Compare commits

..

54 commits

Author SHA1 Message Date
John Alanbrook 50386c0e04 Merge branch 'master' into imgui
# Conflicts:
#	scripts/std.js
#	source/engine/jsffi.c
2024-07-02 16:21:22 -05:00
John Alanbrook 3949b33855 mkdocs 2024-07-02 16:14:32 -05:00
John Alanbrook e1ac2a8fe3 fix render image 2024-07-01 17:00:01 -05:00
John Alanbrook b25cd85071 more gui 2024-07-01 13:05:26 -05:00
John Alanbrook f7fdae024d remove unneede info logging 2024-07-01 10:59:31 -05:00
John Alanbrook 829b6386da some array fns 2024-06-26 16:57:17 -05:00
John Alanbrook 42db19d748 add alpha to all colors 2024-06-26 13:12:34 -05:00
John Alanbrook b5e655e1f2 add vector angle 2024-06-25 17:53:15 -05:00
John Alanbrook 7e851756de fix sprite render 2024-06-19 18:52:41 -05:00
John Alanbrook 35deec52ae image generation 2024-06-19 11:45:34 -05:00
John Alanbrook ebfa801560 add check to image blit 2024-06-18 17:10:24 -05:00
John Alanbrook 2fe4e825aa add thumbnail making 2024-06-18 16:14:23 -05:00
John Alanbrook 1b5bd399cd now read off individual file times from packed gamefiles 2024-06-07 10:38:21 -05:00
John Alanbrook 131bfda5f8 Add debug drawing to 2d physics 2024-06-07 09:49:13 -05:00
John Alanbrook 9d90cd6f56 Fix cache detection for shaders 2024-06-07 00:43:15 -05:00
John Alanbrook b2a466ce8b add box point generation 2024-06-07 00:39:37 -05:00
John Alanbrook ecf3859067 initial add of imgui 2024-06-05 16:07:44 -05:00
John Alanbrook 43da35daad remove verbose render info 2024-06-03 16:45:08 -05:00
John Alanbrook 10678e8bcc add shaders 2024-05-30 17:12:54 -05:00
John Alanbrook cb30231c2f fix makefile; fix yaml to json 2024-05-30 17:12:32 -05:00
John Alanbrook eaa3bf00c5 fix makefile precompilation 2024-05-30 15:59:11 -05:00
John Alanbrook 1ff46c3975 fix input bug 2024-05-30 12:05:51 -05:00
John Alanbrook b2a45fcfdd fix linux opengl render; windows ucrt 2024-05-29 20:21:19 -05:00
John Alanbrook 33fdd97bdd Simplify components in js 2024-05-28 13:39:02 -05:00
John Alanbrook eadfd1dc38 add steam and dsp node js 2024-05-21 18:50:53 -05:00
John Alanbrook 39442c10af Transform is uniform if not present 2024-05-21 09:33:17 -05:00
John Alanbrook 0a318c56e5 Overhaul physics-javascript integration 2024-05-20 13:50:57 -05:00
John Alanbrook 4e4667db50 removed unnecessary physics code 2024-05-17 12:39:04 -05:00
John Alanbrook ba2409fc56 2d physics to javascript 2024-05-17 04:23:03 -05:00
John Alanbrook ee72949029 move window ideas to javascript 2024-05-16 14:50:18 -05:00
John Alanbrook 18c5bc6a56 Add mum rendering camera 2024-05-16 08:21:13 -05:00
John Alanbrook f4ee9d8228 Shader upade works on windows 2024-05-15 16:34:03 -05:00
John Alanbrook cadf10b3a9 Move rendering functions into prosperon 2024-05-15 14:16:05 -05:00
John Alanbrook f1813a046a Fix transform issues 2024-05-15 07:54:19 -05:00
John Alanbrook f8db84538f Correct metal rendering 2024-05-14 15:22:24 -05:00
John Alanbrook 3b3e58cf39 Split up end pass and commit 2024-05-13 14:07:00 -05:00
John Alanbrook 2a1ea67cec Merge transforms into single type 2024-05-12 18:36:14 -05:00
John Alanbrook 36505ebf51 Particles now work with ssbos 2024-05-10 07:55:53 -05:00
John Alanbrook 011d1d99d5 Update sokol gfx 2024-05-09 21:15:19 -05:00
John Alanbrook d43d9d8fe3 Move buffer binding assignment to javascript 2024-05-09 20:37:11 -05:00
John Alanbrook 22c38fe481 Unify pipeline generation 2024-05-08 11:07:19 -05:00
John Alanbrook 4caea247a6 Merge branch 'origin/sprite' 2024-05-06 23:24:24 -05:00
John Alanbrook 0125019b4d Split handmademath into header and source 2024-05-06 23:24:21 -05:00
John Alanbrook a6f6921262 Split handmademath into header and source 2024-05-06 23:20:37 -05:00
John Alanbrook 44b3bf3a7d Move primitive support to javascript 2024-05-06 21:59:22 -05:00
John Alanbrook 97e258ae7c Dynamic on load text shaders 2024-05-03 16:26:40 -05:00
John Alanbrook 79e4772f93 Separate entity from rigidbody 2024-05-02 17:13:09 -05:00
John Alanbrook e86e126894 Sprite, transform, render overhaul 2024-05-02 13:52:28 -05:00
John Alanbrook 41eadce13e Set uniform data from javascript 2024-04-30 10:32:27 -05:00
John Alanbrook 71fda604ee render 2024-04-28 13:33:37 -05:00
John Alanbrook 0052a89c41 3d skeletal animation 2024-04-26 16:04:31 -05:00
John Alanbrook 5af7d0a2e0 fix normals and uv on 3d models 2024-04-23 18:12:45 -05:00
John Alanbrook 8e337db1e5 Sprite rendered quad 2024-04-23 15:58:08 -05:00
John Alanbrook 9d93f603f0 Sprite rendered in javascript initial 2024-04-21 10:05:18 -05:00
149 changed files with 85957 additions and 8778 deletions

131
Makefile
View file

@ -1,11 +1,12 @@
MAKEFLAGS = --jobs=8 PROCS != nproc --all
MAKEFLAGS = -j $(PROCS)
UNAME != uname UNAME != uname
MAKEDIR != pwd MAKEDIR != pwd
# Options # Options
# NDEBUG --- build with debugging symbols and logging # NDEBUG --- build with debugging symbols and logging
ifeq ($(ARCH),) ifeq ($(ARCH),)
ARCH != uname -m ARCH != uname -m
endif endif
CXX:=$(CC) CXX:=$(CC)
@ -16,12 +17,13 @@ LD = $(CC)
STEAM = steam/sdk STEAM = steam/sdk
STEAMAPI = STEAMAPI =
LDFLAGS += -lstdc++
ifeq ($(CROSS)$(CC), emcc) ifeq ($(CROSS)$(CC), emcc)
LDFLAGS += --shell-file shell.html --closure 1 LDFLAGS += --shell-file shell.html --closure 1
CPPFLAGS += -Wbad-function-cast -Wcast-function-type -sSTACK_SIZE=1MB -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=128MB CPPFLAGS += -Wbad-function-cast -Wcast-function-type -sSTACK_SIZE=1MB -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=128MB
NDEBUG = 1 NDEBUG = 1
ARCH:= wasm ARCH:= wasm
OPT=small
endif endif
ifdef NEDITOR ifdef NEDITOR
@ -51,6 +53,10 @@ else
INFO :=$(INFO)_dbg INFO :=$(INFO)_dbg
endif endif
ifdef NSTEAM
CPPFLAGS += -DNSTEAM
endif
ifdef LEAK ifdef LEAK
CPPFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -DLEAK CPPFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -DLEAK
INFO := $(INFO)_leak INFO := $(INFO)_leak
@ -66,71 +72,70 @@ else ifeq ($(OPT), 1)
CPPFLAGS += -O3 -flto CPPFLAGS += -O3 -flto
INFO :=$(INFO)_opt INFO :=$(INFO)_opt
else else
CPPFLAGS += -O2 CPPFLAGS += -O2
endif endif
CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) -MD $(WARNING_FLAGS) -I. -DVER=\"$(SEM)\" -DCOM=\"$(COM)\" -DINFO=\"$(INFO)\" #-DENABLE_SINC_MEDIUM_CONVERTER -DENABLE_SINC_FAST_CONVERTER -DCP_COLLISION_TYPE_TYPE=uintptr_t -DCP_BITMASK_TYPE=uintptr_t CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) $(WARNING_FLAGS) -I. -DVER=\"$(SEM)\" -DCOM=\"$(COM)\" -DINFO=\"$(INFO)\" -Wno-narrowing #-DENABLE_SINC_MEDIUM_CONVERTER -DENABLE_SINC_FAST_CONVERTER -DCP_COLLISION_TYPE_TYPE=uintptr_t -DCP_BITMASK_TYPE=uintptr_t
CPPFLAGS += -DCONFIG_VERSION=\"2024-02-14\" -DCONFIG_BIGNUM #for quickjs CPPFLAGS += -DCONFIG_VERSION=\"2024-02-14\" -DCONFIG_BIGNUM #for quickjs
# ENABLE_SINC_[BEST|FAST|MEDIUM]_CONVERTER # ENABLE_SINC_[BEST|FAST|MEDIUM]_CONVERTER
# default, fast and medium available in game at runtime; best available in editor # default, fast and medium available in game at runtime; best available in editor
PKGCMD = tar --directory --exclude="./*.a" --exclude="./obj" -czf $(DISTDIR)/$(DIST) .
ZIP = .tar.gz
UNZIP = cp $(DISTDIR)/$(DIST) $(DESTDIR) && tar xzf $(DESTDIR)/$(DIST) -C $(DESTDIR) && rm $(DESTDIR)/$(DIST)
INFO := $(INFO)_$(ARCH) INFO := $(INFO)_$(ARCH)
ifeq ($(OS), Windows_NT) # then WINDOWS ifeq ($(OS), Windows_NT) # then WINDOWS
PLATFORM := win64 PLATFORM := win64
DEPS += resource.o DEPS += resource.o
STEAMAPI := steam_api64 STEAMAPI := steam_api64
LDFLAGS += -mwin32 -static LDFLAGS += -mwin32 -static
CPPFLAGS += -mwin32 CPPFLAGS += -mwin32
LDLIBS += mingw32 kernel32 d3d11 user32 shell32 dxgi gdi32 ws2_32 ole32 winmm setupapi m pthread LDLIBS += mingw32 kernel32 d3d11 user32 shell32 dxgi gdi32 ws2_32 ole32 winmm setupapi m pthread
PKGCMD = zip -q -r $(MAKEDIR)/$(DISTDIR)/$(DIST) . -x \*.a ./obj/\* INFO :=$(INFO)_win
ZIP = .zip EXT = .exe
UNZIP = unzip -o -q $(DISTDIR)/$(DIST) -d $(DESTDIR)
INFO :=$(INFO)_win
EXT = .exe
else ifeq ($(OS), IOS) else ifeq ($(OS), IOS)
CC = /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang CC = /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
SDK = iphoneos SDK = iphoneos
SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/$(SDK).platform/Developer/SDKs/$(SDK).sdk SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/$(SDK).platform/Developer/SDKs/$(SDK).sdk
CFLAGS += -isysroot $(SDK_PATH) -miphoneos-version-min=13.0 CFLAGS += -isysroot $(SDK_PATH) -miphoneos-version-min=13.0
LDFLAGS += -isysroot $(SDK_PATH) -miphoneos-version-min=13.0 LDFLAGS += -isysroot $(SDK_PATH) -miphoneos-version-min=13.0
LDFLAGS += -framework Foundation -framework UIKit -framework AudioToolbox -framework Metal -framework MetalKit -framework AVFoundation LDFLAGS += -framework Foundation -framework UIKit -framework AudioToolbox -framework Metal -framework MetalKit -framework AVFoundation
CXXFLAGS += -std=c++11 CXXFLAGS += -std=c++11
CFLAGS += -x objective-c CFLAGS += -x objective-c -DIOS
INFO :=$(INFO)_ios INFO :=$(INFO)_ios
else ifeq ($(OS), wasm) # Then WEB else ifeq ($(OS), wasm) # Then WEB
OS := Web OS := Web
LDFLAGS += -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 LDFLAGS += -sUSE_WEBGPU
CPPFLAGS += -DNSTEAM CPPFLAGS += -DNSTEAM
LDLIBS += GL openal c m dl LDLIBS += GL openal c m dl
STEAMAPI := STEAMAPI :=
EXT = .html EXT = .html
else else
UNAME != uname -s UNAME != uname -s
ifeq ($(UNAME), Linux) # then LINUX ifeq ($(UNAME), Linux) # then LINUX
OS := Linux OS := Linux
PLATFORM := linux64 PLATFORM := linux64
LDFLAGS += -pthread -rdynamic LDFLAGS += -pthread -rdynamic
LDLIBS += GL pthread c m dl X11 Xi Xcursor EGL asound LDLIBS += pthread c m dl X11 Xi Xcursor EGL asound GL
INFO :=$(INFO)_linux INFO :=$(INFO)_linux
STEAMAPI := steam_api
endif endif
ifeq ($(UNAME), Darwin) ifeq ($(UNAME), Darwin)
OS := macos OS := macos
PLATFORM := osx PLATFORM := osx
CPPFLAGS += -arch $(ARCH) CPPFLAGS += -arch $(ARCH)
CFLAGS += -x objective-c CFLAGS += -x objective-c
CXXFLAGS += -std=c++11 CXXFLAGS += -std=c++11
LDFLAGS += -framework Cocoa -framework QuartzCore -framework AudioToolbox -framework Metal -framework MetalKit LDFLAGS += -framework Cocoa -framework QuartzCore -framework AudioToolbox -framework Metal -framework MetalKit
INFO :=$(INFO)_macos INFO :=$(INFO)_macos
STEAMAPI := steam_api
endif endif
endif endif
ifdef NSTEAM
STEAMAPI =
endif
# All other sources # All other sources
OBJS != find source -type f -name '*.c' | grep -vE 'test|tool|example|fuzz|main' | grep -vE 'quickjs' OBJS != find source -type f -name '*.c' | grep -vE 'test|tool|example|fuzz|main' | grep -vE 'quickjs'
CPPOBJS != find source -type f -name '*.cpp' | grep -vE 'test|tool|example|fuzz|main' CPPOBJS != find source -type f -name '*.cpp' | grep -vE 'test|tool|example|fuzz|main'
@ -145,16 +150,18 @@ OBJS := $(patsubst %.m, %$(INFO).o, $(OBJS))
engineincs != find source/engine -maxdepth 1 -type d engineincs != find source/engine -maxdepth 1 -type d
includeflag != find source -type d -name include includeflag != find source -type d -name include
includeflag += $(engineincs) source/shaders source/engine/thirdparty/sokol source/engine/thirdparty/stb source/engine/thirdparty/cgltf source/engine/thirdparty/TinySoundFont source/engine/thirdparty/dr_libs includeflag += $(engineincs) source/engine/thirdparty/sokol source/engine/thirdparty/stb source/engine/thirdparty/cgltf source/engine/thirdparty/TinySoundFont source/engine/thirdparty/dr_libs source/engine/thirdparty/imgui
includeflag += $(STEAM)/public includeflag += $(STEAM)/public
includeflag += source includeflag += source
includeflag := $(addprefix -I, $(includeflag)) includeflag := $(addprefix -I, $(includeflag))
WARNING_FLAGS = -Wno-incompatible-function-pointer-types -Wno-incompatible-pointer-types WARNING_FLAGS = -Wno-incompatible-function-pointer-types -Wno-incompatible-pointer-types
# For vanilla compilation, remove _
ifeq ($(INFO),_) ifeq ($(INFO),_)
INFO := INFO :=
endif endif
APP = prosperon APP = prosperon
NAME = $(APP)$(INFO)$(EXT) NAME = $(APP)$(INFO)$(EXT)
SEM != git describe --tags --abbrev=0 SEM != git describe --tags --abbrev=0
@ -166,22 +173,33 @@ LDPATHS := $(STEAM)/redistributable_bin/$(PLATFORM)
LDPATHS := $(addprefix -L, $(LDPATHS)) LDPATHS := $(addprefix -L, $(LDPATHS))
DEPENDS = $(OBJS:.o=.d) DEPENDS = $(OBJS:.o=.d)
-include $(DEPENDS)
ifndef VERBOSE
.SILENT:
endif
DEPFLAGS = -MT $(@:.d=.o) -MM -MG $< -o $@
%$(INFO).d: %.c
@echo Making deps $@
$(CROSS)$(CC) $(CPPFLAGS) $(DEPFLAGS)
%$(INFO).d: %.cpp
@echo Making deps $@
$(CROSS)$(CXX) $(CPPFLAGS) $(DEPFLAGS)
%$(INFO).d: %.m
@echo Making deps $@
$(CROSS)$(CC) $(CPPFLAGS) $(DEPFLAGS)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPENDS)
endif
.DEFAULT_GOAL := all .DEFAULT_GOAL := all
all: $(NAME) all: $(NAME)
cp -f $(NAME) $(APP)$(EXT) cp -f $(NAME) $(APP)$(EXT)
SHADERS = $(shell ls source/shaders/*.sglsl)
SHADERS := $(patsubst %.sglsl, %.sglsl.h, $(SHADERS))
prereqs: $(SHADERS) source/engine/core.cdb.h
DESTDIR ?= ~/.bin
install: $(NAME)
@echo Copying to destination
cp -f $(NAME) $(DESTDIR)/$(APP)
$(NAME): $(OBJS) $(DEPS) $(NAME): $(OBJS) $(DEPS)
@echo Linking $(NAME) @echo Linking $(NAME)
$(CROSS)$(LD) $^ $(CPPFLAGS) $(LDFLAGS) -L. $(LDPATHS) $(LDLIBS) -o $@ $(CROSS)$(LD) $^ $(CPPFLAGS) $(LDFLAGS) -L. $(LDPATHS) $(LDLIBS) -o $@
@ -199,16 +217,7 @@ $(NAME): $(OBJS) $(DEPS)
@echo Making Objective-C object $@ @echo Making Objective-C object $@
$(CROSS)$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(CROSS)$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
shaders: $(SHADERS) CORE != (ls icons/* fonts/* shaders/*.cg scripts/*.js*)
@echo Making shaders
%.sglsl.h:%.sglsl
@echo Creating shader $^
./sokol-shdc --ifdef -i $^ --slang=glsl330:hlsl5:metal_macos:metal_ios:metal_sim:glsl300es -o $@
SCRIPTS := $(shell ls scripts/*.js*)
CORE != (ls icons/* fonts/*)
CORE := $(CORE) $(SCRIPTS)
packer: tools/packer.c source/engine/miniz.c packer: tools/packer.c source/engine/miniz.c
@echo Making packer @echo Making packer
@ -218,7 +227,7 @@ core.cdb: packer $(CORE)
@echo Packing core.cdb @echo Packing core.cdb
./packer $@ $(CORE) ./packer $@ $(CORE)
source/engine/core.cdb.h: core.cdb core.cdb.h: core.cdb
@echo Making $@ @echo Making $@
xxd -i $< > $@ xxd -i $< > $@
@ -261,12 +270,12 @@ crosswin:
make CROSS=x86_64-w64-mingw32- OS=Windows_NT CC=gcc make CROSS=x86_64-w64-mingw32- OS=Windows_NT CC=gcc
crossweb: crossweb:
make CROSS=em OS=wasm make CC=emcc OS=wasm
mv $(APP).html index.html mv $(APP).html index.html
clean: clean:
@echo Cleaning project @echo Cleaning project
rm -f source/shaders/*.h core.cdb jso cdb packer TAGS source/engine/core.cdb.h tools/libcdb.a $(APP)* *.icns *.ico rm -f core.cdb jso cdb packer TAGS source/engine/core.cdb.h tools/libcdb.a $(APP)* *.icns *.ico
find . -type f -name "*.[oad]" -delete find . -type f -name "*.[oad]" -delete
rm -rf Prosperon.app rm -rf Prosperon.app

View file

@ -1,10 +0,0 @@
console.stdout_lvl = 4;
say(`config after std`);
window.size = [600,600];
window.rendersize = [200,200];
say(`config after window size`);
globalThis.gamestate = {};
gamestate.grid = 10;
//window.title = "Accio!";
say(`end of config`);

View file

@ -1,57 +1,13 @@
#+title: Prosperon Documentation # Engine Tour
#+author: John Alanbrook
#+options: html-postamble:nil
#+DESCRIPTION: Prosperon documentation
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />
#+HTML_HEAD: <script defer data-domain="prosperon.dev" data-api="https://net.pockle.world/net/event" src="https://net.pockle.world/bat/script.js"></script>
#+HTML_HEAD: <link rel="icon" href="orb.gif" type="image/gif">
#+INFOJS_OPT: view:showall ltoc:above path:org-info.js toc:nil
@@html:
<script src="https://kit.fontawesome.com/a87f68ad0a.js" crossorigin="anonymous"></script>
<nav class="floathead">
<a href="https://prosperon.dev">
<img height=50px src="prosperon_orb_horizontal.gif">
</a>
<a href=#top><i class="fa-solid fa-bars"></i></a>
<a href="https://github.com/johnalanbrook/prosperon"><i class="fa-brands fa-github"></i></a>
<a href="https://x.com/pockleworld"><i class="fa-brands fa-x-twitter"></i></a>
</nav>
@@
* Getting Started
#+begin_scholium
Press 'i' to see the table of contents.
Press '?' for shortcuts on navigating this document!
#+end_scholium
** Installation
*** Linux & MacOS
Install the most [[https://prosperon.dev/download][recent binaries]] into your ~$PATH~.
*** Windows
Copy the executable into any folder and run it. If no game is deteced, it scaffolds for you.
*** Building
You will need ~sokol-shdc~ in the top of your directory. Then, ~make shaders~ and ~make~. The command ~make install~ copies it to your home's ~.bin~ path.
** Playing your first game
Download any of the completed example projects. Run ~prosperon play~ in the folder.
Poke around the example projects. You will find it refreshingly straight forward.
* Engine Tour
Prosperon is built in a code-first fashion. Prosperon is built in a code-first fashion.
** Scripting ## Scripting
The scripting language used in Prosperon is Javascript, with QuickJS. It is [[https://tc39.es/ecma262/2023/][ES2023]] compliant. It is fast, and has a number of features that make it well suited for a video game engine, like ref counting. Read more about it [[https://bellard.org/quickjs/][here]]. The scripting language used in Prosperon is Javascript, with QuickJS. It is ES2023 compliant. It is fast.
#+begin_scholium !!! scholium
Javascript is used here mostly to set up objects and tear them down. Although computationally expensive tasks can be done using QuickJS, Prosperon makes it easy to extend with raw C. Javascript is used here mostly to set up objects and tear them down. Although computationally expensive tasks can be done using QuickJS, Prosperon makes it easy to extend with raw C.
#+end_scholium
*** How Prosperon games are structured ### How Prosperon games are structured
Prosperon schews the CommonJS and ES module systems for a custom one suitable for a computer game actor model. It is more restricted than either system, while retaining their full power. Prosperon schews the CommonJS and ES module systems for a custom one suitable for a computer game actor model. It is more restricted than either system, while retaining their full power.
Prosperon games are structured into two types of source files: Prosperon games are structured into two types of source files:
@ -60,32 +16,34 @@ Prosperon games are structured into two types of source files:
Scripts end with a return statement. A script can return a function, an object of functions, or any other sort of value. Scripts end with a return statement. A script can return a function, an object of functions, or any other sort of value.
#+begin_scholium !!! scholium
It is a common requirement to add some amount of functionality to an object. It can be easily done with a script file. It is a common requirement to add some amount of functionality to an object. It can be easily done with a script file.
#+begin_src
*script.js*
function hello() { say("hello"); };
return hello;
#+end_src
#+begin_src
var o = {};
o.use("hello.js");
o.hello();
#+end_src
The ~use~ function on any object loads a module, and ~assign~s its return to the object.
#+end_scholium
Scripts are loaded into memory only once. Further ~use~ statements only generate references to the statements. Because *scripts* are executed in a lambda environment, any ~var~ declared inside the script files are effectively private variables, persistent variables. ```
*script.js*
function hello() { say("hello"); };
return hello;
```
In a *script*, ~this~ refers to ~undefined~. It is nothng. ```
var o = {};
o.use("hello.js");
o.hello();
```
In an *actor* source file, ~this~ refers to the actor. Actors do not end in a ~return~ statement. *actor* source is intended to set up a new agent in your game. Set up the new entity by loading modules and assigning functions to ~this~. The `use` function on any object loads a module, and `assign`s its return to the object.
*** Script entry points Scripts are loaded into memory only once. Further `use` statements only generate references to the statements. Because *scripts* are executed in a lambda environment, any `var` declared inside the script files are effectively private variables, persistent variables.
In a *script*, `this` refers to `undefined`. It is nothng.
In an *actor* source file, `this` refers to the actor. Actors do not end in a `return` statement. *actor* source is intended to set up a new agent in your game. Set up the new entity by loading modules and assigning functions to `this`.
### Script entry points
The first way you can customize Prosperon is by adding scripts to the folder you're running it from. Any file ending with *.js* is a *script* which can be ran by Prosperon. The first way you can customize Prosperon is by adding scripts to the folder you're running it from. Any file ending with *.js* is a *script* which can be ran by Prosperon.
| script | When called | | script | When called |
|-----------------+---------------------------------------------| |-----------------|---------------------------------------------|
| config.js | First script on Prosperon load | | config.js | First script on Prosperon load |
| game.js | Entry point for running the game | | game.js | Entry point for running the game |
| editorconfig.js | Entry point for running the editor | | editorconfig.js | Entry point for running the editor |
@ -93,98 +51,96 @@ The first way you can customize Prosperon is by adding scripts to the folder you
| debug.js | Debug set up | | debug.js | Debug set up |
| dbgret.js | After stopping debug mode, used for cleanup | | dbgret.js | After stopping debug mode, used for cleanup |
#+begin_scholium !!! scholium
Try it out. Add a script called ~config.js~ to your folder, with this. Try it out. Add a script called `config.js` to your folder, with this.
#+begin_src ```
console.log("Hello world!"); console.log("Hello world!");
Game.quit(); Game.quit();
#+end_src ```
Run ~prosperon~. You will see "Hello world!" in the console, and it shuts down. Run `prosperon`. You will see "Hello world!" in the console, and it shuts down.
#+end_scholium
Using ~config.js~ and ~game.js~, you can write an entire game, without reaching any further. When you want to populate a world with independent actors, entities are what you will reach for.
** Actors Using `config.js` and `game.js`, you can write an entire game, without reaching any further. When you want to populate a world with independent actors, entities are what you will reach for.
## Actors
The fundamental tool for building in Prosperon is the actor system. Actors run independently from each other. Actors are defined by a combination of code and data. All actors have a *master* which controls certain properties of the actor. The fundamental tool for building in Prosperon is the actor system. Actors run independently from each other. Actors are defined by a combination of code and data. All actors have a *master* which controls certain properties of the actor.
The most masterful actor is the *Empyrean*. The first actor you create will have the Empyrean as its master. Subsequent actors can use any other actor as its master. The most masterful actor is the *Empyrean*. The first actor you create will have the Empyrean as its master. Subsequent actors can use any other actor as its master.
| fn | description | | fn | description |
|---------------------+----------------------------------------------------------| |---------------------|----------------------------------------------------------|
| spawn(text, config) | Creates an actor as the padawan of this one, using text | | spawn(text, config) | Creates an actor as the padawan of this one, using text |
| kill() | Kills an actor | | kill() | Kills an actor |
| delay(fn, seconds) | Calls 'fn' after 'seconds' with the context of the actor | | delay(fn, seconds) | Calls 'fn' after 'seconds' with the context of the actor |
*** Actor Lifetime ### Actor Lifetime
When an actor dies, all of the actors that have it as their master will die as well. When an actor dies, all of the actors that have it as their master will die as well.
*** Turns ### Turns
Actors get fragments of time called a *turn*. Actors which belong to different systems can have different lengths of turns. Actors get fragments of time called a *turn*. Actors which belong to different systems can have different lengths of turns.
*** Actor files ### Actor files
Actor files end with the extension *.jso*[fn::"Javascript object".]. They list a series of functions to call on a newly formed actor. Actors have a number of useful functions which are called as defined. Actor files end with the extension *.jso*[fn::"Javascript object".]. They list a series of functions to call on a newly formed actor. Actors have a number of useful functions which are called as defined.
| function | call time | | function | call time |
|----------+----------------------------------------------------------| |----------|----------------------------------------------------------|
| start | The first function called when the actor is in the world | | start | The first function called when the actor is in the world |
| update | Called once per turn | | update | Called once per turn |
| gui | Called on GUI draw | | gui | Called on GUI draw |
| stop | Called when the actor is killed | | stop | Called when the actor is killed |
| gizmo | Called by the editor when the entity is selected | | gizmo | Called by the editor when the entity is selected |
#+begin_scholium !!! scholium
Create a new actor, then kill it. Create a new actor, then kill it.
#+begin_src ```
var act_die_call = function() { var act_die_call = function() {
console.log(`Actor ${this.id} has died.`); console.log(`Actor ${this.id} has died.`);
} }
var act1 = Empyrean.spawn(); var act1 = Empyrean.spawn();
var act2 = actor1.spawn(); var act2 = actor1.spawn();
act1.stop = act_die_call; act1.stop = act_die_call;
act2.stop = act_die_call; act2.stop = act_die_call;
Empyrean.kill(); /* Error: The Empyrean cannot be killed */ Empyrean.kill(); /* Error: The Empyrean cannot be killed */
act1.kill(); act1.kill();
act2.kill(); /* Error: act2 has been killed because act1 was */ act2.kill(); /* Error: act2 has been killed because act1 was */
#+end_src ```
#+end_scholium
#+begin_scholium !!! scholium
Now simplify by putting the code into a file named *hello.jso*. Now simplify by putting the code into a file named *hello.jso*.
#+begin_src ```
this.stop = function() { this.stop = function() {
console.log(`Actor ${this.id} has died.`); console.log(`Actor ${this.id} has died.`);
} }
#+end_src ```
Now spawn two actors using it. Now spawn two actors using it.
#+begin_src ```
var act1 = Empyrean.spawn("hello.jso"); var act1 = Empyrean.spawn("hello.jso");
var act2 = act1.spawn("hello.jso"); var act2 = act1.spawn("hello.jso");
#+end_src ```
#+end_scholium
*** Actor configuration
### Actor configuration
Actors can be created using an optional configuration file. A configuration file is one of any accepted data types. Currently, JSON or [[https://www.crockford.com/nota.html][Nota]]. Configuration files are loaded after an actor's script file, overwriting any defined values on it. Actors can be created using an optional configuration file. A configuration file is one of any accepted data types. Currently, JSON or [[https://www.crockford.com/nota.html][Nota]]. Configuration files are loaded after an actor's script file, overwriting any defined values on it.
#+begin_scholium !!! scholium
Add a name for the actor to take on using a configuration file named *hello.json*. Add a name for the actor to take on using a configuration file named *hello.json*.
#+begin_src ```
{ {
"name": "Actor 1" "name": "Actor 1"
} }
#+end_src ```
Now create *hello.jso* to use it. Now create *hello.jso* to use it.
#+begin_src ```
this.start = function() { console.log(`I, ${this.name}, have been created.`); } this.start = function() { console.log(`I, ${this.name}, have been created.`); }
#+end_src ```
#+end_scholium
** Entities
## Entities
Game worlds are made of entities. Entities are a type of actor with a number of useful properties. Entities can only be created on the actor named *Primum*[fn::See the Primum Mobile]. The Primum is the outermost actor with a physical space. While Actors are more abstract, Entities exist in a definite space, with a position, rotation, and so on. Entities can respond to physics and play sounds. Anything which can be thought of as having a position in space should be an entitiy. Game worlds are made of entities. Entities are a type of actor with a number of useful properties. Entities can only be created on the actor named *Primum*[fn::See the Primum Mobile]. The Primum is the outermost actor with a physical space. While Actors are more abstract, Entities exist in a definite space, with a position, rotation, and so on. Entities can respond to physics and play sounds. Anything which can be thought of as having a position in space should be an entitiy.
#+begin_scholium !!! scholium
The first and most masterful entity is the Primum. The Primum has no components, and its rotation and position are zero. It defines the center of the game. The first and most masterful entity is the Primum. The Primum has no components, and its rotation and position are zero. It defines the center of the game.
#+end_scholium
In editor mode, when an entity moves, all of its *padawans* also move. In editor mode, when an entity moves, all of its *padawans* also move.
@ -192,26 +148,26 @@ When the game is actively simulating, this only holds if there are physical cons
Prosperon automatically generates physical pin constraints between objects with the appropriate physical properties. Prosperon automatically generates physical pin constraints between objects with the appropriate physical properties.
*** Adding Components ### Adding Components
Entities can have *components*. Components are essentially javascript wrappers over C code into the engine. Scripting is done to set the components up on entities, after which most of the work is done by the C plugin. Entities can have *components*. Components are essentially javascript wrappers over C code into the engine. Scripting is done to set the components up on entities, after which most of the work is done by the C plugin.
#+begin_scholium !!! scholium
For example, to render an image, set up a *sprite* component on an entity and point its path to an image on your harddrive. For example, to render an image, set up a *sprite* component on an entity and point its path to an image on your harddrive.
#+begin_src ```
var ent = Empyrean.spawn(); var ent = Empyrean.spawn();
var spr = ent.add_component(component.sprite); var spr = ent.add_component(component.sprite);
spr.path = "image.png"; spr.path = "image.png";
#+end_src ```
Put that into your config file and run ~prosperon~. You should see the contents of "image.png" on the screen. Put that into your config file and run `prosperon`. You should see the contents of "image.png" on the screen.
Try using an animated gif. Prosperon has native support for gif animations!
Try using an animated gif. Prosperon has native support for gif animations!
#+end_scholium
Components only work in the context of an entity. They have no meaning outside of a physical object in the world. They have no inherent scripting capabilities. Components only work in the context of an entity. They have no meaning outside of a physical object in the world. They have no inherent scripting capabilities.
While components can be added via scripting, it is easier to add them via the editor, as we will later see. While components can be added via scripting, it is easier to add them via the editor, as we will later see.
*** Ur system ### Ur system
The ur[fn::A German prefix meaning primitive, original, or earliest.] system is a prototypical inheritence system used by the actor files. When actor files are loaded, they are stored as an ur. An *ur* holds a list of (text, config) required to create an entity. The ur[fn::A German prefix meaning primitive, original, or earliest.] system is a prototypical inheritence system used by the actor files. When actor files are loaded, they are stored as an ur. An *ur* holds a list of (text, config) required to create an entity.
When prosperon starts, it searches for urs by name. Any file ending in ".jso" or ".json" will be interpereted as an ur, with same named jso and json being applied as (text, config) for an ur. A jso or json alone also constitute an ur. When prosperon starts, it searches for urs by name. Any file ending in ".jso" or ".json" will be interpereted as an ur, with same named jso and json being applied as (text, config) for an ur. A jso or json alone also constitute an ur.
@ -225,14 +181,13 @@ An ur can also be defined by a json file. If an ur is found, it takes predecent
Any ur file with this sort of json creates an ur which can be created in the game. A file named "box.ur" will be ingested and be available as "ur.box". When saving differences, it creates a json file with the same name as an ur (in this case, "box.json"). Any ur file with this sort of json creates an ur which can be created in the game. A file named "box.ur" will be ingested and be available as "ur.box". When saving differences, it creates a json file with the same name as an ur (in this case, "box.json").
#+begin_scholium !!! scholium
Create an ur from the *hello* files above, and then spawn it. Create an ur from the *hello* files above, and then spawn it.
#+begin_src ```
ur.create("hello", "hello.jso", "hello.json"); ur.create("hello", "hello.jso", "hello.json");
Primum.spawn(ur.hello); Primum.spawn(ur.hello);
#+end_src ```
When creating an actor from source files, all of its setup must take place. In this example, the setup happens during *ur.create*, and spawning is simply a matter of prototyping it. When creating an actor from source files, all of its setup must take place. In this example, the setup happens during *ur.create*, and spawning is simply a matter of prototyping it.
#+end_scholium
This method allows high composability of game objects. This method allows high composability of game objects.
@ -240,68 +195,67 @@ If an entity is created without an ur, is ur is defined as its given text and da
Objects can be composed on the fly by stringing together urs. For example, a "2x.json" might define scale as 2x. Then, you can create a goblin with `ur.goblin`, or a large goblin with `ur.goblin.2x`. This creates a goblin object, and then applies the 2x scripts and jsons onto the object. Objects can be composed on the fly by stringing together urs. For example, a "2x.json" might define scale as 2x. Then, you can create a goblin with `ur.goblin`, or a large goblin with `ur.goblin.2x`. This creates a goblin object, and then applies the 2x scripts and jsons onto the object.
*** Urs in game ### Urs in game
Each ur has the following fields. Each ur has the following fields.
| field | description | | field | description |
|-----------+-------------------------------------------------------------| |-----------|-------------------------------------------------------------|
| instances | An array of instances of this ur | | instances | An array of instances of this ur |
| name | Name of the ur | | name | Name of the ur |
| text | Path to the script file | | text | Path to the script file |
| data | Object to write to a newly generated actor | | data | Object to write to a newly generated actor |
| proto | An object that looks like a freshly made entity from the ur | | proto | An object that looks like a freshly made entity from the ur |
An *ur* has a full path given like ~ur.goblin.big~. ~goblin~ and ~big~ can both possibly have a *.jso* script as well as a *data* file. An *ur* has a full path given like `ur.goblin.big`. `goblin` and `big` can both possibly have a *.jso* script as well as a *data* file.
When ~goblin.big~ is created, the new object has the ~goblin~ script run on it, followed by the ~big~ script. The ~data~ fields consist of objects prototyped from each other, so that the ~__proto__~ of ~big.data~ is ~goblin.data~. All fields of this objects are assigned to the ~big goblin~. When `goblin.big` is created, the new object has the `goblin` script run on it, followed by the `big` script. The `data` fields consist of objects prototyped from each other, so that the `__proto__` of `big.data` is `goblin.data`. All fields of this objects are assigned to the `big goblin`.
The unaltered form of every ur-based-entity is saved in the ur's ~proto~ field. As you edit objects, the differences between how your object is now, compared to its ~ur.proto~ is a list of differences. These differences can be rolled into the ~ur~, or saved as a subtype. The unaltered form of every ur-based-entity is saved in the ur's `proto` field. As you edit objects, the differences between how your object is now, compared to its `ur.proto` is a list of differences. These differences can be rolled into the `ur`, or saved as a subtype.
*** Prototyping Entities ### Prototyping Entities
Ur types are the prototype of created entities. This makes it trivial to change huge swathes of the game, or make tiny adjustments to single objects, in a natural and intuitive way. When a value is changed on an entity, it is private. When a value is changed on an ur, it propogates to all entities. Values cannot be added or removed in subtypes. Ur types are the prototype of created entities. This makes it trivial to change huge swathes of the game, or make tiny adjustments to single objects, in a natural and intuitive way. When a value is changed on an entity, it is private. When a value is changed on an ur, it propogates to all entities. Values cannot be added or removed in subtypes.
Entities all have the following functions to assist with this: Entities all have the following functions to assist with this:
| function | use | | function | use |
|---------------+---------------------------------------------| |---------------|---------------------------------------------|
| clone(parent) | Create an entity prototyped from the parent | | clone(parent) | Create an entity prototyped from the parent |
| dup(parent) | Create an exact duplicate of the parent | | dup(parent) | Create an exact duplicate of the parent |
| revert() | Removes all local changes on the entity | | revert() | Removes all local changes on the entity |
Speaking of practical experience, is best for ur prototype chains to be shallow. Speaking of practical experience, is best for ur prototype chains to be shallow.
*** Spawning ### Spawning
Actor data and ur types can remember which entities were contained in it when saving. They are stored in the *objects* field. When an entity with an *objects* field is spawned, it spawns all of the objects listed in turn. Actor data and ur types can remember which entities were contained in it when saving. They are stored in the *objects* field. When an entity with an *objects* field is spawned, it spawns all of the objects listed in turn.
When an entity is spawned, it is addressable directly through its master entity. Its name is generated from its file or ur type name. When an entity is spawned, it is addressable directly through its master entity. Its name is generated from its file or ur type name.
#+begin_scholium !!! scholium
Let's take a simple RPG game. Let's take a simple RPG game.
#+begin_src ```
Primum Primum
level1 level1
orc orc
goblin goblin
human human
sword sword
ui ui
#+end_src ```
The orc, for example, is addressable by ~Primum.level1.orc~. The ~human~ has a ~sword~ spawned underneath it. When he is killed, his sword also disappears. The orc, for example, is addressable by `Primum.level1.orc`. The `human` has a `sword` spawned underneath it. When he is killed, his sword also disappears.
#+end_scholium
*** Resources ### Resources
Assets can generally be used simply with their filename. Assets can be modified with a sidecar file named *filename.asset*, so, a file ~ball.png~ can have additional parameters through its ~ball.png.asset~ file. Assets can generally be used simply with their filename. Assets can be modified with a sidecar file named *filename.asset*, so, a file `ball.png` can have additional parameters through its `ball.png.asset` file.
| sigil | meaning | | sigil | meaning |
|--------+------------------------| |--------|------------------------|
| \slash | root of project | | \slash | root of project |
| @ | root of save directory | | @ | root of save directory |
| # | root of link | | # | root of link |
Resources can be referenced in a relative manner by actor scripts. When it comes to actors using assets, relative filepaths are useful and encouraged. Resources can be referenced in a relative manner by actor scripts. When it comes to actors using assets, relative filepaths are useful and encouraged.
#+begin_src ```
/ /
score.wav score.wav
/bumper /bumper
@ -310,22 +264,21 @@ Resources can be referenced in a relative manner by actor scripts. When it comes
/ball /ball
hit.wav hit.wav
ball.jso ball.jso
#+end_src ```
Path resolution occurs during actor creation. In effect, a reference to *hit.wav* in *bumper.jso* will resolve to the absolute path */bumper/hit.wav*. Path resolution occurs during actor creation. In effect, a reference to *hit.wav* in *bumper.jso* will resolve to the absolute path */bumper/hit.wav*.
If the asset is not found, it is searched for until the project root is reached. The bumper can reference *score.wav* and have the path resolution take place. Later, if the it is decided for the bumper to have a unique score sound, a new /score.wav/ can be placed in its folder and it will work without changing any code. If the asset is not found, it is searched for until the project root is reached. The bumper can reference *score.wav* and have the path resolution take place. Later, if the it is decided for the bumper to have a unique score sound, a new /score.wav/ can be placed in its folder and it will work without changing any code.
#+begin_scholium !!! caution
Caution! Because the path is resolved during object load, you will need to fresh the bumper's ur or spawn a new bumper for it to use the newly placed /score.wav/. Because the path is resolved during object load, you will need to fresh the bumper's ur or spawn a new bumper for it to use the newly placed /score.wav/.
#+end_scholium
**** Links ###* Links
Links can be specified using the "#" sign. These are shortcuts you can specify for large projects. Specify them in the array ~Resources.links~. Links can be specified using the "#" sign. These are shortcuts you can specify for large projects. Specify them in the array `Resources.links`.
An example is of the form ~trees:/world/assets/nature/trees~. Links are called with ~#~, so you can now make a "fern" with ~Primum.spawn("#trees/fern.jso")~. An example is of the form `trees:/world/assets/nature/trees`. Links are called with `#`, so you can now make a "fern" with `Primum.spawn("#trees/fern.jso")`.
*** Ur auto creation ### Ur auto creation
Instead of coding all the ur type creation by hand, Prosperon can automatically search your project's folder and create the ur types for you. Any /[name].jso/ file is converted into an ur with the name. Any /[name].json/ file is then applied over it, should it exist. If there is a /.json/ file without a corresponding /.jso/, it can still be turned into an ur, if it is a valid ur format. Instead of coding all the ur type creation by hand, Prosperon can automatically search your project's folder and create the ur types for you. Any /[name].jso/ file is converted into an ur with the name. Any /[name].json/ file is then applied over it, should it exist. If there is a /.json/ file without a corresponding /.jso/, it can still be turned into an ur, if it is a valid ur format.
Folders and files beginning with a '.' (hidden) or a '_' will be ignored for ur creation. Folders and files beginning with a '.' (hidden) or a '_' will be ignored for ur creation.
@ -334,7 +287,7 @@ The folder hierarchy of your file system determines the ur prototype chain. /.js
Only one ur of any name can be created. Only one ur of any name can be created.
#+begin_src ```
@/ @/
flipper.js flipper.js
flipper/ flipper/
@ -345,50 +298,50 @@ Only one ur of any name can be created.
flipper.js flipper.js
left/ left/
left.js left.js
#+end_src ```
~prototypes.generate_ur(path)~ will generate all ur-types for a given path. You can preload specific levels this way, or the entire game using ~prototypes.generate_ur('.')~. If your game is small enough, this can have a massive runtime improvement. `prototypes.generate_ur(path)` will generate all ur-types for a given path. You can preload specific levels this way, or the entire game using `prototypes.generate_ur('.')`. If your game is small enough, this can have a massive runtime improvement.
** Input ## Input
Input is done in a highly generic and customizable manner. *players* can take control of any object (actor or otherwise) in Prosperon, after which it is referred to as a *pawn* of a player. If the object has a defined *input* object, it is a valid pawn. One player can have many pawns, but each pawn may have only one player. Input is done in a highly generic and customizable manner. *players* can take control of any object (actor or otherwise) in Prosperon, after which it is referred to as a *pawn* of a player. If the object has a defined *input* object, it is a valid pawn. One player can have many pawns, but each pawn may have only one player.
Pawns are added as a stack, with the newest ones getting priority, and handled first. It is possible for pawns to block input to lower pawns on the stack. Pawns are added as a stack, with the newest ones getting priority, and handled first. It is possible for pawns to block input to lower pawns on the stack.
#+begin_src ```
/newest/ /newest/
car <== When a key is pressed, this is the first pawn to handle input car <== When a key is pressed, this is the first pawn to handle input
player player
ui <== /block/ is set to true here, so editor recieves no input! ui <== /block/ is set to true here, so editor recieves no input!
editor editor
/oldest/ /oldest/
#+end_src ```
The default player can be obtained with ~Player.players[0]~. Players are all local, and the highest number is determined by platform. The default player can be obtained with `Player.players[0]`. Players are all local, and the highest number is determined by platform.
The *input* object defines a number of keys or actions, with their values being functions. The *input* object defines a number of keys or actions, with their values being functions.
*** Editor input ### Editor input
The editor input style defines keystrokes. It is good for custom editors, or any sort of game that requires many hotkeys. Keystrokes are case sensitive and can be augmented with auxiliary keys. The editor input style defines keystrokes. It is good for custom editors, or any sort of game that requires many hotkeys. Keystrokes are case sensitive and can be augmented with auxiliary keys.
| symbol | key | | symbol | key |
|--------+-------| |--------|-------|
| C | ctrl | | C | ctrl |
| M | alt | | M | alt |
| S | super | | S | super |
#+begin_src ```
var orc = Primum.spawn(ur.orc); var orc = Primum.spawn(ur.orc);
orc.inputs = {}; orc.inputs = {};
orc.inputs.a = function() { ... }; orc.inputs.a = function() { ... };
orc.inputs.A = function() { ... }; /* This is only called with a capital A! */ orc.inputs.A = function() { ... }; /* This is only called with a capital A! */
orc.inputs['C-a'] = function() { ... }; /* Control-a */ orc.inputs['C-a'] = function() { ... }; /* Control-a */
Player.players[0].control(orc); /* player 0 is now in control of the orc */ Player.players[0].control(orc); /* player 0 is now in control of the orc */
#+end_src ```
The input object can be modified to customize how it handles input. The input object can be modified to customize how it handles input.
| property | type | effect | | property | type | effect |
|----------------+----------+--------------------------------------| |----------------|----------|--------------------------------------|
| post | function | called after any input is processed | | post | function | called after any input is processed |
| =release_post= | function | called after any input is released | | =release_post= | function | called after any input is released |
| fallthru | bool | false if input should stop with this | | fallthru | bool | false if input should stop with this |
@ -397,27 +350,27 @@ The input object can be modified to customize how it handles input.
The input can be modified by setting properties on the associated function. The input can be modified by setting properties on the associated function.
| property | type | effect | | property | type | effect |
|----------+----------+--------------------------------------------------------| |----------|----------|--------------------------------------------------------|
| released | function | Called when the input is released | | released | function | Called when the input is released |
| rep | bool | true if holding the input should repeatedly trigger it | | rep | bool | true if holding the input should repeatedly trigger it |
| down | function | called while the input is down | | down | function | called while the input is down |
** GUI ## GUI
Game GUIs are written by registering an entity's *gui* property to a function. Game GUIs are written by registering an entity's *gui* property to a function.
The GUI system which ships with Prosperon is called *MUM*. MUM is a declarative, immediate mode interface system. Immediate to eliminate the issue of data synchronization in the game. The GUI system which ships with Prosperon is called *MUM*. MUM is a declarative, immediate mode interface system. Immediate to eliminate the issue of data synchronization in the game.
All GUI objects derive from MUM. MUM has a list of properties, used for rendering. Mum also has functions which cause drawing to appear on the screen. All GUI objects derive from MUM. MUM has a list of properties, used for rendering. Mum also has functions which cause drawing to appear on the screen.
** Physics ## Physics
Prospeorn comes with the [[https://chipmunk-physics.net][Chipmunk]] physics engine built in. It is a fast, stable physics solution. All entities are assumed to be physics based objects, and components can be added to them to enable more physics features. Prospeorn comes with the [[https://chipmunk-physics.net][Chipmunk]] physics engine built in. It is a fast, stable physics solution. All entities are assumed to be physics based objects, and components can be added to them to enable more physics features.
* Editor Tour * Editor Tour
Prosperon's visual editor is an assistant for the creation and editing of your game entities and actors. In the editor, all ur types are loaded, and assets are constantly monitored for changes for hot reloading. Prosperon's visual editor is an assistant for the creation and editing of your game entities and actors. In the editor, all ur types are loaded, and assets are constantly monitored for changes for hot reloading.
To initiate it, execute ~prosperon~. To initiate it, execute `prosperon`.
** Editing entities ## Editing entities
The desktop is the topmost entity that exists in the editor. Instead of editing specific files, you simply load them into your desktop, and go from there. This makes it easier to see two different entities simultaneously so you can ensure changes to one are congruous with the vision for the others. The desktop is the topmost entity that exists in the editor. Instead of editing specific files, you simply load them into your desktop, and go from there. This makes it easier to see two different entities simultaneously so you can ensure changes to one are congruous with the vision for the others.
The main editor view is made up of entities. Each entity can have a number of components attached to it. When an entity is selected, its name, position, and list of components are listed. The main editor view is made up of entities. Each entity can have a number of components attached to it. When an entity is selected, its name, position, and list of components are listed.
@ -426,47 +379,46 @@ Basic use of the editor involves spawning new entities, or ones from already mad
Assign the entity's *gizmo* property to a function to have that function called each gui rendering frame. Assign the entity's *gizmo* property to a function to have that function called each gui rendering frame.
** The REPL[fn::Read-eval-print loop] ## The REPL[fn::Read-eval-print loop]
The REPL lets you poke around in the game. It makes iteration and experimentation fast, fun, and easy. The REPL lets you poke around in the game. It makes iteration and experimentation fast, fun, and easy.
The symbol ~$~ references the current REPL entity. If no entity is selected, the REPL entity is the currently edited one. Otherwise, it is the selected entity, or group of entities, as an array. The symbol `$` references the current REPL entity. If no entity is selected, the REPL entity is the currently edited one. Otherwise, it is the selected entity, or group of entities, as an array.
#+begin_scholium !!! scholium
Easily run commands on multiple entities using Javascript functions like for each. Easily run commands on multiple entities using Javascript functions like for each.
#+begin_src ```
$.forEach(e => console.log(e.pos)); $.forEach(e => console.log(e.pos));
#+end_src ```
#+end_scholium
The REPL is a powerful tool for editing your game. Arbitrary code can be ran in it, meaning any esoteric activity you need done for your game can be done easily. Commonly used functions should be copied into your /editorconfig.js/ to be called and used at will. The REPL is a powerful tool for editing your game. Arbitrary code can be ran in it, meaning any esoteric activity you need done for your game can be done easily. Commonly used functions should be copied into your /editorconfig.js/ to be called and used at will.
** Playing the game ## Playing the game
Playing the game involves running the game from a special /debug.js/ file, or from the beginning, as if the game were packaged and shipped. Playing the game involves running the game from a special /debug.js/ file, or from the beginning, as if the game were packaged and shipped.
| key | action | | key | action |
|-------+-----------------------------------------------------| |-------|-----------------------------------------------------|
| f5 | Play the game, starting with entry point /debug.js/ | | f5 | Play the game, starting with entry point /debug.js/ |
| f6 | Play the game from the beginning | | f6 | Play the game from the beginning |
While playing the game, a limited editor is available that allows for simple debugging tasks. While playing the game, a limited editor is available that allows for simple debugging tasks.
| key | action | | key | action |
|-----+-----------------------------| |-----|-----------------------------|
| C-p | Pause | | C-p | Pause |
| M-p | One time step | | M-p | One time step |
| C-q | Quit play, return to editor | | C-q | Quit play, return to editor |
** Script Editor ## Script Editor
Prosperon comes with an in-engine script editor. It implements a subset of emacs, and adds a few engine specific features. Prosperon comes with an in-engine script editor. It implements a subset of emacs, and adds a few engine specific features.
*** Syntax coloring? ... nope! ### Syntax coloring? ... nope!
The editor that ships with Prosperon has *context coloring*, which is a good deal more useful than syntax coloring. The editor that ships with Prosperon has *context coloring*, which is a good deal more useful than syntax coloring.
** Debugging ## Debugging
Debugging functions are mapped to the F buttons, and are available in any debug build of the game. Pressing the specified key toggles the feature; pressing it with /alt/ shows a legend for that feature. Debugging functions are mapped to the F buttons, and are available in any debug build of the game. Pressing the specified key toggles the feature; pressing it with /alt/ shows a legend for that feature.
| key | description | | key | description |
|-----+----------------------------| |-----|----------------------------|
| F1 | Draw physics info | | F1 | Draw physics info |
| F3 | Draw bounding boxes | | F3 | Draw bounding boxes |
| F12 | Draw gui info | | F12 | Draw gui info |
@ -478,7 +430,7 @@ Prosperon is a multiplatform engine. Bundling your game for these platforms esse
- Conversion of assets - Conversion of assets
- Packing into a CDB[fn::Constant database] - Packing into a CDB[fn::Constant database]
To distribute your game for a given platform, run ~prosperon build {platform}~. To distribute your game for a given platform, run `prosperon build {platform}`.
| platform | | platform |
|----------| |----------|
@ -488,7 +440,7 @@ To distribute your game for a given platform, run ~prosperon build {platform}~.
You will find your game ready to go. Rename the executable to the name of your game and run it to play. Congratulations! You will find your game ready to go. Rename the executable to the name of your game and run it to play. Congratulations!
** Building static content ## Building static content
Static content creation involves any number of optimizations. Static content creation involves any number of optimizations.
- Bitmap font creation - Bitmap font creation
@ -496,13 +448,13 @@ Static content creation involves any number of optimizations.
Creation of these assets is invisible. Prosperon updates its understanding of how to pull assets based on the existance of these packed ones. Creation of these assets is invisible. Prosperon updates its understanding of how to pull assets based on the existance of these packed ones.
** Converting assets ## Converting assets
Images, videos, and sounds, are converted to assets most suitable for the target platform. This may be for speed or simple compatability. *You do not need to do anything*. Use your preferred asset types during production. Images, videos, and sounds, are converted to assets most suitable for the target platform. This may be for speed or simple compatability. *You do not need to do anything*. Use your preferred asset types during production.
** Packing into a CDB ## Packing into a CDB
A *cdb* is known as a "constant database". It is a write once type of database, with extremely fast retrieval times. Packing your game into a cdb means to create a database with key:value pairs of the filenames of your game. The Prosperon executable is already packed with a core cdb. Your game assets are packed alongside it as the game cdb. A *cdb* is known as a "constant database". It is a write once type of database, with extremely fast retrieval times. Packing your game into a cdb means to create a database with key:value pairs of the filenames of your game. The Prosperon executable is already packed with a core cdb. Your game assets are packed alongside it as the game cdb.
You can create your game's cdb by running ~prosperon -b~. You will find a *game.cdb* in the root directory. You can create your game's cdb by running `prosperon -b`. You will find a *game.cdb* in the root directory.
* Modding & Patching * Modding & Patching
When an asset is requested in Prosperon, it is searched for in the following manner. When an asset is requested in Prosperon, it is searched for in the following manner.
@ -513,13 +465,13 @@ When an asset is requested in Prosperon, it is searched for in the following man
Game modification is trivial using this described system. By putting an asset in the same path as the asset's location in the game cdb, when that asset is requested it will be pulled from the file system instead of the game cdb. Game modification is trivial using this described system. By putting an asset in the same path as the asset's location in the game cdb, when that asset is requested it will be pulled from the file system instead of the game cdb.
Given a Prosperon-built game, you can unpack its content into a directory by running ~prosperon unpack {game}~. Given a Prosperon-built game, you can unpack its content into a directory by running `prosperon unpack {game}`.
** Shipping ## Shipping
Once a game's assets are modified, it may be desirable to ship them. Run ~prosperon patch create {game}~ to create a /patch.cdb/ filled only with the files that are different compared to those found in the /game.cdb/ in the /game/. Once a game's assets are modified, it may be desirable to ship them. Run `prosperon patch create {game}` to create a /patch.cdb/ filled only with the files that are different compared to those found in the /game.cdb/ in the /game/.
To update /game/ to use the new patch, run ~prosperon patch apply {patch}~, replacing /patch/ with the name of the cdb file generated above. To update /game/ to use the new patch, run `prosperon patch apply {patch}`, replacing /patch/ with the name of the cdb file generated above.
Many patches can be bundled by running ~prosperon patch bundle {list of patches}~. This creates a patch that will update the game as if the user had updated each patch in order. Many patches can be bundled by running `prosperon patch bundle {list of patches}`. This creates a patch that will update the game as if the user had updated each patch in order.
Mods can be distributed with the same idea. Mods can be distributed with the same idea.

16
doc/docs/start.md Normal file
View file

@ -0,0 +1,16 @@
# Getting Started
## Installation
### Linux & MacOS
Install the most [[https://prosperon.dev/download][recent binaries]] into your `$PATH`.
### Windows
Copy the executable into any folder and run it. If no game is deteced, it scaffolds for you.
### Building
You will need `sokol-shdc` in the top of your directory. Then, `make shaders` and `make`. The command `make install` copies it to your home's `.bin` path.
## Playing your first game
Download any of the completed example projects. Run `prosperon play` in the folder.
Poke around the example projects. You will find it refreshingly straight forward.

11
doc/mkdocs.yml Normal file
View file

@ -0,0 +1,11 @@
site_name: Prosperon Documentation
repo_url: https://forge.pockle.world/john/prosperon
theme:
name: material
plugins:
- search
markdown_extensions:
- admonition
- tables

View file

@ -2,7 +2,6 @@ var actor = {};
actor.spawn = function(script, config, callback){ actor.spawn = function(script, config, callback){
if (typeof script !== 'string') return undefined; if (typeof script !== 'string') return undefined;
console.info(`spawning actor with script ${script}`);
var padawan = Object.create(actor); var padawan = Object.create(actor);
use(script, padawan); use(script, padawan);

View file

@ -358,13 +358,6 @@ Object.readonly = function(o, name, msg)
Object.defineProperty(o, name, prop); Object.defineProperty(o, name, prop);
} }
Object.extend = function(from)
{
var n = {};
Object.mixin(n, from);
return n;
}
Object.mixin = function(target, source) Object.mixin = function(target, source)
{ {
if (typeof source !== 'object') return target; if (typeof source !== 'object') return target;
@ -542,10 +535,8 @@ Object.unhide = function(obj, ...props)
{ {
for (var prop of props) { for (var prop of props) {
var p = Object.getOwnPropertyDescriptor(obj,prop); var p = Object.getOwnPropertyDescriptor(obj,prop);
if (!p) { if (!p)
console.warn(`No property of name ${prop}.`); continue;
return;
}
p.enumerable = true; p.enumerable = true;
Object.defineProperty(obj, prop, p); Object.defineProperty(obj, prop, p);
} }
@ -810,6 +801,13 @@ Object.defineProperty(String.prototype, 'name', {
return this.fromlast('/').tolast('.'); } return this.fromlast('/').tolast('.'); }
}); });
Object.defineProperty(String.prototype, 'set_name', {
value: function(name) {
var dir = this.dir();
return this.dir() + name + "." + this.ext();
}
});
Object.defineProperty(String.prototype, 'base', { Object.defineProperty(String.prototype, 'base', {
value: function() { return this.fromlast('/'); } value: function() { return this.fromlast('/'); }
}); });
@ -820,6 +818,12 @@ Object.defineProperty(String.prototype, 'splice', {
} }
}); });
Object.defineProperty(String.prototype, 'sub', {
value: function(index, str) {
return this.slice(0,index) + str + this.slice(index+str.length);
}
});
Object.defineProperty(String.prototype, 'rm', { Object.defineProperty(String.prototype, 'rm', {
value: function(index, endidx = index+1) { return this.slice(0,index) + this.slice(endidx); } value: function(index, endidx = index+1) { return this.slice(0,index) + this.slice(endidx); }
}); });
@ -1163,11 +1167,6 @@ Object.defineProperty(Array.prototype, 'anyjs', {
}); });
/* Return true if array contains x */ /* Return true if array contains x */
/*Object.defineProperty(Array.prototype, 'includes', {
value: function(x) {
return this.some(e => e === x);
}});
*/
Object.defineProperty(Array.prototype, 'empty', { Object.defineProperty(Array.prototype, 'empty', {
get: function() { return this.length === 0; }, get: function() { return this.length === 0; },
}); });
@ -1185,6 +1184,9 @@ Object.defineProperty(Array.prototype, 'unique', {
} }
}); });
Object.defineProperty(Array.prototype, 'unduped', {
value: function() { return [... new Set(this)]; }
});
Object.defineProperty(Array.prototype, 'findIndex', { Object.defineProperty(Array.prototype, 'findIndex', {
value: function(fn) { value: function(fn) {
@ -1217,6 +1219,15 @@ Object.defineProperty(Array.prototype, 'find', {
return ret; return ret;
}}); }});
Object.defineProperty(Array.prototype, 'search', {
value: function(val) {
for (var i = 0; i < this.length; i++)
if (this[i] === val) return i;
return undefined;
}
});
Object.defineProperty(Array.prototype, 'last', { Object.defineProperty(Array.prototype, 'last', {
value: function() { return this[this.length-1]; }, value: function() { return this[this.length-1]; },
}); });
@ -1336,16 +1347,19 @@ Math.snap = function(val, grid) {
} }
Math.angledist = function (a1, a2) { Math.angledist = function (a1, a2) {
a1 = Math.turn2deg(a1); a1 = a1%1;
a2 = Math.turn2deg(a2); a2 = a2%1;
var dist = a2 - a1; var dist = a2 - a1;
var wrap = dist >= 0 ? dist+360 : dist-360; if (dist == 0) return dist;
wrap %= 360;
if (Math.abs(dist) < Math.abs(wrap)) if (dist > 0) {
return Math.deg2turn(dist); if (dist > 0.5) return dist-1;
return dist;
}
return Math.deg2turn(wrap); if (dist < -0.5) return dist+1;
return dist;
}; };
Math.angledist.doc = "Find the shortest angle between two angles."; Math.angledist.doc = "Find the shortest angle between two angles.";
Math.TAU = Math.PI*2; Math.TAU = Math.PI*2;
@ -1492,6 +1506,16 @@ Vector.random = function() {
return Vector.norm(vec); return Vector.norm(vec);
} }
Vector.angle_between = function(a,b)
{
var dot = Vector.dot(a,b);
var am = Vector.length(a);
var bm = Vector.length(b);
var cos_a = dot / (am*bm);
var angle = Math.acos(cos_a);
return Math.rad2turn(angle);
}
Vector.angle = function(v) { return Math.rad2turn(Math.atan2(v.y, v.x)); } Vector.angle = function(v) { return Math.rad2turn(Math.atan2(v.y, v.x)); }
Vector.rotate = function(v,angle) { Vector.rotate = function(v,angle) {
var r = Vector.length(v); var r = Vector.length(v);
@ -1543,18 +1567,74 @@ Math.sortpointsccw = function(points)
var cm = points2cm(points); var cm = points2cm(points);
var cmpoints = points.map(function(x) { return x.sub(cm); }); var cmpoints = points.map(function(x) { return x.sub(cm); });
var ccw = cmpoints.sort(function(a,b) { var ccw = cmpoints.sort(function(a,b) {
aatan = Math.atan2(a.y, a.x); var aatan = Math.atan2(a.y, a.x);
batan = Math.atan2(b.y, b.x); var batan = Math.atan2(b.y, b.x);
return aatan - batan; return aatan - batan;
}); });
return ccw.map(function(x) { return x.add(cm); }); return ccw.map(function(x) { return x.add(cm); });
} }
var yaml = {};
yaml.tojson = function(yaml)
{
// Replace key value pairs that are strings with quotation marks around them
yaml = yaml.replace(/(\w+):/g, '"$1":');
yaml = yaml.replace(/: ([\w\.\/]+)/g, ': "$1"'); // TODO: make this more general
yaml = yaml.split("\n");
var cont = {};
var cur = 0;
for (var i = 0; i < yaml.length; i++) {
var line = yaml[i];
var indent = line.search(/\S/);
if (indent > cur) {
if (line[indent] == "-") {
cont[indent] = "array";
yaml[i] = line.sub(indent, '[');
} else {
cont[indent] = "obj";
yaml[i] = line.sub(indent-1, '{');
}
}
if (indent < cur) {
while (cur > indent) {
if (cont[cur] === "obj")
yaml[i-1] = yaml[i-1] + "}";
else if (cont[cur] === "array")
yaml[i-1] = yaml[i-1] + "]";
delete cont[cur];
cur--;
}
}
if (indent === cur) {
if (yaml[i][indent] === '-')
yaml[i] = yaml[i].sub(indent,',');
else
yaml[i-1] = yaml[i-1] + ',';
}
cur = indent;
}
yaml = "{" + yaml.join("\n") + "}";
yaml = yaml.replace(/\s/g, '');
yaml = yaml.replace(/,}/g, '}');
yaml = yaml.replace(/,]/g, ']');
yaml = yaml.replace(/,"[^"]+"\:,/g, ',');
return yaml;
}
Math.sign = function(n) { return n >= 0 ? 1 : -1; }
return { return {
convert, convert,
time, time,
json, json,
Vector, Vector,
bbox bbox,
yaml
}; };

View file

@ -13,6 +13,10 @@ this.view2world = function(pos) {
pos = pos.scale(this.zoom); pos = pos.scale(this.zoom);
pos = pos.add(this.pos); pos = pos.add(this.pos);
} }
if (window.mode === window.modetypes.expand) {
pos = pos.sub(window.size.scale(0.5));
pos = pos.scale([window.rendersize.x/window.size.x, window.rendersize.y/window.size.y]);
}
return pos; return pos;
}; };
this.world2view = function(pos) { this.world2view = function(pos) {
@ -25,6 +29,9 @@ this.world2view = function(pos) {
pos = pos.sub(this.pos); pos = pos.sub(this.pos);
pos = pos.scale(1/this.zoom); pos = pos.scale(1/this.zoom);
pos = pos.add(window.size.scale(0.5)); pos = pos.add(window.size.scale(0.5));
}
if (window.mode === window.modetypes.expand) {
} }
return pos; return pos;
}; };

View file

@ -95,10 +95,12 @@ Color.normalize = function(c) {
continue; continue;
} }
c[p][3] = 255;
for (var color of c[p]) { for (var color of c[p]) {
if (color > 1) { if (color > 1) {
fmt = "8b"; fmt = "8b";
break; break;
} }
} }

View file

@ -1,44 +1,4 @@
var component = { var component = {};
components: [],
toString() {
if ('gameobject' in this)
return this.name + " on " + this.gameobject;
else
return this.name;
},
name: "component",
component: true,
enabled: true,
enable() { this.enabled = true; },
disable() { this.enabled = false; },
isComponent(c) {
if (typeof c !== 'object') return false;
if (typeof c.toString !== 'function') return false;
if (typeof c.make !== 'function') return false;
return (typeof component[c.toString()] === 'object');
},
make(go) {
var nc = Object.create(this);
nc.gameobject = go;
Object.mixin(nc, this._enghook(go, nc));
assign_impl(nc,this.impl);
Object.hide(nc, 'gameobject', 'id');
nc.post();
nc.make = undefined;
return nc;
},
kill() { console.info("Kill not created for this component yet"); },
sync(){},
post(){},
gui(){},
gizmo(){},
finish_center() {},
extend(spec) { return Object.copy(this, spec); },
};
var make_point_obj = function(o, p) var make_point_obj = function(o, p)
{ {
@ -53,41 +13,8 @@ var make_point_obj = function(o, p)
} }
} }
var assign_impl = function(obj, impl) var sprite = {
{
var tmp = {};
for (var key of Object.keys(impl))
if (typeof obj[key] !== 'undefined' && typeof obj[key] !== 'function')
tmp[key] = obj[key];
Object.mixin(obj, impl);
for (var key in tmp)
obj[key] = tmp[key];
}
function json_from_whitelist(whitelist)
{
return function() {
var o = {};
for (var p of whitelist)
o[p] = this[p];
return o;
}
}
Object.mixin(os.sprite(true), {
loop: true, loop: true,
toJSON:json_from_whitelist([
"path",
"pos",
"scale",
"angle",
"color",
"emissive",
"parallax",
"frame"
]),
anim:{}, anim:{},
playing: 0, playing: 0,
play(str = 0) { play(str = 0) {
@ -110,6 +37,7 @@ Object.mixin(os.sprite(true), {
if (!self.gameobject) return; if (!self.gameobject) return;
//self.path = playing.path; //self.path = playing.path;
self.frame = playing.frames[f].rect; self.frame = playing.frames[f].rect;
self.rect = [self.frame.x, self.frame.y, self.frame.w, self.frame.h];
f = (f+1)%playing.frames.length; f = (f+1)%playing.frames.length;
if (f === 0) { if (f === 0) {
self.anim_done?.(); self.anim_done?.();
@ -117,7 +45,7 @@ Object.mixin(os.sprite(true), {
} }
stop = self.gameobject.delay(advance, playing.frames[f].time); stop = self.gameobject.delay(advance, playing.frames[f].time);
} }
this.tex(game.texture(playing.path));
advance(); advance();
}, },
stop() { stop() {
@ -133,7 +61,9 @@ Object.mixin(os.sprite(true), {
this._p = p; this._p = p;
this.del_anim?.(); this.del_anim?.();
this.texture = game.texture(p); this.texture = game.texture(p);
this.tex(this.texture);
this.diffuse = this.texture;
this.rect = [0,0,1,1];
var anim = SpriteAnim.make(p); var anim = SpriteAnim.make(p);
if (!anim) return; if (!anim) return;
@ -141,6 +71,7 @@ Object.mixin(os.sprite(true), {
this.play(); this.play();
this.pos = this.dimensions().scale(this.anchor); this.pos = this.dimensions().scale(this.anchor);
}, },
get path() { get path() {
return this._p; return this._p;
@ -150,8 +81,8 @@ Object.mixin(os.sprite(true), {
this.anim = undefined; this.anim = undefined;
this.gameobject = undefined; this.gameobject = undefined;
this.anim_done = undefined; this.anim_done = undefined;
delete allsprites[this.guid];
}, },
toString() { return "sprite"; },
move(d) { this.pos = this.pos.add(d); }, move(d) { this.pos = this.pos.add(d); },
grow(x) { grow(x) {
this.scale = this.scale.scale(x); this.scale = this.scale.scale(x);
@ -175,19 +106,8 @@ Object.mixin(os.sprite(true), {
}, },
width() { return this.dimensions().x; }, width() { return this.dimensions().x; },
height() { return this.dimensions().y; }, height() { return this.dimensions().y; },
}); };
globalThis.allsprites = {};
os.sprite(true).make = function(go)
{
var sp = os.sprite();
sp.go = go;
sp.gameobject = go;
return sp;
}
component.sprite = os.sprite(true);
var sprite = component.sprite;
sprite.doc = { sprite.doc = {
path: "Path to the texture.", path: "Path to the texture.",
@ -223,7 +143,18 @@ sprite.inputs.kp3 = function() { this.setanchor("ur"); }
sprite.inputs.kp2 = function() { this.setanchor("um"); } sprite.inputs.kp2 = function() { this.setanchor("um"); }
sprite.inputs.kp1 = function() { this.setanchor("ul"); } sprite.inputs.kp1 = function() { this.setanchor("ul"); }
Object.seal(sprite); component.sprite = function(obj) {
var sp = Object.create(sprite);
sp.gameobject = obj;
sp.guid = prosperon.guid();
allsprites[sp.guid] = sp;
return sp;
}
sprite.shade = [1,1,1,1];
Object.mixin(os.make_seg2d(), {
sync() { this.set_endpoints(this.points[0], this.points[1]); }
});
/* sprite anim returns a data structure for the given file path /* sprite anim returns a data structure for the given file path
frames: array of frames frames: array of frames
@ -251,13 +182,11 @@ SpriteAnim.make = function(path) {
return animcache[path]; return animcache[path];
}; };
SpriteAnim.gif = function(path) { SpriteAnim.gif = function(path) {
console.info(`making an anim from ${path}`);
var anim = {}; var anim = {};
anim.frames = []; anim.frames = [];
anim.path = path; anim.path = path;
var tex = game.texture(path); var tex = game.texture(path);
var frames = tex.frames; var frames = tex.frames;
console.info(`frames are ${frames}`);
if (frames === 1) return undefined; if (frames === 1) return undefined;
var yslice = 1/frames; var yslice = 1/frames;
for (var f = 0; f < frames; f++) { for (var f = 0; f < frames; f++) {
@ -276,7 +205,6 @@ SpriteAnim.gif = function(path) {
anim.frames[i].time = times[i]/1000; anim.frames[i].time = times[i]/1000;
anim.loop = true; anim.loop = true;
var dim = [tex.width,tex.height]; var dim = [tex.width,tex.height];
console.info(`dimensions are ${dim}`);
dim.y /= frames; dim.y /= frames;
anim.dim = dim; anim.dim = dim;
return {0:anim}; return {0:anim};
@ -329,7 +257,7 @@ SpriteAnim.aseprite = function(path) {
var anims = {}; var anims = {};
var frames = Array.isArray(data.frames) ? data.frames : Object.values(data.frames); var frames = Array.isArray(data.frames) ? data.frames : Object.values(data.frames);
var f = 0; var f = 0;
if (data.meta.frameTags.length === 0) { if (!data.meta.frameTags || data.meta.frameTags.length === 0) {
anims[0] = aseframeset2anim(frames, data.meta); anims[0] = aseframeset2anim(frames, data.meta);
return anims; return anims;
} }
@ -360,18 +288,7 @@ SpriteAnim.strip.doc = 'Given a path and number of frames, converts a horizontal
SpriteAnim.aseprite.doc = 'Given an aseprite json metadata, returns an object of animations defined in the aseprite file.'; SpriteAnim.aseprite.doc = 'Given an aseprite json metadata, returns an object of animations defined in the aseprite file.';
SpriteAnim.find.doc = 'Given a path, find the relevant animation for the file.'; SpriteAnim.find.doc = 'Given a path, find the relevant animation for the file.';
/* For all colliders, "shape" is a pointer to a phys2d_shape, "id" is a pointer to the shape data */ var collider2d = {};
var collider2d = Object.copy(component, {
impl: {
set sensor(x) { pshape.set_sensor(this.shape,x); },
get sensor() { return pshape.get_sensor(this.shape); },
set enabled(x) { pshape.set_enabled(this.shape,x); },
get enabled() { return pshape.get_enabled(this.shape); }
},
});
Object.hide(collider2d.impl, 'enabled');
collider2d.inputs = {}; collider2d.inputs = {};
collider2d.inputs['M-s'] = function() { this.sensor = !this.sensor; } collider2d.inputs['M-s'] = function() { this.sensor = !this.sensor; }
collider2d.inputs['M-s'].doc = "Toggle if this collider is a sensor."; collider2d.inputs['M-s'].doc = "Toggle if this collider is a sensor.";
@ -379,27 +296,11 @@ collider2d.inputs['M-s'].doc = "Toggle if this collider is a sensor.";
collider2d.inputs['M-t'] = function() { this.enabled = !this.enabled; } collider2d.inputs['M-t'] = function() { this.enabled = !this.enabled; }
collider2d.inputs['M-t'].doc = "Toggle if this collider is enabled."; collider2d.inputs['M-t'].doc = "Toggle if this collider is enabled.";
component.polygon2d = Object.copy(collider2d, { Object.mix(os.make_poly2d(), {
toJSON:json_from_whitelist([
'points',
'sensor'
]),
toString() { return "polygon2d"; },
flipx: false,
flipy: false,
boundingbox() { boundingbox() {
return bbox.frompoints(this.spoints()); return bbox.frompoints(this.spoints());
}, },
hides: ['id', 'shape', 'gameobject'],
_enghook: os.make_poly2d,
points:[],
setpoints(points) {
this.points = points;
this.sync();
},
/* EDITOR */ /* EDITOR */
spoints() { spoints() {
var spoints = this.points.slice(); var spoints = this.points.slice();
@ -407,7 +308,7 @@ component.polygon2d = Object.copy(collider2d, {
if (this.flipx) { if (this.flipx) {
spoints.forEach(function(x) { spoints.forEach(function(x) {
var newpoint = x.slice(); var newpoint = x.slice();
newpoint.x = -newpoint.x; newpoint.x = -newpoint.x;
spoints.push(newpoint); spoints.push(newpoint);
}); });
} }
@ -415,8 +316,8 @@ component.polygon2d = Object.copy(collider2d, {
if (this.flipy) { if (this.flipy) {
spoints.forEach(function(x) { spoints.forEach(function(x) {
var newpoint = x.slice(); var newpoint = x.slice();
newpoint.y = -newpoint.y; newpoint.y = -newpoint.y;
spoints.push(newpoint); spoints.push(newpoint);
}); });
} }
return spoints; return spoints;
@ -445,63 +346,71 @@ function pointscaler(x) {
this.points = this.points.map(p => p.mult(x)); this.points = this.points.map(p => p.mult(x));
} }
component.polygon2d.impl = Object.mix(collider2d.impl, { Object.mixin(os.make_poly2d(), {
sync() { poly2d.setverts(this.id,this.spoints()); }, sync() {
query() { return physics.shape_query(this.shape); }, this.setverts(this.points);
},
grow: pointscaler, grow: pointscaler,
}); });
var polygon2d = component.polygon2d; var polyinputs = Object.create(collider2d.inputs);
os.make_poly2d().inputs = polyinputs;
polygon2d.inputs = {}; polyinputs = {};
//polygon2d.inputs.post = function() { this.sync(); }; polyinputs.f10 = function() {
polygon2d.inputs.f10 = function() {
this.points = Math.sortpointsccw(this.points); this.points = Math.sortpointsccw(this.points);
}; };
polygon2d.inputs.f10.doc = "Sort all points to be CCW order."; polyinputs.f10.doc = "Sort all points to be CCW order.";
polygon2d.inputs['C-lm'] = function() { polyinputs['C-lm'] = function() {
this.points.push(this.gameobject.world2this(input.mouse.worldpos())); this.points.push(this.gameobject.world2this(input.mouse.worldpos()));
}; };
polygon2d.inputs['C-lm'].doc = "Add a point to location of mouse."; polyinputs['C-lm'].doc = "Add a point to location of mouse.";
polygon2d.inputs.lm = function(){}; polyinputs.lm = function(){};
polygon2d.inputs.lm.released = function(){}; polyinputs.lm.released = function(){};
polygon2d.inputs['C-M-lm'] = function() { polyinputs['C-M-lm'] = function() {
var idx = Math.grab_from_points(input.mouse.worldpos(), this.points.map(p => this.gameobject.this2world(p)), 25); var idx = Math.grab_from_points(input.mouse.worldpos(), this.points.map(p => this.gameobject.this2world(p)), 25);
if (idx === -1) return; if (idx === -1) return;
this.points.splice(idx, 1); this.points.splice(idx, 1);
}; };
polygon2d.inputs['C-M-lm'].doc = "Remove point under mouse."; polyinputs['C-M-lm'].doc = "Remove point under mouse.";
polygon2d.inputs['C-b'] = function() { polyinputs['C-b'] = function() {
this.points = this.spoints; this.points = this.spoints;
this.flipx = false; this.flipx = false;
this.flipy = false; this.flipy = false;
}; };
polygon2d.inputs['C-b'].doc = "Freeze mirroring in place."; polyinputs['C-b'].doc = "Freeze mirroring in place.";
component.edge2d = Object.copy(collider2d, { var edge2d = {
toJSON:json_from_whitelist([
'sensor',
'thickness',
'points',
'hollow',
'hollowt',
'angle',
]),
dimensions:2, dimensions:2,
thickness:0, thickness:1,
/* if type === -1, point to point */ /* if type === -1, point to point */
type: Spline.type.catmull, type: Spline.type.catmull,
C: 1, /* when in bezier, continuity required. 0, 1 or 2. */ C: 1, /* when in bezier, continuity required. 0, 1 or 2. */
looped: false, looped: false,
angle: 0.5, /* smaller for smoother bezier */ angle: 0.5, /* smaller for smoother bezier */
elasticity: 0,
friction: 0,
sync() {
var ppp = this.sample();
this.segs ??= [];
var count = ppp.length-1;
this.segs.length = count;
for (var i = 0; i < count; i++) {
this.segs[i] ??= os.make_seg2d(this.body);
this.segs[i].set_endpoints(ppp[i],ppp[i+1]);
this.segs[i].set_neighbors(ppp[i],ppp[i+1]);
this.segs[i].radius = this.thickness;
this.segs[i].elasticity = this.elasticity;
this.segs[i].friction = this.friction;
this.segs[i].collide = this.collide;
}
},
flipx: false, flipx: false,
flipy: false, flipy: false,
points:[],
toString() { return "edge2d"; },
hollow: false, hollow: false,
hollowt: 0, hollowt: 0,
@ -540,12 +449,10 @@ component.edge2d = Object.copy(collider2d, {
return arr1.concat(arr2.reverse()); return arr1.concat(arr2.reverse());
} }
return spoints; if (this.looped)
}, spoints = spoints.wrapped(1);
setpoints(points) { return spoints;
this.points = points;
// this.sync();
}, },
post() { post() {
@ -578,9 +485,6 @@ component.edge2d = Object.copy(collider2d, {
boundingbox() { return bbox.frompoints(this.points.map(x => x.scale(this.gameobject.scale))); }, boundingbox() { return bbox.frompoints(this.points.map(x => x.scale(this.gameobject.scale))); },
hides: ['gameobject', 'id', 'shape'],
_enghook: os.make_edge2d,
/* EDITOR */ /* EDITOR */
gizmo() { gizmo() {
if (this.type === Spline.type.catmull || this.type === -1) { if (this.type === Spline.type.catmull || this.type === -1) {
@ -656,12 +560,12 @@ component.edge2d = Object.copy(collider2d, {
var idx = 0; var idx = 0;
if (Spline.is_catmull(this.type) || this.type === -1) { if (Spline.is_catmull(this.type) || this.type === -1) {
if (this.points.length >= 2) if (this.points.length >= 2)
idx = physics.closest_point(pos, this.points, 400); idx = physics.closest_point(pos, this.points, 400);
if (idx === this.points.length) if (idx === this.points.length)
this.points.push(pos); this.points.push(pos);
else else
this.points.splice(idx, 0, pos); this.points.splice(idx, 0, pos);
} }
if (Spline.is_bezier(this.type)) { if (Spline.is_bezier(this.type)) {
@ -671,11 +575,11 @@ component.edge2d = Object.copy(collider2d, {
if (idx === 0) { if (idx === 0) {
this.points.unshift(pos.slice(), pos.add([-100,0]), Vector.reflect_point(this.points[1], this.points[0])); this.points.unshift(pos.slice(), pos.add([-100,0]), Vector.reflect_point(this.points[1], this.points[0]));
return; return;
} }
if (idx === Spline.bezier_node_count(this.points)) { if (idx === Spline.bezier_node_count(this.points)) {
this.points.push(Vector.reflect_point(this.points.at(-2), this.points.at(-1)), pos.add([-100,0]), pos.slice()); this.points.push(Vector.reflect_point(this.points.at(-2), this.points.at(-1)), pos.add([-100,0]), pos.slice());
return; return;
} }
idx = 2 + (idx-1)*3; idx = 2 + (idx-1)*3;
var adds = [pos.add([100,0]), pos.slice(), pos.add([-100,0])]; var adds = [pos.add([100,0]), pos.slice(), pos.add([-100,0])];
@ -688,71 +592,64 @@ component.edge2d = Object.copy(collider2d, {
this.points.forEach(x =>picks.push(make_point_obj(this,x))); this.points.forEach(x =>picks.push(make_point_obj(this,x)));
return picks; return picks;
}, },
}); };
component.edge2d.impl = Object.mix(collider2d.impl, { component.edge2d = function(obj) {
set thickness(x) { edge2d.set_thickness(this.id,x); }, if (!obj.body) obj.rigidify();
get thickness() { return edge2d.get_thickness(this.id); }, var edge = Object.create(edge2d);
grow: pointscaler, edge.body = obj.body;
sync() { return edge;
var sensor = this.sensor; }
var points = this.sample();
if (!points) return;
edge2d.setverts(this.id,points);
this.sensor = sensor;
},
});
var bucket = component.edge2d; edge2d.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises.";
bucket.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises."; edge2d.inputs = {};
bucket.inputs = {}; edge2d.inputs.h = function() { this.hollow = !this.hollow; };
bucket.inputs.h = function() { this.hollow = !this.hollow; }; edge2d.inputs.h.doc = "Toggle hollow.";
bucket.inputs.h.doc = "Toggle hollow.";
bucket.inputs['C-g'] = function() { if (this.hollowt > 0) this.hollowt--; }; edge2d.inputs['C-g'] = function() { if (this.hollowt > 0) this.hollowt--; };
bucket.inputs['C-g'].doc = "Thin the hollow thickness."; edge2d.inputs['C-g'].doc = "Thin the hollow thickness.";
bucket.inputs['C-g'].rep = true; edge2d.inputs['C-g'].rep = true;
bucket.inputs['C-f'] = function() { this.hollowt++; }; edge2d.inputs['C-f'] = function() { this.hollowt++; };
bucket.inputs['C-f'].doc = "Increase the hollow thickness."; edge2d.inputs['C-f'].doc = "Increase the hollow thickness.";
bucket.inputs['C-f'].rep = true; edge2d.inputs['C-f'].rep = true;
bucket.inputs['M-v'] = function() { if (this.thickness > 0) this.thickness--; }; edge2d.inputs['M-v'] = function() { if (this.thickness > 0) this.thickness--; };
bucket.inputs['M-v'].doc = "Decrease spline thickness."; edge2d.inputs['M-v'].doc = "Decrease spline thickness.";
bucket.inputs['M-v'].rep = true; edge2d.inputs['M-v'].rep = true;
bucket.inputs['C-y'] = function() { edge2d.inputs['C-y'] = function() {
this.points = this.spoints(); this.points = this.spoints();
this.flipx = false; this.flipx = false;
this.flipy = false; this.flipy = false;
this.hollow = false; this.hollow = false;
}; };
bucket.inputs['C-y'].doc = "Freeze mirroring,"; edge2d.inputs['C-y'].doc = "Freeze mirroring,";
bucket.inputs['M-b'] = function() { this.thickness++; }; edge2d.inputs['M-b'] = function() { this.thickness++; };
bucket.inputs['M-b'].doc = "Increase spline thickness."; edge2d.inputs['M-b'].doc = "Increase spline thickness.";
bucket.inputs['M-b'].rep = true; edge2d.inputs['M-b'].rep = true;
bucket.inputs.plus = function() { edge2d.inputs.plus = function() {
if (this.angle <= 1) { if (this.angle <= 1) {
this.angle = 1; this.angle = 1;
return; return;
} }
this.angle *= 0.9; this.angle *= 0.9;
}; };
bucket.inputs.plus.doc = "Increase the number of samples of this spline."; edge2d.inputs.plus.doc = "Increase the number of samples of this spline.";
bucket.inputs.plus.rep = true; edge2d.inputs.plus.rep = true;
bucket.inputs.minus = function() { this.angle *= 1.1; }; edge2d.inputs.minus = function() { this.angle *= 1.1; };
bucket.inputs.minus.doc = "Decrease the number of samples on this spline."; edge2d.inputs.minus.doc = "Decrease the number of samples on this spline.";
bucket.inputs.minus.rep = true; edge2d.inputs.minus.rep = true;
bucket.inputs['C-r'] = function() { this.points = this.points.reverse(); }; edge2d.inputs['C-r'] = function() { this.points = this.points.reverse(); };
bucket.inputs['C-r'].doc = "Reverse the order of the spline's points."; edge2d.inputs['C-r'].doc = "Reverse the order of the spline's points.";
bucket.inputs['C-l'] = function() { this.looped = !this.looped}; edge2d.inputs['C-l'] = function() { this.looped = !this.looped};
bucket.inputs['C-l'].doc = "Toggle spline being looped."; edge2d.inputs['C-l'].doc = "Toggle spline being looped.";
bucket.inputs['C-c'] = function() { edge2d.inputs['C-c'] = function() {
switch(this.type) { switch(this.type) {
case Spline.type.bezier: case Spline.type.bezier:
this.points = Spline.bezier2catmull(this.points); this.points = Spline.bezier2catmull(this.points);
@ -761,9 +658,9 @@ bucket.inputs['C-c'] = function() {
this.type = Spline.type.catmull; this.type = Spline.type.catmull;
}; };
bucket.inputs['C-c'].doc = "Set type of spline to catmull-rom."; edge2d.inputs['C-c'].doc = "Set type of spline to catmull-rom.";
bucket.inputs['C-b'] = function() { edge2d.inputs['C-b'] = function() {
switch(this.type) { switch(this.type) {
case Spline.type.catmull: case Spline.type.catmull:
this.points = Spline.catmull2bezier(Spline.catmull_caps(this.points)); this.points = Spline.catmull2bezier(Spline.catmull_caps(this.points));
@ -772,10 +669,10 @@ bucket.inputs['C-b'] = function() {
this.type = Spline.type.bezier; this.type = Spline.type.bezier;
}; };
bucket.inputs['C-o'] = function() { this.type = -1; }; edge2d.inputs['C-o'] = function() { this.type = -1; };
bucket.inputs['C-o'].doc = "Set spline to linear."; edge2d.inputs['C-o'].doc = "Set spline to linear.";
bucket.inputs['C-M-lm'] = function() { edge2d.inputs['C-M-lm'] = function() {
if (Spline.is_catmull(this.type)) { if (Spline.is_catmull(this.type)) {
var idx = Math.grab_from_points(input.mouse.worldpos(), this.points.map(p => this.gameobject.this2world(p)), 25); var idx = Math.grab_from_points(input.mouse.worldpos(), this.points.map(p => this.gameobject.this2world(p)), 25);
if (idx === -1) return; if (idx === -1) return;
@ -785,12 +682,12 @@ bucket.inputs['C-M-lm'] = function() {
this.points = this.points.newfirst(idx); this.points = this.points.newfirst(idx);
}; };
bucket.inputs['C-M-lm'].doc = "Select the given point as the '0' of this spline."; edge2d.inputs['C-M-lm'].doc = "Select the given point as the '0' of this spline.";
bucket.inputs['C-lm'] = function() { this.add_node(input.mouse.worldpos()); } edge2d.inputs['C-lm'] = function() { this.add_node(input.mouse.worldpos()); }
bucket.inputs['C-lm'].doc = "Add a point to the spline at the mouse position."; edge2d.inputs['C-lm'].doc = "Add a point to the spline at the mouse position.";
bucket.inputs['C-M-lm'] = function() { edge2d.inputs['C-M-lm'] = function() {
var idx = -1; var idx = -1;
if (Spline.is_catmull(this.type)) if (Spline.is_catmull(this.type))
idx = Math.grab_from_points(input.mouse.worldpos(), this.points.map(p => this.gameobject.this2world(p)), 25); idx = Math.grab_from_points(input.mouse.worldpos(), this.points.map(p => this.gameobject.this2world(p)), 25);
@ -802,12 +699,12 @@ bucket.inputs['C-M-lm'] = function() {
this.rm_node(idx); this.rm_node(idx);
}; };
bucket.inputs['C-M-lm'].doc = "Remove point from the spline."; edge2d.inputs['C-M-lm'].doc = "Remove point from the spline.";
bucket.inputs.lm = function(){}; edge2d.inputs.lm = function(){};
bucket.inputs.lm.released = function(){}; edge2d.inputs.lm.released = function(){};
bucket.inputs.lb = function() { edge2d.inputs.lb = function() {
var np = []; var np = [];
this.points.forEach(function(c) { this.points.forEach(function(c) {
@ -816,10 +713,10 @@ bucket.inputs.lb = function() {
this.points = np; this.points = np;
}; };
bucket.inputs.lb.doc = "Rotate the points CCW."; edge2d.inputs.lb.doc = "Rotate the points CCW.";
bucket.inputs.lb.rep = true; edge2d.inputs.lb.rep = true;
bucket.inputs.rb = function() { edge2d.inputs.rb = function() {
var np = []; var np = [];
this.points.forEach(function(c) { this.points.forEach(function(c) {
@ -828,37 +725,27 @@ bucket.inputs.rb = function() {
this.points = np; this.points = np;
}; };
bucket.inputs.rb.doc = "Rotate the points CW."; edge2d.inputs.rb.doc = "Rotate the points CW.";
bucket.inputs.rb.rep = true; edge2d.inputs.rb.rep = true;
component.circle2d = Object.copy(collider2d, { /* CIRCLE */
radius:10,
offset:[0,0],
toString() { return "circle2d"; },
boundingbox() { function shape_maker(maker) {
return bbox.fromcwh([0,0], [this.radius,this.radius]); return function(obj) {
}, if (!obj.body) obj.rigidify();
return maker(obj.body);
}
}
component.circle2d = shape_maker(os.make_circle2d);
component.poly2d = shape_maker(os.make_poly2d);
component.seg2d = shape_maker(os.make_seg2d);
hides: ['gameobject', 'id', 'shape', 'scale'], Object.mix(os.make_circle2d(), {
_enghook: os.make_circle2d, boundingbox() { return bbox.fromcwh(this.offset, [this.radius,this.radius]); },
});
component.circle2d.impl = Object.mix({
toJSON:json_from_whitelist([
"pos",
"radius",
]),
set radius(x) { circle2d.set_radius(this.id,x); circle2d.sync(this.id); },
get radius() { return circle2d.get_radius(this.id); },
set scale(x) { this.radius = x; }, set scale(x) { this.radius = x; },
get scale() { return this.radius; }, get scale() { return this.radius; },
set offset(x) { circle2d.set_offset(this.id,x); circle2d.sync(this.id); },
get offset() { circle2d.get_offset(this.id); },
get pos() { return this.offset; }, get pos() { return this.offset; },
set pos(x) { this.offset = x; }, set pos(x) { this.offset = x; },
@ -866,7 +753,6 @@ component.circle2d.impl = Object.mix({
if (typeof x === 'number') this.scale *= x; if (typeof x === 'number') this.scale *= x;
else if (typeof x === 'object') this.scale *= x[0]; else if (typeof x === 'object') this.scale *= x[0];
}, },
});
}, collider2d.impl);
return {component, SpriteAnim}; return {component, SpriteAnim};

View file

@ -1,3 +1,5 @@
var debug = {};
debug.fn_break = function(fn,obj = globalThis) { debug.fn_break = function(fn,obj = globalThis) {
if (typeof fn !== 'function') return; if (typeof fn !== 'function') return;

View file

@ -5,10 +5,7 @@
game.loadurs(); game.loadurs();
console.info(`window size: ${window.size}, render size: ${window.rendersize}`);
player[0].control(debug); player[0].control(debug);
render.clear_color([35,60,92,255].map(x => x/255));
var show_frame = true; var show_frame = true;
@ -344,7 +341,7 @@ var editor = {
root = root ? root + "." : root; root = root ? root + "." : root;
Object.entries(obj.objects).forEach(function(x) { Object.entries(obj.objects).forEach(function(x) {
var p = root + x[0]; var p = root + x[0];
render.text(p, x[1].screenpos(), 1, editor.color_depths[depth]); render.text(p, x[1].this2screen(), 1, editor.color_depths[depth]);
editor.draw_objects_names(x[1], p, depth+1); editor.draw_objects_names(x[1], p, depth+1);
}); });
}, },
@ -391,8 +388,8 @@ var editor = {
gui() { gui() {
/* Clean out killed objects */ /* Clean out killed objects */
if (show_frame) // if (show_frame)
render.line(shape.box(window.rendersize.x, window.rendersize.y).wrapped(1).map(p => game.camera.world2view(p)), Color.yellow); /// render.line(shape.box(window.rendersize.x, window.rendersize.y).wrapped(1).map(p => game.camera.world2view(p)), Color.yellow);
render.text([0,0], game.camera.world2view([0,0])); render.text([0,0], game.camera.world2view([0,0]));
@ -402,7 +399,7 @@ var editor = {
if (this.comp_info && this.sel_comp) if (this.comp_info && this.sel_comp)
render.text(input.print_pawn_kbm(this.sel_comp,false), [100,700],1); render.text(input.print_pawn_kbm(this.sel_comp,false), [100,700],1);
render.cross(editor.edit_level.screenpos(),3,Color.blue); render.cross(editor.edit_level.this2screen(),3,Color.blue);
var thiso = editor.get_this(); var thiso = editor.get_this();
var clvl = thiso; var clvl = thiso;
@ -447,16 +444,16 @@ var editor = {
render.text("$$$$$$", [0,ypos],1,editor.color_depths[depth]); render.text("$$$$$$", [0,ypos],1,editor.color_depths[depth]);
this.selectlist.forEach(function(x) { this.selectlist.forEach(function(x) {
render.text(x.urstr(), x.screenpos().add([0, render.font.linegap*2]), 1, Color.editor.ur); render.text(x.urstr(), x.this2screen().add([0, render.font.linegap*2]), 1, Color.editor.ur);
render.text(x.pos.map(function(x) { return Math.round(x); }), x.screenpos()); render.text(x.pos.map(function(x) { return Math.round(x); }), x.this2screen());
render.cross(x.screenpos(), 10, Color.blue); render.cross(x.this2screen(), 10, Color.blue);
}); });
Object.entries(thiso.objects).forEach(function(x) { Object.entries(thiso.objects).forEach(function(x) {
var p = x[1].namestr(); var p = x[1].namestr();
render.text(p, x[1].screenpos().add([0,render.font.linegap]),1,editor.color_depths[depth]); render.text(p, x[1].this2screen().add([0,render.font.linegap]),1,editor.color_depths[depth]);
render.point(x[1].screenpos(),5,Color.blue.alpha(0.3)); render.point(x[1].this2screen(),5,Color.blue.alpha(0.3));
render.point(x[1].screenpos(), 1, Color.red); render.point(x[1].this2screen(), 1, Color.red);
}); });
var mg = physics.pos_query(input.mouse.worldpos()); var mg = physics.pos_query(input.mouse.worldpos());
@ -474,7 +471,7 @@ var editor = {
for (var key in this.selectlist[0].components) { for (var key in this.selectlist[0].components) {
var selected = this.sel_comp === this.selectlist[0].components[key]; var selected = this.sel_comp === this.selectlist[0].components[key];
var str = (selected ? ">" : " ") + key + " [" + this.selectlist[0].components[key].toString() + "]"; var str = (selected ? ">" : " ") + key + " [" + this.selectlist[0].components[key].toString() + "]";
render.text(str, this.selectlist[0].screenpos().add([0,-render.font.linegap*(i++)])); render.text(str, this.selectlist[0].this2screen().add([0,-render.font.linegap*(i++)]));
} }
if (this.sel_comp) { if (this.sel_comp) {
@ -879,7 +876,7 @@ editor.inputs['C-s'] = function() {
} }
var savejs = saveobj.json_obj(); var savejs = saveobj.json_obj();
var tur = saveobj.get_ur(); var tur = saveobj.ur;
if (!tur) { if (!tur) {
console.warn(`Can't save object because it has no ur.`); console.warn(`Can't save object because it has no ur.`);
return; return;

View file

@ -1,107 +1,97 @@
"use math"; "use math";
Object.defineProperty(String.prototype, 'tolast', { Object.defineProperty(String.prototype, "tolast", {
value: function(val) { value: function (val) {
var idx = this.lastIndexOf(val); var idx = this.lastIndexOf(val);
if (idx === -1) return this.slice(); if (idx === -1) return this.slice();
return this.slice(0,idx); return this.slice(0, idx);
} },
}); });
Object.defineProperty(String.prototype, 'dir', { Object.defineProperty(String.prototype, "dir", {
value: function() { value: function () {
if (!this.includes('/')) return ""; if (!this.includes("/")) return "";
return this.tolast('/'); return this.tolast("/");
} },
}); });
Object.defineProperty(String.prototype, 'folder', { Object.defineProperty(String.prototype, "folder", {
value: function() { value: function () {
var dir = this.dir(); var dir = this.dir();
if (!dir) return ""; if (!dir) return "";
else return dir + "/"; else return dir + "/";
} },
}); });
globalThis.Resources = {}; globalThis.Resources = {};
Resources.replpath = function(str, path) Resources.replpath = function (str, path) {
{
if (!str) return str; if (!str) return str;
if (str[0] === "/") if (str[0] === "/") return str.rm(0);
return str.rm(0);
if (str[0] === "@") if (str[0] === "@") return os.prefpath() + "/" + str.rm(0);
return os.prefpath() + "/" + str.rm(0);
if (!path) return str; if (!path) return str;
var stem = path.dir(); var stem = path.dir();
while (stem) { while (stem) {
var tr = stem + "/" +str; var tr = stem + "/" + str;
if (io.exists(tr)) return tr; if (io.exists(tr)) return tr;
stem = stem.updir(); stem = stem.updir();
} }
return str; return str;
} };
Resources.replstrs = function(path) Resources.replstrs = function (path) {
{
if (!path) return; if (!path) return;
var script = io.slurp(path); var script = io.slurp(path);
var regexp = /"[^"\s]*?\.[^"\s]+?"/g; var regexp = /"[^"\s]*?\.[^"\s]+?"/g;
var stem = path.dir(); var stem = path.dir();
script = script.replace(regexp,function(str) { script = script.replace(regexp, function (str) {
var newstr = Resources.replpath(str.trimchr('"'), path); var newstr = Resources.replpath(str.trimchr('"'), path);
return `"${newstr}"`; return `"${newstr}"`;
}); });
return script; return script;
} };
globalThis.json = {}; globalThis.json = {};
json.encode = function(value, replacer, space = 1) json.encode = function (value, replacer, space = 1) {
{
return JSON.stringify(value, replacer, space); return JSON.stringify(value, replacer, space);
} };
json.decode = function(text, reviver) json.decode = function (text, reviver) {
{
if (!text) return undefined; if (!text) return undefined;
return JSON.parse(text,reviver); return JSON.parse(text, reviver);
} };
json.readout = function(obj) json.readout = function (obj) {
{
var j = {}; var j = {};
for (var k in obj) for (var k in obj)
if (typeof obj[k] === 'function') if (typeof obj[k] === "function") j[k] = "function " + obj[k].toString();
j[k] = 'function ' + obj[k].toString(); else j[k] = obj[k];
else
j[k] = obj[k];
return json.encode(j); return json.encode(j);
} };
json.doc = { json.doc = {
doc: "json implementation.", doc: "json implementation.",
encode: "Encode a value to json.", encode: "Encode a value to json.",
decode: "Decode a json string to a value.", decode: "Decode a json string to a value.",
readout: "Encode an object fully, including function definitions." readout: "Encode an object fully, including function definitions.",
}; };
Resources.scripts = ["jsoc", "jsc", "jso", "js"]; Resources.scripts = ["jsoc", "jsc", "jso", "js"];
Resources.images = ["png", "gif", "jpg", "jpeg"]; Resources.images = ["png", "gif", "jpg", "jpeg"];
Resources.sounds = ["wav", 'flac', 'mp3', "qoa"]; Resources.sounds = ["wav", "flac", "mp3", "qoa"];
Resources.is_image = function(path) { Resources.is_image = function (path) {
var ext = path.ext(); var ext = path.ext();
return Resources.images.any(x => x === ext); return Resources.images.any((x) => x === ext);
} };
function find_ext(file, ext) function find_ext(file, ext) {
{
if (io.exists(file)) return file; if (io.exists(file)) return file;
for (var e of ext) { for (var e of ext) {
var nf = `${file}.${e}`; var nf = `${file}.${e}`;
@ -110,46 +100,48 @@ function find_ext(file, ext)
return; return;
} }
Resources.find_image = function(file) { return find_ext(file,Resources.images); } Resources.find_image = function (file) {
Resources.find_sound = function(file) { return find_ext(file,Resources.sounds); } return find_ext(file, Resources.images);
Resources.find_script = function(file) { return find_ext(file,Resources.scripts); } };
Resources.find_sound = function (file) {
return find_ext(file, Resources.sounds);
};
Resources.find_script = function (file) {
return find_ext(file, Resources.scripts);
};
profile.best_t = function(t) { profile.best_t = function (t) {
var qq = 'ns'; var qq = "ns";
if (t > 1000) { if (t > 1000) {
t /= 1000; t /= 1000;
qq = 'us'; qq = "us";
if (t > 1000) { if (t > 1000) {
t /= 1000; t /= 1000;
qq = 'ms'; qq = "ms";
} }
} }
return `${t.toPrecision(4)} ${qq}`; return `${t.toPrecision(4)} ${qq}`;
} };
profile.report = function(start, msg = "[undefined report]") profile.report = function (start, msg = "[undefined report]") {
{ console.info(`${msg} in ${profile.best_t(profile.now() - start)}`);
console.info(`${msg} in ${profile.best_t(profile.now()-start)}`); };
}
profile.addreport = function(cache, line, start) profile.addreport = function (cache, line, start) {
{
cache[line] ??= []; cache[line] ??= [];
cache[line].push(profile.now()-start); cache[line].push(profile.now() - start);
} };
profile.printreport = function(cache, name) profile.printreport = function (cache, name) {
{
var report = name + "\n"; var report = name + "\n";
for (var i in cache) { for (var i in cache)
report += `${i} ${profile.best_t(profcache[i].reduce((a,b) => a+b)/profcache[i].length)}\n`; report += `${i} ${profile.best_t(cache[i].reduce((a, b) => a + b) / cache[i].length)}\n`;
}
return report; return report;
} };
console.transcript = ""; console.transcript = "";
console.say = function(msg) { console.say = function (msg) {
msg += "\n"; msg += "\n";
console.print(msg); console.print(msg);
console.transcript += msg; console.transcript += msg;
@ -158,16 +150,14 @@ console.log = console.say;
globalThis.say = console.say; globalThis.say = console.say;
globalThis.print = console.print; globalThis.print = console.print;
console.pprint = function(msg,lvl = 0) { console.pprint = function (msg, lvl = 0) {
if (typeof msg === "object") msg = JSON.stringify(msg, null, 2);
if (typeof msg === 'object')
msg = JSON.stringify(msg, null, 2);
var file = "nofile"; var file = "nofile";
var line = 0; var line = 0;
console.rec(0,msg,file,line); console.rec(0, msg, file, line);
var caller = (new Error()).stack.split('\n')[2]; var caller = new Error().stack.split("\n")[2];
if (caller) { if (caller) {
var md = caller.match(/\((.*)\:/); var md = caller.match(/\((.*)\:/);
var m = md ? md[1] : "SCRIPT"; var m = md ? md[1] : "SCRIPT";
@ -180,19 +170,33 @@ console.pprint = function(msg,lvl = 0) {
console.rec(lvl, msg, file, line); console.rec(lvl, msg, file, line);
}; };
console.spam = function(msg) { console.pprint (msg,0); }; console.spam = function (msg) {
console.debug = function(msg) { console.pprint(msg,1); }; console.pprint(msg, 0);
console.info = function(msg) { console.pprint(msg, 2); }; };
console.warn = function(msg) { console.pprint(msg, 3); }; console.debug = function (msg) {
console.error = function(msg) { console.pprint(msg + "\n" + console.stackstr(2), 4);}; console.pprint(msg, 1);
console.panic = function(msg) { console.pprint(msg + "\n" + console.stackstr(2), 5); }; };
console.stackstr = function(skip=0) { console.info = function (msg) {
console.pprint(msg, 2);
};
console.warn = function (msg) {
console.pprint(msg, 3);
};
console.error = function (msg) {
console.pprint(msg + "\n" + console.stackstr(2), 4);
};
console.panic = function (msg) {
console.pprint(msg + "\n" + console.stackstr(2), 5);
};
console.stackstr = function (skip = 0) {
var err = new Error(); var err = new Error();
var stack = err.stack.split('\n'); var stack = err.stack.split("\n");
return stack.slice(skip,stack.length).join('\n'); return stack.slice(skip, stack.length).join("\n");
}; };
console.stack = function(skip = 0) { console.log(console.stackstr(skip+1)); }; console.stack = function (skip = 0) {
console.log(console.stackstr(skip + 1));
};
console.stdout_lvl = 1; console.stdout_lvl = 1;
console.trace = console.stack; console.trace = console.stack;
@ -207,15 +211,14 @@ console.doc = {
say: "Write raw text to console, plus a newline.", say: "Write raw text to console, plus a newline.",
stack: "Output a stacktrace to console.", stack: "Output a stacktrace to console.",
console: "Output directly to in game console.", console: "Output directly to in game console.",
clear: "Clear console." clear: "Clear console.",
}; };
globalThis.global = globalThis; globalThis.global = globalThis;
var profcache = {}; var profcache = {};
function use(file, env = {}, script) function use(file, env = {}, script) {
{
file = Resources.find_script(file); file = Resources.find_script(file);
var st = profile.now(); var st = profile.now();
@ -228,7 +231,7 @@ function use(file, env = {}, script)
} }
script ??= Resources.replstrs(file); script ??= Resources.replstrs(file);
script = `(function() { var self = this; ${script}; })`; script = `(function() { var self = this; ${script}; })`;
var fn = os.eval(file,script); var fn = os.eval(file, script);
use.cache[file] = fn; use.cache[file] = fn;
var ret = fn.call(env); var ret = fn.call(env);
profile.addreport(profcache, file, st); profile.addreport(profcache, file, st);
@ -237,158 +240,181 @@ function use(file, env = {}, script)
use.cache = {}; use.cache = {};
global.check_registers = function(obj) global.check_registers = function (obj) {
{ if (typeof obj.update === "function")
if (typeof obj.update === 'function') obj.timers.push(Register.update.register(obj.update.bind(obj)));
obj.timers.push(Register.update.register(obj.update.bind(obj)));
if (typeof obj.physupdate === 'function') if (typeof obj.physupdate === "function")
obj.timers.push(Register.physupdate.register(obj.physupdate.bind(obj))); obj.timers.push(Register.physupdate.register(obj.physupdate.bind(obj)));
if (typeof obj.draw === 'function') if (typeof obj.draw === "function")
obj.timers.push(Register.draw.register(obj.draw.bind(obj), obj)); obj.timers.push(Register.draw.register(obj.draw.bind(obj), obj));
if (typeof obj.debug === 'function') if (typeof obj.debug === "function")
obj.timers.push(Register.debug.register(obj.debug.bind(obj))); obj.timers.push(Register.debug.register(obj.debug.bind(obj)));
if (typeof obj.gui === 'function') if (typeof obj.gui === "function")
obj.timers.push(Register.gui.register(obj.gui.bind(obj))); obj.timers.push(Register.gui.register(obj.gui.bind(obj)));
if (typeof obj.screengui === 'function') if (typeof obj.screengui === "function")
obj.timers.push(Register.screengui.register(obj.screengui.bind(obj))); obj.timers.push(Register.screengui.register(obj.screengui.bind(obj)));
for (var k in obj) { for (var k in obj) {
if (!k.startswith("on_")) continue; if (!k.startswith("on_")) continue;
var signal = k.fromfirst("on_"); var signal = k.fromfirst("on_");
Event.observe(signal, obj, obj[k]); Event.observe(signal, obj, obj[k]);
}; }
} };
Object.assign(global, use("scripts/base")); Object.assign(global, use("scripts/base"));
global.obscure('global'); global.obscure("global");
global.mixin("scripts/render"); global.mixin("scripts/render");
global.mixin("scripts/debug"); global.mixin("scripts/debug");
var frame_t = profile.secs(profile.now()); var frame_t = profile.secs(profile.now());
var phys_step = 1/240;
var sim = {}; var sim = {};
sim.mode = "play"; sim.mode = "play";
sim.play = function() { this.mode = "play"; os.reindex_static(); }; sim.play = function () {
sim.playing = function() { return this.mode === 'play'; }; this.mode = "play";
sim.pause = function() { this.mode = "pause"; }; os.reindex_static();
sim.paused = function() { return this.mode === 'pause'; }; };
sim.step = function() { this.mode = 'step'; }; sim.playing = function () {
sim.stepping = function() { return this.mode === 'step'; } return this.mode === "play";
};
sim.pause = function () {
this.mode = "pause";
};
sim.paused = function () {
return this.mode === "pause";
};
sim.step = function () {
this.mode = "step";
};
sim.stepping = function () {
return this.mode === "step";
};
var physlag = 0; var physlag = 0;
var gggstart = game.engine_start; var gggstart = game.engine_start;
game.engine_start = function(s) { game.engine_start = function (s) {
game.startengine = 1; game.startengine = 1;
gggstart(function() { gggstart(
global.mixin("scripts/sound.js"); function () {
world_start(); global.mixin("scripts/sound.js");
go_init(); world_start();
window.set_icon(os.make_texture("icons/moon.gif")) window.set_icon(os.make_texture("icons/moon.gif"));
Object.readonly(window.__proto__, 'vsync'); Object.readonly(window.__proto__, "vsync");
Object.readonly(window.__proto__, 'enable_dragndrop'); Object.readonly(window.__proto__, "enable_dragndrop");
Object.readonly(window.__proto__, 'enable_clipboard'); Object.readonly(window.__proto__, "enable_clipboard");
Object.readonly(window.__proto__, 'high_dpi'); Object.readonly(window.__proto__, "high_dpi");
Object.readonly(window.__proto__, 'sample_count'); Object.readonly(window.__proto__, "sample_count");
s(); s();
}, process);
} shape.quad = {
pos: os.make_buffer([0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0], 0),
verts: 4,
uv: os.make_buffer([0, 1, 1, 1, 0, 0, 1, 0], 2),
index: os.make_buffer([0, 1, 2, 2, 1, 3], 1),
count: 6,
};
shape.triangle = {
pos: os.make_buffer([0, 0, 0, 0.5, 1, 0, 1, 0, 0], 0),
uv: os.make_buffer([0, 0, 0.5, 1, 1, 0], 2),
verts: 3,
count: 3,
index: os.make_buffer([0, 2, 1], 1),
};
render.init();
},
process,
window.size.x,
window.size.y,
);
};
game.startengine = 0; game.startengine = 0;
var frames = [];
function process() function process() {
{ var startframe = profile.now();
var dt = profile.secs(profile.now()) - frame_t; var dt = profile.secs(profile.now()) - frame_t;
frame_t = profile.secs(profile.now()); frame_t = profile.secs(profile.now());
prosperon.appupdate(dt); prosperon.appupdate(dt);
prosperon.emitters_step(dt);
input.procdown(); input.procdown();
if (sim.mode === "play" || sim.mode === "step") { if (sim.mode === "play" || sim.mode === "step") {
prosperon.update(dt*game.timescale); prosperon.update(dt * game.timescale);
if (sim.mode === "step") if (sim.mode === "step") sim.pause();
sim.pause();
physlag += dt; physlag += dt;
while (physlag > phys_step) { while (physlag > physics.delta) {
physlag -= phys_step; physlag -= physics.delta;
prosperon.phys2d_step(phys_step*game.timescale); var st = profile.now();
prosperon.physupdate(phys_step*game.timescale); prosperon.phys2d_step(physics.delta * game.timescale);
prosperon.physupdate(physics.delta * game.timescale);
profile.addreport(profcache, "physics step", st);
} }
} }
var st = profile.now();
if (!game.camera) prosperon.window_render(window.size);
prosperon.window_render(world, 1); prosperon.draw();
else prosperon.debug();
prosperon.window_render(game.camera, game.camera.zoom);
render.set_camera();
render.sprites(); // blits all sprites
render.emitters(); // blits emitters
prosperon.draw(); // draw calls
debug.draw(); // calls needed debugs
render.flush();
prosperon.hook3d?.();
render.hud_res(window.rendersize);
prosperon.gui(); prosperon.gui();
render.flush();
render.hud_res(window.size);
prosperon.screengui(); prosperon.screengui();
render.flush(); prosperon.hookend?.();
profile.addreport(profcache, "render frame", st);
render.end_pass(); frames.push(profile.secs(profile.now() - startframe));
if (frames.length > 20) frames.shift();
} }
globalThis.fps = function () {
var sum = 0;
for (var i = 0; i < frames.length; i++) sum += frames[i];
return frames.length / sum;
};
game.timescale = 1; game.timescale = 1;
var eachobj = function(obj,fn) var eachobj = function (obj, fn) {
{
var val = fn(obj); var val = fn(obj);
if (val) return val; if (val) return val;
for (var o in obj.objects) { for (var o in obj.objects) {
if (obj.objects[o] === obj) if (obj.objects[o] === obj)
console.error(`Object ${obj.toString()} is referenced by itself.`); console.error(`Object ${obj.toString()} is referenced by itself.`);
val = eachobj(obj.objects[o],fn); val = eachobj(obj.objects[o], fn);
if (val) return val; if (val) return val;
} }
} };
game.all_objects = function(fn, startobj = world) { return eachobj(startobj,fn); }; game.all_objects = function (fn, startobj = world) {
game.find_object = function(fn, startobj = world) { return eachobj(startobj, fn);
};
} game.find_object = function (fn, startobj = world) {};
game.tags = {}; game.tags = {};
game.tag_add = function(tag, obj) { game.tag_add = function (tag, obj) {
game.tags[tag] ??= {}; game.tags[tag] ??= {};
game.tags[tag][obj.guid] = obj; game.tags[tag][obj.guid] = obj;
} };
game.tag_rm = function(tag, obj) { game.tag_rm = function (tag, obj) {
delete game.tags[tag][obj.guid]; delete game.tags[tag][obj.guid];
} };
game.tag_clear_guid = function(guid) game.tag_clear_guid = function (guid) {
{ for (var tag in game.tags) delete game.tags[tag][guid];
for (var tag in game.tags) };
delete game.tags[tag][guid];
}
game.objects_with_tag = function(tag) game.objects_with_tag = function (tag) {
{
if (!game.tags[tag]) return []; if (!game.tags[tag]) return [];
return Object.values(game.tags[tag]); return Object.values(game.tags[tag]);
} };
game.doc = {}; game.doc = {};
game.doc.object = "Returns the entity belonging to a given id."; game.doc.object = "Returns the entity belonging to a given id.";
@ -396,86 +422,85 @@ game.doc.pause = "Pause game simulation.";
game.doc.play = "Resume or start game simulation."; game.doc.play = "Resume or start game simulation.";
game.doc.camera = "Current camera."; game.doc.camera = "Current camera.";
game.texture = function(path) game.texture = function (path, force = false) {
{ if (force && game.texture.cache[path]) return game.texture.cache[path];
if (game.texture.cache[path]) return game.texture.cache[path];
if (!io.exists(path)) { if (!io.exists(path)) {
console.warn(`Missing texture: ${path}`); console.warn(`Missing texture: ${path}`);
game.texture.cache[path] = game.texture("icons/no_tex.gif"); game.texture.cache[path] = game.texture("icons/no_tex.gif");
} else } else game.texture.cache[path] ??= os.make_texture(path);
game.texture.cache[path] ??= os.make_texture(path);
return game.texture.cache[path]; return game.texture.cache[path];
} };
game.texture.cache = {}; game.texture.cache = {};
prosperon.semver = {}; prosperon.semver = {};
prosperon.semver.valid = function(v, range) prosperon.semver.valid = function (v, range) {
{ v = v.split(".");
v = v.split('.'); range = range.split(".");
range = range.split('.');
if (v.length !== 3) return undefined; if (v.length !== 3) return undefined;
if (range.length !== 3) return undefined; if (range.length !== 3) return undefined;
if (range[0][0] === '^') { if (range[0][0] === "^") {
range[0] = range[0].slice(1); range[0] = range[0].slice(1);
if (parseInt(v[0]) >= parseInt(range[0])) return true; if (parseInt(v[0]) >= parseInt(range[0])) return true;
return false; return false;
} }
if (range[0] === '~') { if (range[0] === "~") {
range[0] = range[0].slice(1); range[0] = range[0].slice(1);
for (var i = 0; i < 2; i++) for (var i = 0; i < 2; i++)
if (parseInt(v[i]) < parseInt(range[i])) return false; if (parseInt(v[i]) < parseInt(range[i])) return false;
return true; return true;
} }
return prosperon.semver.cmp(v.join('.'), range.join('.')) === 0; return prosperon.semver.cmp(v.join("."), range.join(".")) === 0;
} };
prosperon.semver.cmp = function(v1, v2) prosperon.semver.cmp = function (v1, v2) {
{ var ver1 = v1.split(".");
var ver1 = v1.split('.'); var ver2 = v2.split(".");
var ver2 = v2.split('.');
for (var i = 0; i < 3; i++) { for (var i = 0; i < 3; i++) {
var n1 = parseInt(ver1[i]); var n1 = parseInt(ver1[i]);
var n2 = parseInt(ver2[i]); var n2 = parseInt(ver2[i]);
if (n1 > n2) if (n1 > n2) return 1;
return 1; else if (n1 < n2) return -1;
else if (n1 < n2)
return -1;
} }
return 0; return 0;
} };
prosperon.semver.doc = "Functions for semantic versioning numbers. Semantic versioning is given as a triple digit number, as MAJOR.MINOR.PATCH."; prosperon.semver.doc =
prosperon.semver.cmp.doc = "Compare two semantic version numbers, given like X.X.X."; "Functions for semantic versioning numbers. Semantic versioning is given as a triple digit number, as MAJOR.MINOR.PATCH.";
prosperon.semver.cmp.doc =
"Compare two semantic version numbers, given like X.X.X.";
prosperon.semver.valid.doc = `Test if semantic version v is valid, given a range. prosperon.semver.valid.doc = `Test if semantic version v is valid, given a range.
Range is given by a semantic versioning number, prefixed with nothing, a ~, or a ^. Range is given by a semantic versioning number, prefixed with nothing, a ~, or a ^.
~ means that MAJOR and MINOR must match exactly, but any PATCH greater or equal is valid. ~ means that MAJOR and MINOR must match exactly, but any PATCH greater or equal is valid.
^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`; ^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`;
prosperon.iconified = function(icon) {}; prosperon.iconified = function (icon) {};
prosperon.focus = function(focus) {}; prosperon.focus = function (focus) {};
prosperon.resize = function(dimensions) {}; prosperon.resize = function (dimensions) {
prosperon.suspended = function(sus) {}; window.size.x = dimensions.x;
prosperon.mouseenter = function(){}; window.size.y = dimensions.y;
prosperon.mouseleave = function(){}; };
prosperon.touchpress = function(touches){}; prosperon.suspended = function (sus) {};
prosperon.touchrelease = function(touches){}; prosperon.mouseenter = function () {};
prosperon.touchmove = function(touches){}; prosperon.mouseleave = function () {};
prosperon.clipboardpaste = function(str){}; prosperon.touchpress = function (touches) {};
prosperon.quit = function(){ prosperon.touchrelease = function (touches) {};
prosperon.touchmove = function (touches) {};
prosperon.clipboardpaste = function (str) {};
prosperon.quit = function () {
say(profile.printreport(profcache, "USE REPORT")); say(profile.printreport(profcache, "USE REPORT"));
say(profile.printreport(entityreport, "ENTITY REPORT")); say(profile.printreport(entityreport, "ENTITY REPORT"));
console.info("QUITTING"); console.info("QUITTING");
for (var i in debug.log.time) for (var i in debug.log.time)
say(debug.log.time[i].map(x=>profile.ms(x))); say(debug.log.time[i].map((x) => profile.ms(x)));
}; };
global.mixin("scripts/input"); global.mixin("scripts/input");
@ -526,18 +551,21 @@ var Register = {
var n = {}; var n = {};
var fns = []; var fns = [];
n.register = function(fn, obj) { n.register = function (fn, obj) {
if (typeof fn !== 'function') return; if (typeof fn !== "function") return;
if (typeof obj === 'object') if (typeof obj === "object") fn = fn.bind(obj);
fn = fn.bind(obj);
fns.push(fn); fns.push(fn);
return function() { return function () {
fns.remove(fn); fns.remove(fn);
}; };
} };
prosperon[name] = function(...args) { fns.forEach(x => x(...args)); } prosperon[name] = function (...args) {
fns.forEach((x) => x(...args));
};
prosperon[name].fns = fns; prosperon[name].fns = fns;
n.clear = function() { fns = []; } n.clear = function () {
fns = [];
};
Register[name] = n; Register[name] = n;
Register.registries.push(n); Register.registries.push(n);
@ -563,33 +591,32 @@ var Event = {
}, },
unobserve(name, obj) { unobserve(name, obj) {
this.events[name] = this.events[name].filter(x => x[0] !== obj); this.events[name] = this.events[name].filter((x) => x[0] !== obj);
}, },
rm_obj(obj) { rm_obj(obj) {
Object.keys(this.events).forEach(name => Event.unobserve(name,obj)); Object.keys(this.events).forEach((name) => Event.unobserve(name, obj));
}, },
notify(name, ...args) { notify(name, ...args) {
if (!this.events[name]) return; if (!this.events[name]) return;
this.events[name].forEach(function(x) { this.events[name].forEach(function (x) {
x[1].call(x[0], ...args); x[1].call(x[0], ...args);
}); });
}, },
}; };
// window.rendersize is the resolution the game renders at
// window.size is the physical size of the window on the desktop // window.size is the physical size of the window on the desktop
window.modetypes = { // set to one of the following
stretch: 0, // stretch render to fill window // stretch render to fill window
keep: 1, // keep render exact dimensions, with no stretching // keep render exact dimensions, with no stretching
width: 2, // keep render at width // width keep render at width
height: 3, // keep render at height // height keep render at height
expand: 4, // expand width or height // expand width or height
full: 5 // expand out beyond window // full expand out beyond window
};
window.size = [640, 480]; window.size = [640, 480];
window.mode = "keep";
window.set_icon.doc = "Set the icon of the window using the PNG image at path."; window.set_icon.doc = "Set the icon of the window using the PNG image at path.";
@ -597,19 +624,26 @@ global.mixin("scripts/spline");
global.mixin("scripts/components"); global.mixin("scripts/components");
window.doc = {}; window.doc = {};
window.doc.dimensions = "Window width and height packaged in an array [width,height]"; window.doc.dimensions =
"Window width and height packaged in an array [width,height]";
window.doc.title = "Name in the title bar of the window."; window.doc.title = "Name in the title bar of the window.";
window.doc.boundingbox = "Boundingbox of the window, with top and right being its height and width."; window.doc.boundingbox =
"Boundingbox of the window, with top and right being its height and width.";
global.mixin("scripts/actor"); global.mixin("scripts/actor");
global.mixin("scripts/entity"); global.mixin("scripts/entity");
function world_start() { function world_start() {
globalThis.world = os.make_gameobject(); globalThis.world = Object.create(entity);
world.transform = os.make_transform();
world.objects = {}; world.objects = {};
world.toString = function() { return "world"; }; world.toString = function () {
return "world";
};
world.ur = "world"; world.ur = "world";
world.kill = function() { this.clear(); }; world.kill = function () {
this.clear();
};
world.phys = 2; world.phys = 2;
world.zoom = 1; world.zoom = 1;
world._ed = { selectable: false }; world._ed = { selectable: false };
@ -624,9 +658,4 @@ global.mixin("scripts/widget");
globalThis.mum = app.spawn("scripts/mum"); globalThis.mum = app.spawn("scripts/mum");
window.title = `Prosperon v${prosperon.version}`; window.title = `Prosperon v${prosperon.version}`;
window.size = [500,500]; window.size = [500, 500];
window.boundingbox = function() {
var pos = game.camera.pos;
var wh = window.rendersize.scale(game.camera.zoom);
return bbox.fromcwh(pos,wh);
}

View file

@ -12,7 +12,18 @@ function obj_unique_name(name, obj) {
return n; return n;
} }
var gameobject = { function unique_name(list, name = "new_object") {
var str = name.replaceAll('.', '_');
var n = 1;
var t = str;
while (list.indexOf(t) !== -1) {
t = str + n;
n++;
}
return t;
};
var entity = {
get_comp_by_name(name) { get_comp_by_name(name) {
var comps = []; var comps = [];
for (var c of Object.values(this.components)) for (var c of Object.values(this.components))
@ -22,78 +33,10 @@ var gameobject = {
return undefined; return undefined;
}, },
check_dirty() { rigidify() {
this._ed.urdiff = this.json_obj(); this.body = os.make_body(this.transform);
this._ed.dirty = !Object.empty(this._ed.urdiff);
return; // TODO: IMPLEMENT
var lur = this.master.ur;
if (!lur) return;
var lur = lur.objects[this.toString()];
var d = ediff(this._ed.urdiff, lur);
if (!d || Object.empty(d))
this._ed.inst = true;
else
this._ed.inst = false;
}, },
namestr() {
var s = this.toString();
if (this._ed?.dirty)
if (this._ed.inst) s += "#";
else s += "*";
return s;
},
urstr() {
var str = this.ur.name;
if (this._ed.dirty) str = "*" + str;
return str;
},
full_path() {
return this.path_from(world);
},
/* pin this object to the to object */
pin(to) {
var p = joint.pin(this,to);
},
slide(to, a = [0,0], b = [0,0], min = 0, max = 50) {
var p = joint.slide(this, to, a, b, min, max);
p.max_force = 500;
p.break();
},
pivot(to, piv = this.pos) {
var p = joint.pivot(this, to, piv);
},
/* groove is on to, from local points a and b, anchored to this at local anchor */
groove(to, a, b, anchor = [0,0]) {
var p = joint.groove(to, this, a, b, anchor);
},
damped_spring(to, length = Vector.length(this.pos,to.pos), stiffness = 1, damping = 1) {
var dc = 2 * Math.sqrt(stiffness * this.mass);
var p = joint.damped_spring(this, to, [0, 0], [0, 0], stiffness, damping * dc);
},
damped_rotary_spring(to, angle = 0, stiffness = 1, damping = 1) {
/* calculate actual damping value from the damping ratio */
/* damping = 1 is critical */
var dc = 2 * Math.sqrt(stiffness * this.get_moi()); /* critical damping number */
/* zeta = actual/critical */
var p = joint.damped_rotary(this, to, angle, stiffness, damping * dc);
},
rotary_limit(to, min, max) {
var p = joint.rotary(this, to, Math.turn2rad(min), Math.turn2rad(max));
},
ratchet(to, ratch) {
var phase = this.angle - to.angle;
var p = joint.ratchet(this, to, phase, Math.turn2rad(ratch));
},
gear(to, ratio = 1, phase = 0) {
var phase = this.angle - to.angle;
var p = joint.gear(this, to, phase, ratio);
},
motor(to, rate) {
var p = joint.motor(this, to, rate);
},
path_from(o) { path_from(o) {
var p = this.toString(); var p = this.toString();
@ -106,6 +49,10 @@ var gameobject = {
return p; return p;
}, },
drawlayer: 0,
full_path() { return this.path_from(world); },
clear() { clear() {
for (var k in this.objects) { for (var k in this.objects) {
this.objects[k].kill(); this.objects[k].kill();
@ -113,6 +60,11 @@ var gameobject = {
this.objects = {}; this.objects = {};
}, },
sync() {
this.components.forEach(function(x) { x.sync?.(); });
this.objects.forEach(function(x) { x.sync(); });
},
delay(fn, seconds) { delay(fn, seconds) {
var timers = this.timers; var timers = this.timers;
var stop = function() { var stop = function() {
@ -145,110 +97,60 @@ var gameobject = {
cry(file) { return audio.cry(file); }, cry(file) { return audio.cry(file); },
set pos(x) { this.set_pos(x); }, get pos() { return this.transform.pos; },
get pos() { return this.rpos; }, set pos(x) { this.transform.pos = x; },
set angle(x) { this.set_angle(x); }, get angle() { return this.transform.angle; },
get angle() { return this.rangle; }, set angle(x) { this.transform.angle = x; },
set scale(x) { this.set_scale(x); }, get scale() { return this.transform.scale; },
get scale() { return this.rscale; }, set scale(x) { this.transform.scale = x; },
set_pos(x, relative = world) { move(vec) { this.pos = this.pos.add(vec); },
var newpos = relative.this2world(x); rotate(x) { this.transform.rotate(x, [0,0,-1]); },
var move = newpos.sub(this.pos);
this.rpos = newpos;
this.objects.forEach(x => x.move(move));
},
set_angle(x, relative = world) {
var newangle = relative.angle + x;
var diff = newangle - this.angle;
this.rangle = newangle;
this.objects.forEach(obj => {
obj.rotate(diff);
obj.set_pos(Vector.rotate(obj.get_pos(obj.master), diff), obj.master);
});
},
set_scale(x, relative = world) {
if (typeof x === 'number') x = [x,x,x];
var newscale = relative.scale.map((s,i) => x[i]*s);
var pct = this.scale.map((s,i) => newscale[i]/s);
this.rscale = newscale;
this.objects.forEach(obj => {
obj.grow(pct);
obj.set_pos(obj.get_pos(obj.master).map((x,i) => x*pct[i]), obj.master);
});
},
get_pos(relative = world) {
if (relative === world) return this.pos;
return relative.world2this(this.pos);
//return this.pos.sub(relative.pos);
},
get_angle(relative = world) {
if (relative === world) return this.angle;
return this.angle - relative.angle;
},
get_scale(relative = world) {
if (relative === world) return this.scale;
var masterscale = relative.scale;
return this.scale.map((x,i) => x/masterscale[i]);
},
/* Moving, rotating, scaling functions, world relative */
move(vec) { this.set_pos(this.pos.add(vec)); },
rotate(x) { this.set_angle(this.angle + x); },
grow(vec) { grow(vec) {
if (typeof vec === 'number') vec = [vec,vec,vec]; if (typeof vec === 'number') vec = [vec,vec];
this.set_scale(this.scale.map((x, i) => x * vec[i])); this.scale = this.scale.map((x,i) => x*vec[i]);
}, },
screenpos() { return game.camera.world2view(this.pos); }, /* Reparent 'this' to be 'parent's child */
reparent(parent) {
assert(parent, `Tried to reparent ${this.toString()} to nothing.`);
console.spam(`parenting ${this.toString()} to ${parent.toString()}`);
if (this.master === parent) {
console.warn("not reparenting ...");
console.warn(`${this.master} is the same as ${parent}`);
return;
}
get_ur() { return this.ur; }, var name = unique_name(Object.keys(parent), this.name);
this.name = name;
this.master?.remove_obj(this);
this.master = parent;
parent.objects[this.guid] = this;
parent[name] = this;
Object.hide(parent, name);
},
remove_obj(obj) {
delete this.objects[obj.guid];
delete this[obj.name];
Object.unhide(this, obj.name);
},
/* spawn an entity
text can be:
the file path of a script
an ur object
nothing
*/
spawn(text, config, callback) { spawn(text, config, callback) {
var st = profile.now(); var st = profile.now();
var ent = os.make_gameobject(); var ent = Object.create(entity);
ent.transform = os.make_transform();
ent.guid = prosperon.guid(); ent.guid = prosperon.guid();
ent.components = {}; ent.components = {};
ent.objects = {}; ent.objects = {};
ent.timers = []; ent.timers = [];
Object.mixin(ent, { if (!text)
set category(n) { ent.ur = emptyur;
if (n === 0) { else if (typeof text === 'object' && text) {// assume it's an ur
this.categories = n;
return;
}
var cat = (1 << (n-1));
this.categories = cat;
},
get category() {
if (this.categories === 0) return 0;
var pos = 0;
var num = this.categories;
while (num > 0) {
if (num & 1) {
break;
}
pos++;
num >>>= 1;
}
return pos+1;
}
});
if (typeof text === 'object' && text) {// assume it's an ur
ent.ur = text; ent.ur = text;
text = ent.ur.text; text = ent.ur.text;
config = [ent.ur.data, config].filter(x => x).flat(); config = [ent.ur.data, config].filter(x => x).flat();
@ -281,9 +183,8 @@ var gameobject = {
for (var [prop, p] of Object.entries(ent)) { for (var [prop, p] of Object.entries(ent)) {
if (!p) continue; if (!p) continue;
if (typeof p !== 'object') continue; if (typeof p !== 'object') continue;
if (component.isComponent(p)) continue;
if (!p.comp) continue; if (!p.comp) continue;
ent[prop] = component[p.comp].make(ent); ent[prop] = component[p.comp](ent);
Object.merge(ent[prop], p); Object.merge(ent[prop], p);
ent.components[prop] = ent[prop]; ent.components[prop] = ent[prop];
}; };
@ -294,7 +195,7 @@ var gameobject = {
if (sim.playing()) if (sim.playing())
if (typeof ent.start === 'function') ent.start(); if (typeof ent.start === 'function') ent.start();
Object.hide(ent, 'ur', 'components', 'objects', 'timers', 'guid', 'master', 'categories'); Object.hide(ent, 'ur', 'components', 'objects', 'timers', 'guid', 'master');
ent._ed = { ent._ed = {
selectable: true, selectable: true,
@ -330,63 +231,21 @@ var gameobject = {
ent.ur.fresh.objects[i] = ent.objects[i].instance_obj(); ent.ur.fresh.objects[i] = ent.objects[i].instance_obj();
profile.addreport(entityreport, ent.ur.name, st); profile.addreport(entityreport, ent.ur.name, st);
return ent; return ent;
}, },
/* Reparent 'this' to be 'parent's child */ disable() { this.components.forEach(function(x) { x.disable(); }); },
reparent(parent) { enable() { this.components.forEach(function(x) { x.enable(); }); },
assert(parent, `Tried to reparent ${this.toString()} to nothing.`);
console.spam(`parenting ${this.toString()} to ${parent.toString()}`);
if (this.master === parent) {
console.warn("not reparenting ...");
console.warn(`${this.master} is the same as ${parent}`);
return;
}
this.master?.remove_obj(this);
this.master = parent;
function unique_name(list, name = "new_object") {
var str = name.replaceAll('.', '_');
var n = 1;
var t = str;
while (list.indexOf(t) !== -1) {
t = str + n;
n++;
}
return t;
};
var name = unique_name(Object.keys(parent.objects), this.ur.name);
parent.objects[name] = this;
parent[name] = this;
Object.hide(parent, name);
this.toString = function() { return name; };
},
remove_obj(obj) {
delete this.objects[obj.toString()];
delete this[obj.toString()];
Object.unhide(this, obj.toString());
},
components: {},
objects: {},
master: undefined,
this2screen(pos) { return game.camera.world2view(this.this2world(pos)); }, this2screen(pos) { return game.camera.world2view(this.this2world(pos)); },
screen2this(pos) { return this.world2this(game.camera.view2world(pos)); }, screen2this(pos) { return this.world2this(game.camera.view2world(pos)); },
in_air() { return this.in_air(); }, /* Make a unique object the same as its prototype */
revert() { Object.merge(this, this.ur.fresh); },
hide() { this.components.forEach(x => x.hide?.());
this.objects.forEach(x => x.hide?.()); },
show() { this.components.forEach(function(x) { x.show?.(); });
this.objects.forEach(function(x) { x.show?.(); }); },
name: "new_object",
toString() { return this.name; },
width() { width() {
var bb = this.boundingbox(); var bb = this.boundingbox();
return bb.r - bb.l; return bb.r - bb.l;
@ -397,19 +256,11 @@ var gameobject = {
return bb.t - bb.b; return bb.t - bb.b;
}, },
/* Make a unique object the same as its prototype */
revert() { Object.merge(this, this.ur.fresh); },
toString() { return "new_object"; },
flipx() { return this.scale.x < 0; }, flipx() { return this.scale.x < 0; },
flipy() { return this.scale.y < 0; }, flipy() { return this.scale.y < 0; },
mirror(plane) { this.scale = Vector.reflect(this.scale, plane); }, mirror(plane) { this.scale = Vector.reflect(this.scale, plane); },
disable() { this.components.forEach(function(x) { x.disable(); }); },
enable() { this.components.forEach(function(x) { x.enable(); }); },
/* Bounding box of the object in world dimensions */ /* Bounding box of the object in world dimensions */
boundingbox() { boundingbox() {
var boxes = []; var boxes = [];
@ -476,22 +327,7 @@ var gameobject = {
return t; return t;
}, },
/* Velocity and angular velocity of the object */ dup(diff) {
phys_obj() {
var phys = {};
phys.velocity = this.velocity;
phys.angularvelocity = this.angularvelocity;
return phys;
},
phys_mat() {
return {
friction: this.friction,
elasticity: this.elasticity
}
},
dup(diff) {
var n = this.master.spawn(this.ur); var n = this.master.spawn(this.ur);
Object.totalmerge(n, this.transform()); Object.totalmerge(n, this.transform());
return n; return n;
@ -515,9 +351,8 @@ var gameobject = {
for (var key in this.components) { for (var key in this.components) {
this.components[key].kill?.(); this.components[key].kill?.();
this.components[key].gameobject = undefined; this.components[key].gameobject = undefined;
this[key].enabled = false; this.components[key].enabled = false;
delete this.components[key]; delete this.components[key];
delete this[key];
} }
delete this.components; delete this.components;
@ -532,10 +367,6 @@ var gameobject = {
} }
}, },
up() { return [0, 1].rotate(this.angle); },
down() { return [0, -1].rotate(this.angle); },
right() { return [1, 0].rotate(this.angle); },
left() { return [-1, 0].rotate(this.angle); },
make_objs(objs) { make_objs(objs) {
for (var prop in objs) { for (var prop in objs) {
@ -565,29 +396,168 @@ var gameobject = {
return this.objects[newname]; return this.objects[newname];
}, },
add_component(comp, data, name = comp.toString()) { add_component(comp, data) {
if (typeof comp.make !== 'function') return; var name = prosperon.guid();
name = obj_unique_name(name, this); this.components[name] = comp(this);
this[name] = comp.make(this); if (data) {
this[name].comp = comp.toString(); Object.assign(this.components[name], data);
this.components[name] = this[name]; this.components[name].sync?.();
if (data) }
Object.assign(this[name], data); return this.components[name];
return this[name];
}, },
} };
function go_init() { var gameobject = {
var gop = os.make_gameobject().__proto__; check_dirty() {
Object.mixin(gop, gameobject); this._ed.urdiff = this.json_obj();
gop.sync = function() { this._ed.dirty = !Object.empty(this._ed.urdiff);
this.selfsync(); return; // TODO: IMPLEMENT
this.components.forEach(function(x) { x.sync?.(); }); var lur = this.master.ur;
this.objects.forEach(function(x) { x.sync?.(); }); if (!lur) return;
var lur = lur.objects[this.toString()];
var d = ediff(this._ed.urdiff, lur);
if (!d || Object.empty(d))
this._ed.inst = true;
else
this._ed.inst = false;
},
namestr() {
var s = this.toString();
if (this._ed?.dirty)
if (this._ed.inst) s += "#";
else s += "*";
return s;
},
urstr() {
var str = this.ur.name;
if (this._ed.dirty) str = "*" + str;
return str;
},
/* pin this object to the to object */
pin(to) {
var p = joint.pin(this,to);
},
slide(to, a = [0,0], b = [0,0], min = 0, max = 50) {
var p = joint.slide(this, to, a, b, min, max);
p.max_force = 500;
p.break();
},
pivot(to, piv = this.pos) {
var p = joint.pivot(this, to, piv);
},
/* groove is on to, from local points a and b, anchored to this at local anchor */
groove(to, a, b, anchor = [0,0]) {
var p = joint.groove(to, this, a, b, anchor);
},
damped_spring(to, length = Vector.length(this.pos,to.pos), stiffness = 1, damping = 1) {
var dc = 2 * Math.sqrt(stiffness * this.mass);
var p = joint.damped_spring(this, to, [0, 0], [0, 0], stiffness, damping * dc);
},
damped_rotary_spring(to, angle = 0, stiffness = 1, damping = 1) {
/* calculate actual damping value from the damping ratio */
/* damping = 1 is critical */
var dc = 2 * Math.sqrt(stiffness * this.get_moi()); /* critical damping number */
/* zeta = actual/critical */
var p = joint.damped_rotary(this, to, angle, stiffness, damping * dc);
},
rotary_limit(to, min, max) {
var p = joint.rotary(this, to, Math.turn2rad(min), Math.turn2rad(max));
},
ratchet(to, ratch) {
var phase = this.angle - to.angle;
var p = joint.ratchet(this, to, phase, Math.turn2rad(ratch));
},
gear(to, ratio = 1, phase = 0) {
var phase = this.angle - to.angle;
var p = joint.gear(this, to, phase, ratio);
},
motor(to, rate) {
var p = joint.motor(this, to, rate);
},
set_pos(x, relative = world) {
var newpos = relative.this2world(x);
var move = newpos.sub(this.pos);
this.rpos = newpos;
this.objects.forEach(x => x.move(move));
},
set_angle(x, relative = world) {
var newangle = relative.angle + x;
var diff = newangle - this.angle;
this.rangle = newangle;
this.objects.forEach(obj => {
obj.rotate(diff);
obj.set_pos(Vector.rotate(obj.get_pos(obj.master), diff), obj.master);
});
},
set_scale(x, relative = world) {
if (typeof x === 'number') x = [x,x,x];
var newscale = relative.scale.map((s,i) => x[i]*s);
var pct = this.scale.map((s,i) => newscale[i]/s);
this.rscale = newscale;
this.objects.forEach(obj => {
obj.grow(pct);
obj.set_pos(obj.get_pos(obj.master).map((x,i) => x*pct[i]), obj.master);
});
},
get_pos(relative = world) {
if (relative === world) return this.pos;
return relative.world2this(this.pos);
//return this.pos.sub(relative.pos);
},
get_angle(relative = world) {
if (relative === world) return this.angle;
return this.angle - relative.angle;
},
get_scale(relative = world) {
if (relative === world) return this.scale;
var masterscale = relative.scale;
return this.scale.map((x,i) => x/masterscale[i]);
},
in_air() { return this.in_air(); },
/* Velocity and angular velocity of the object */
phys_obj() {
var phys = {};
phys.velocity = this.velocity;
phys.angularvelocity = this.angularvelocity;
return phys;
},
set category(n) {
if (n === 0) {
this.categories = n;
return;
}
var cat = (1 << (n-1));
this.categories = cat;
},
get category() {
if (this.categories === 0) return 0;
var pos = 0;
var num = this.categories;
while (num > 0) {
if (num & 1) {
break;
}
pos++;
num >>>= 1;
}
return pos+1;
} }
} }
gameobject.spawn.doc = `Spawn an entity of type 'ur' on this entity. Returns the spawned entity.`; entity.spawn.doc = `Spawn an entity of type 'ur' on this entity. Returns the spawned entity.`;
gameobject.doc = { gameobject.doc = {
doc: "All objects in the game created through spawning have these attributes.", doc: "All objects in the game created through spawning have these attributes.",
@ -695,8 +665,18 @@ function apply_ur(u, ent) {
} }
} }
var emptyur = {
name: "empty"
}
var getur = function(text, data) var getur = function(text, data)
{ {
if (!text && !data) {
console.info('empty ur');
return {
name: "empty"
};
}
var urstr = text + "+" + data; var urstr = text + "+" + data;
if (!ur[urstr]) { if (!ur[urstr]) {
ur[urstr] = { ur[urstr] = {
@ -747,6 +727,7 @@ game.loadurs = function() {
} }
} }
return;
for (var file of io.glob("**.json").filter(f => !ur[f.name()])) { for (var file of io.glob("**.json").filter(f => !ur[f.name()])) {
if (file[0] === '.' || file[0] === '_') continue; if (file[0] === '.' || file[0] === '_') continue;
var newur = ur_from_file(file); var newur = ur_from_file(file);
@ -784,4 +765,4 @@ game.ur.save = function(str)
} }
} }
return { go_init } return { entity }

View file

@ -1,4 +1,9 @@
var shape = {}; var shape = {};
shape.box = {};
shape.box.points = function(ll, ur)
{
return [ll, ll.add([ur.x-ll.x,0]), ur, ll.add([0,ur.y-ll.y])];
}
shape.sphere = {}; shape.sphere = {};
shape.circle = {}; shape.circle = {};
shape.sphere.volume = function(r) { return Math.pi*r*r*r*4/3; }; shape.sphere.volume = function(r) { return Math.pi*r*r*r*4/3; };

View file

@ -164,6 +164,16 @@ Mum.button = Mum.text._int.extend({
action() { console.warn("Button has no action."); }, action() { console.warn("Button has no action."); },
}); });
var mumcam = {};
mumcam.transform = os.make_transform();
mumcam.ortho = true;
mumcam.near = 0;
mumcam.far = 1000;
mumcam.transform.pos = [100,100,-100];
mumcam.app = true;
var textssbo = render.text_ssbo();
Mum.window = Mum.extend({ Mum.window = Mum.extend({
start() { start() {
this.wh = [this.width, this.height]; this.wh = [this.width, this.height];
@ -173,7 +183,6 @@ Mum.window = Mum.extend({
var p = cursor.sub(this.wh.scale(this.anchor)).add(this.padding); var p = cursor.sub(this.wh.scale(this.anchor)).add(this.padding);
render.window(p,this.wh, this.color); render.window(p,this.wh, this.color);
this.bb = bbox.blwh(p, this.wh); this.bb = bbox.blwh(p, this.wh);
gui.flush();
this.max_width = this.width; this.max_width = this.width;
if (this.selectable) gui.controls.check_bb(this); if (this.selectable) gui.controls.check_bb(this);
var pos = [this.bb.l, this.bb.t].add(this.padding); var pos = [this.bb.l, this.bb.t].add(this.padding);
@ -181,7 +190,12 @@ Mum.window = Mum.extend({
if (item.hide) return; if (item.hide) return;
item.draw(pos.slice(),this); item.draw(pos.slice(),this);
}, this); }, this);
gui.flush(); render.set_camera(mumcam);
render.setpipeline(render.textshader.pipe);
render.shader_apply_material(render.textshader);
var bind = render.sg_bind(render.textshader, shape.quad, {text:render.font.texture}, textssbo);
bind.inst = render.flushtext();
render.spdraw(bind);
gui.scissor_win(); gui.scissor_win();
}, },
}); });

View file

@ -119,6 +119,7 @@ prosperon.textinput = function(c){
}; };
prosperon.mousemove = function(pos, dx){ prosperon.mousemove = function(pos, dx){
mousepos = pos; mousepos = pos;
mousepos.y = window.size.y - mousepos.y;
player[0].mouse_input("move", pos, dx); player[0].mouse_input("move", pos, dx);
}; };
prosperon.mousescroll = function(dx){ prosperon.mousescroll = function(dx){
@ -299,15 +300,20 @@ var Player = {
raw_input(cmd, state, ...args) { raw_input(cmd, state, ...args) {
for (var pawn of this.pawns.reversed()) { for (var pawn of this.pawns.reversed()) {
if (typeof pawn.inputs?.any === 'function') { if (!pawn.inputs) {
console.error(`pawn no longer has inputs object.`);
continue;
}
if (typeof pawn.inputs.any === 'function') {
pawn.inputs.any(cmd); pawn.inputs.any(cmd);
if (!pawn.inputs.fallthru) if (!pawn.inputs.fallthru)
return; return;
} }
if (!pawn.inputs?.[cmd]) { if (!pawn.inputs[cmd]) {
if (pawn.inputs?.block) return; if (pawn.inputs.block) return;
continue; continue;
} }
@ -315,21 +321,22 @@ var Player = {
switch (state) { switch (state) {
case 'pressed': case 'pressed':
fn = pawn.inputs[cmd]; fn = pawn.inputs[cmd];
break; break;
case 'rep': case 'rep':
fn = pawn.inputs[cmd].rep ? pawn.inputs[cmd] : null; fn = pawn.inputs[cmd].rep ? pawn.inputs[cmd] : null;
break; break;
case 'released': case 'released':
fn = pawn.inputs[cmd].released; fn = pawn.inputs[cmd].released;
break; break;
case 'down': case 'down':
fn = pawn.inputs[cmd].down; fn = pawn.inputs[cmd].down;
} }
if (typeof fn === 'function') { if (typeof fn === 'function') {
fn.call(pawn, ... args); fn.call(pawn, ... args);
pawn.inputs.post?.call(pawn); if (!pawn.inputs) continue; // TODO: OK? Checking if the call uncontrolled the pawn
pawn.inputs.post?.call(pawn);
} }
switch (state) { switch (state) {
@ -369,6 +376,10 @@ var Player = {
pawns: [], pawns: [],
control(pawn) { control(pawn) {
if (!pawn.inputs) {
console.warn(`attempted to control a pawn without any input object.`);
return;
}
this.pawns.push_unique(pawn); this.pawns.push_unique(pawn);
}, },

View file

@ -53,6 +53,8 @@ physics.gravity.strength = 500;
physics.damp = physics.make_damp(); physics.damp = physics.make_damp();
physics.damp.mask = ~1; physics.damp.mask = ~1;
physics.delta = 1/240;
return { return {
physics physics
} }

View file

@ -4,6 +4,324 @@ render.doc = {
wireframe: "Show only wireframes of models." wireframe: "Show only wireframes of models."
}; };
var shaderlang = {
macos: "metal_macos",
windows: "hlsl5",
linux: "glsl430",
web: "wgsl",
ios: "metal_ios",
}
var attr_map = {
a_pos: 0,
a_uv: 1,
a_norm: 2,
a_bone: 3,
a_weight: 4,
a_color: 5,
a_tan: 6,
a_angle: 7,
a_wh: 8,
a_st: 9,
a_ppos: 10,
a_scale: 11
}
var blend_map = {
mix: true,
none: false
}
var primitive_map = {
point: 1,
line: 2,
linestrip: 3,
triangle: 4,
trianglestrip: 5
}
var cull_map = {
none: 1,
front: 2,
back: 3
}
var depth_map = {
off: false,
on: true
}
var face_map = {
cw: 2,
ccw: 1
}
render.poly_prim = function(verts)
{
var index = [];
if (verts.length < 1) return undefined;
for (var i = 0; i < verts.length; i++)
verts[i][2] = 0;
for (var i = 2; i < verts.length; i++) {
index.push(0);
index.push(i-1);
index.push(i);
}
return {
pos: os.make_buffer(verts.flat()),
verts: verts.length,
index: os.make_buffer(index, 1),
count: index.length
};
}
function shader_directive(shader, name, map)
{
var reg = new RegExp(`#${name}.*`, 'g');
var mat = shader.match(reg);
if (!mat) return undefined;
reg = new RegExp(`#${name}\s*`, 'g');
var ff = mat.map(d=>d.replace(reg,''))[0].trim();
if (map) return map[ff];
return ff;
}
function global_uni(uni, stage)
{
switch(uni.name) {
case "time":
render.setuniv(stage, uni.slot, profile.secs(profile.now()));
return true;
case "projection":
render.setuniproj(stage, uni.slot);
return true;
case "view":
render.setuniview(stage, uni.slot);
return true;
case "vp":
render.setunivp(stage, uni.slot);
return true;
}
return false;
}
render.make_shader = function(shader)
{
var file = shader;
shader = io.slurp(shader);
if (!shader) {
console.info(`not found! slurping shaders/${file}`);
shader = io.slurp(`shaders/${file}`);
}
var writejson = `.prosperon/${file.name()}.shader.json`;
var st = profile.now();
breakme: if (io.exists(writejson)) {
var data = json.decode(io.slurp(writejson));
var filemod = io.mod(writejson);
if (!data.files) break breakme;
for (var i of data.files)
if (io.mod(i) > filemod)
break breakme;
profile.report(st, `CACHE make shader from ${file}`);
var shaderobj = json.decode(io.slurp(writejson));
var obj = shaderobj[os.sys()];
obj.pipe = render.pipeline(obj);
return obj;
}
var out = `.prosperon/${file.name()}.shader`;
var files = [file];
var incs = shader.match(/#include <.*>/g);
if (incs)
for (var inc of incs) {
var filez = inc.match(/#include <(.*)>/)[1];
var macro = io.slurp(filez);
if (!macro) {
filez = `shaders/${filez}`;
macro = io.slurp(filez);
}
shader = shader.replace(inc, macro);
files.push(filez);
}
var blend = shader_directive(shader, 'blend', blend_map);
var primitive = shader_directive(shader, 'primitive', primitive_map);
var cull = shader_directive(shader, 'cull', cull_map);
var depth = shader_directive(shader, 'depth', depth_map);
var face = shader_directive(shader, 'face', face_map);
var indexed = shader_directive(shader, 'indexed');
if (typeof indexed == 'undefined') indexed = true;
if (indexed === 'false') indexed = false;
shader = shader.replace(/uniform\s+(\w+)\s+(\w+);/g, "uniform _$2 { $1 $2; };");
shader = shader.replace(/(texture2D|sampler) /g, "uniform $1 ");
// shader = shader.replace(/uniform texture2D ?(.*);/g, "uniform _$1_size { vec2 $1_size; };\nuniform texture2D $1;");
io.slurpwrite(out, shader);
var compiled = {};
// shader file is created, now cross compile to all targets
for (var platform in shaderlang) {
var backend = shaderlang[platform];
var ret = os.system(`sokol-shdc -f bare_yaml --slang=${backend} -i ${out} -o ${out}`);
if (ret) {
console.error(`error compiling shader ${file}. No compilation found for ${platform}:${backend}, and no cross compiler available.`);
return;
}
/* Take YAML and create the shader object */
var yamlfile = `${out}_reflection.yaml`;
var jjson = yaml.tojson(io.slurp(yamlfile));
var obj = json.decode(jjson);
io.rm(yamlfile);
obj = obj.shaders[0].programs[0];
function add_code(stage) {
stage.code = io.slurp(stage.path);
io.rm(stage.path);
delete stage.path;
}
add_code(obj.vs);
if (!obj.fs)
if (obj.vs.fs) {
obj.fs = obj.vs.fs;
delete obj.vs.fs;
}
add_code(obj.fs);
obj.blend = blend;
obj.cull = cull;
obj.primitive = primitive;
obj.depth = depth;
obj.face = face;
obj.indexed = indexed;
if (obj.vs.inputs)
for (var i of obj.vs.inputs) {
if (!(i.name in attr_map))
i.mat = -1;
else
i.mat = attr_map[i.name];
}
function make_unimap(stage) {
if (!stage.uniform_blocks) return {};
var unimap = {};
for (var uni of stage.uniform_blocks) {
var uniname = uni.struct_name[0] == "_" ? uni.struct_name.slice(1) : uni.struct_name;
unimap[uniname] = {
name: uniname,
slot: Number(uni.slot),
size: Number(uni.size)
};
}
return unimap;
}
obj.vs.unimap = make_unimap(obj.vs);
obj.fs.unimap = make_unimap(obj.fs);
obj.name = file;
compiled[platform] = obj;
}
compiled.files = files;
io.slurpwrite(writejson, json.encode(compiled));
profile.report(st, `make shader from ${file}`);
var obj = compiled[os.sys()];
obj.pipe = render.pipeline(obj);
return obj;
}
var shader_unisize = {
4: render.setuniv,
8: render.setuniv2,
12: render.setuniv3,
16: render.setuniv4
};
render.shader_apply_material = function(shader, material = {})
{
for (var p in shader.vs.unimap) {
if (global_uni(shader.vs.unimap[p], 0)) continue;
if (!(p in material)) continue;
var s = shader.vs.unimap[p];
shader_unisize[s.size](0, s.slot, material[p]);
}
for (var p in shader.fs.unimap) {
if (global_uni(shader.fs.unimap[p], 1)) continue;
if (!(p in material)) continue;
var s = shader.fs.unimap[p];
shader_unisize[s.size](1, s.slot, material[p]);
}
if (!material.diffuse) return;
if ("diffuse_size" in shader.fs.unimap)
render.setuniv2(1, shader.fs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]);
if ("diffuse_size" in shader.vs.unimap)
render.setuniv2(0, shader.vs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]);
}
render.sg_bind = function(shader, mesh = {}, material = {}, ssbo)
{
var bind = {};
bind.attrib = [];
if (shader.vs.inputs)
for (var a of shader.vs.inputs) {
if (!(a.name in mesh)) {
if (!(a.name.slice(2) in mesh)) {
console.error(`cannot draw shader ${shader.name}; there is no attrib ${a.name} in the given mesh.`);
return undefined;
} else
bind.attrib.push(mesh[a.name.slice(2)]);
} else
bind.attrib.push(mesh[a.name]);
}
bind.images = [];
if (shader.fs.images)
for (var img of shader.fs.images) {
if (material[img.name])
bind.images.push(material[img.name]);
else
bind.images.push(game.texture("icons/no_tex.gif"));
}
if (shader.indexed) {
bind.index = mesh.index;
bind.count = mesh.count;
} else
bind.count = mesh.verts;
bind.ssbo = [];
if (shader.vs.storage_buffers)
for (var b of shader.vs.storage_buffers)
bind.ssbo.push(ssbo);
return bind;
}
render.device = { render.device = {
pc: [1920,1080], pc: [1920,1080],
macbook_m2: [2560,1664, 13.6], macbook_m2: [2560,1664, 13.6],
@ -40,29 +358,103 @@ render.device = {
render.device.doc = `Device resolutions given as [x,y,inches diagonal].`; render.device.doc = `Device resolutions given as [x,y,inches diagonal].`;
var textshader;
var circleshader;
var polyshader;
render.init = function() {
textshader = render.make_shader("shaders/text_base.cg");
render.spriteshader = render.make_shader("shaders/sprite.cg");
render.postshader = render.make_shader("shaders/simplepost.cg");
circleshader = render.make_shader("shaders/circle.cg");
polyshader = render.make_shader("shaders/poly.cg");
render.textshader = textshader;
os.make_circle2d().draw = function() {
render.circle(this.body().transform().pos, this.radius, [1,1,0,1]);
}
var disabled = [148/255,148/255, 148/255, 1];
var sleep = [1, 140/255, 228/255, 1];
var dynamic = [1, 70/255, 46/255, 1];
var kinematic = [1, 194/255, 64/255, 1];
var static_color = [73/255, 209/255, 80/255, 1];
os.make_poly2d().draw = function() {
var body = this.body();
var color = body.sleeping() ? [0,0.3,0,0.4] : [0,1,0,0.4];
var t = body.transform();
render.poly(this.points, color, body.transform());
color.a = 1;
render.line(this.points.wrapped(1), color, 1, body.transform());
}
os.make_seg2d().draw = function() {
render.line([this.a(), this.b()], [1,0,1,1], Math.max(this.radius/2, 1), this.body().transform());
}
joint.pin().draw = function() {
var a = this.bodyA();
var b = this.bodyB();
render.line([a.transform().pos.xy, b.transform().pos.xy], [0,1,1,1], 1);
}
}
render.circle = function(pos, radius, color) {
var mat = {
radius: radius,
coord: pos,
shade: color
};
render.setpipeline(circleshader.pipe);
render.shader_apply_material(circleshader, mat);
var bind = render.sg_bind(circleshader, shape.quad, mat);
bind.inst = 1;
render.spdraw(bind);
}
render.poly = function(points, color, transform) {
var buffer = render.poly_prim(points);
var mat = { shade: color};
render.setpipeline(polyshader.pipe);
render.setunim4(0,polyshader.vs.unimap.model.slot, transform);
render.shader_apply_material(polyshader, mat);
var bind = render.sg_bind(polyshader, buffer, mat);
bind.inst = 1;
render.spdraw(bind);
}
render.line = function(points, color = Color.white, thickness = 1, transform) {
var buffer = os.make_line_prim(points, thickness, 0, false);
render.setpipeline(polyshader.pipe);
var mat = {
shade: color
};
render.shader_apply_material(polyshader, mat);
render.setunim4(0,polyshader.vs.unimap.model.slot, transform);
var bind = render.sg_bind(polyshader, buffer, mat);
bind.inst = 1;
render.spdraw(bind);
}
/* All draw in screen space */ /* All draw in screen space */
render.point = function(pos,size,color = Color.blue) { render.point = function(pos,size,color = Color.blue) {
render.circle(pos,size,size,color); render.circle(pos,size,size,color);
};
var tmpline = render.line;
render.line = function(points, color = Color.white, thickness = 1) {
tmpline(points,color,thickness);
}; };
render.cross = function(pos, size, color = Color.red) { render.cross = function(pos, size, color = Color.red) {
var a = [ var a = [
pos.add([0,size]), pos.add([0,size]),
pos.add([0,-size]) pos.add([0,-size])
]; ];
var b = [ var b = [
pos.add([size,0]), pos.add([size,0]),
pos.add([-size,0]) pos.add([-size,0])
]; ];
render.line(a,color);
render.line(a,color); render.line(b,color);
render.line(b,color); };
};
render.arrow = function(start, end, color = Color.red, wingspan = 4, wingangle = 10) { render.arrow = function(start, end, color = Color.red, wingspan = 4, wingangle = 10) {
var dir = end.sub(start).normalized(); var dir = end.sub(start).normalized();
@ -123,10 +515,19 @@ render.text = function(str, pos, size = 1, color = Color.white, wrap = -1, ancho
return bb; return bb;
}; };
render.image = function(tex, pos, rotation = 0, color = Color.white, dimensions = [tex.width, tex.height]) { render.image = function(tex, pos, scale = 1, rotation = 0, color = Color.white, dimensions = [tex.width, tex.height]) {
var scale = [dimensions.x/tex.width, dimensions.y/tex.height]; var t = os.make_transform();
gui.img(tex,pos, scale, 0.0, false, [0.0,0.0], color); t.pos = pos;
return bbox.fromcwh([0,0], [tex.width,tex.height]); t.scale = [scale,scale,scale];
render.setpipeline(render.spriteshader.pipe);
render.setunim4(0, render.spriteshader.vs.unimap.model.slot, t);
render.shader_apply_material(render.spriteshader, {
shade: color,
diffuse: tex
});
var bind = render.sg_bind(render.spriteshader, shape.quad, {diffuse:tex});
bind.inst = 1;
render.spdraw(bind);
} }
render.fontcache = {}; render.fontcache = {};
@ -139,12 +540,10 @@ render.set_font = function(path, size) {
} }
render.doc = "Draw shapes in screen space."; render.doc = "Draw shapes in screen space.";
render.circle.doc = "Draw a circle at pos, with a given radius and color."; //render.circle.doc = "Draw a circle at pos, with a given radius and color.";
render.cross.doc = "Draw a cross centered at pos, with arm length size."; render.cross.doc = "Draw a cross centered at pos, with arm length size.";
render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle."; render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle.";
render.poly.doc = "Draw a concave polygon from a set of points.";
render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright."; render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright.";
render.box.doc = "Draw a box centered at pos, with width and height in the tuple wh.";
render.line.doc = "Draw a line from a set of points, and a given thickness.";
return {render}; return {render};

View file

@ -192,7 +192,7 @@ Cmdline.register_order("edit", function() {
} }
window.size = [1280, 720]; window.size = [1280, 720];
window.mode = window.modetypes.full; window.mode = "full";
sim.pause(); sim.pause();
game.engine_start(function() { game.engine_start(function() {
@ -243,13 +243,9 @@ Cmdline.register_order("play", function(argv) {
var project = json.decode(io.slurp(projectfile)); var project = json.decode(io.slurp(projectfile));
game.title = project.title; game.title = project.title;
window.mode = window.modetypes.expand;
global.mixin("config.js"); global.mixin("config.js");
if (project.title) window.title = project.title; if (project.title) window.title = project.title;
if (window.rendersize.equal([0,0])) window.rendersize = window.size;
console.info(`Starting game with window size ${window.size} and render ${window.rendersize}.`);
game.engine_start(function() { game.engine_start(function() {
render.set_font("fonts/c64.ttf", 8); render.set_font("fonts/c64.ttf", 8);
global.app = actor.spawn("game.js"); global.app = actor.spawn("game.js");
@ -455,8 +451,8 @@ function cmd_args(cmdargs)
if (cmds.length === 0) if (cmds.length === 0)
cmds[0] = "play"; cmds[0] = "play";
else if (!Cmdline.orders[cmds[0]]) { else if (!Cmdline.orders[cmds[0]]) {
cmds[0] = "play";
console.warn(`Command ${cmds[0]} not found. Playing instead.`); console.warn(`Command ${cmds[0]} not found. Playing instead.`);
cmds[0] = "play";
} }
Cmdline.orders[cmds[0]](cmds.slice(1)); Cmdline.orders[cmds[0]](cmds.slice(1));
@ -476,9 +472,54 @@ Cmdline.register_cmd("l", function(n) {
console.level = n; console.level = n;
}, "Set log level."); }, "Set log level.");
function convertYAMLtoJSON(yamlString) {
const lines = yamlString.split('\n');
const jsonObj = {};
let currentKey = '';
let currentValue = '';
let currentDepth = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line || line.startsWith('#')) {
continue;
}
const depth = (line.match(/^\s+/g) || [''])[0].length;
const keyValue = line.split(':');
const key = keyValue[0].trim();
const value = keyValue[1].trim();
if (depth > currentDepth) {
jsonObj[currentKey] = convertYAMLtoJSON(currentValue);
currentKey = key;
currentValue = value;
} else if (depth === currentDepth) {
jsonObj[currentKey] = convertYAMLtoJSON(currentValue);
currentKey = key;
currentValue = value;
} else {
jsonObj[currentKey] = convertYAMLtoJSON(currentValue);
currentKey = '';
currentValue = '';
i--; // To reprocess the current line with updated values
}
currentDepth = depth;
}
if (currentKey) {
jsonObj[currentKey] = convertYAMLtoJSON(currentValue);
}
return jsonObj;
}
return { return {
Resources, Resources,
Cmdline, Cmdline,
cmd_args cmd_args,
convertYAMLtoJSON
}; };

View file

@ -1,4 +1,3 @@
var inputpanel = { var inputpanel = {
title: "untitled", title: "untitled",
toString() { return this.title; }, toString() { return this.title; },

51
shaders/base.cg Normal file
View file

@ -0,0 +1,51 @@
#blend mix
#primitive triangle
#cull none
#depth off
@vs vs
in vec3 a_pos;
in vec2 a_uv;
out vec2 uv;
#define PI 3.141592
vec3 pos;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 vp;
uniform mat4 model;
@include_block vert
void main()
{
pos = a_pos;
uv = a_uv;
vert();
gl_Position = vp * model * vec4(pos, 1.0);
}
@end
@fs fs
in vec2 uv;
out vec4 color;
#define PI 3.141592
texture2D diffuse;
sampler smp;
@include_block frag
void main()
{
frag();
}
@end
@program sprite vs fs

61
shaders/basetext.cg Normal file
View file

@ -0,0 +1,61 @@
#depth off
@vs vs
in vec2 a_pos;
in vec2 a_uv;
struct letter {
vec2 pos;
vec2 wh;
vec2 uv;
vec2 st;
vec4 color;
};
readonly buffer ssbo {
letter ls[];
};
out vec2 uv;
out vec2 fuv;
out vec4 color0;
vec2 pos;
uniform mat4 vp;
@include_block vert
void main()
{
letter l = ls[gl_InstanceIndex];
fuv = l.uv + vec2(a_pos.x*l.st.x, l.st.y - a_pos.y*l.st.y);
uv = a_uv;
color0 = l.color;
pos = l.pos+(a_pos*l.wh);
vert();
gl_Position = vp * vec4(pos, 0.0, 1.0);
}
@end
@fs fs
in vec2 uv;
in vec2 fuv;
in vec4 color0;
out vec4 color;
texture2D text;
sampler smp;
@include_block frag
void main()
{
float lettera = texture(sampler2D(text,smp),fuv).r;
if (lettera < 0.1f) discard;
frag();
}
@end
@program text vs fs

44
shaders/circle.cg Normal file
View file

@ -0,0 +1,44 @@
#blend mix
#primitive triangle
#cull none
#depth off
@vs vert
in vec3 a_pos;
uniform float radius;
uniform vec2 coord;
uniform mat4 vp;
out vec2 coords;
out float rad;
void main() {
vec3 pos = a_pos;
pos.xy -= 0.5;
pos.xy *= 2;
coords = pos.xy;
pos *= radius;
pos.xy += coord;
rad = radius;
gl_Position = vp * vec4(pos,1);
}
@end
@fs frag
in vec2 coords;
in float rad;
uniform vec4 shade;
out vec4 color;
void main() {
float px = 1/rad;
float R = 1;
float R2 = 0.90;
float dist = sqrt(dot(coords,coords));
float sm = 1 - smoothstep(R-px,R,dist);
float sm2 = smoothstep(R2-px,R2,dist);
float alpha = sm*sm2;
color = vec4(shade.xyz, alpha*alpha);
}
@end
@program circle vert frag

25
shaders/poly.cg Normal file
View file

@ -0,0 +1,25 @@
#depth off
#primitive triangle
#cull none
#blend mix
@vs vs
in vec3 a_pos;
uniform mat4 vp;
uniform mat4 model;
void main() {
gl_Position = vp * model * vec4(a_pos, 1);
}
@end
@fs fs
uniform vec4 shade;
out vec4 color;
void main() {
color = shade;
}
@end
@program sprite vs fs

42
shaders/postbase.cg Normal file
View file

@ -0,0 +1,42 @@
#cull back
@vs vs
in vec3 a_pos;
in vec2 a_uv;
out vec2 uv;
void main()
{
vec3 pos = a_pos;
pos -= 0.5;
pos *= 2;
uv = a_uv;
gl_Position = vec4(pos.xy, 0, 1.0);
}
@end
@fs fs
in vec2 uv;
out vec4 color;
#define PI 3.141592
@image_sample_type diffuse unfilterable_float
texture2D diffuse;
@sampler_type smp nonfiltering
sampler smp;
uniform vec2 mouse;
uniform float time;
@include_block frag
void main()
{
frag();
}
@end
@program p vs fs

8
shaders/simplepost.cg Normal file
View file

@ -0,0 +1,8 @@
@block frag
void frag()
{
color = texture(sampler2D(diffuse,smp),uv);
}
@end
#include <postbase.cg>

22
shaders/sprite.cg Normal file
View file

@ -0,0 +1,22 @@
@block vert
uniform vec4 emissive;
uniform vec4 rect;
uniform vec2 diffuse_size;
void vert()
{
pos *= vec3(diffuse_size * rect.zw,1);
uv = (uv*rect.zw)+rect.xy;
}
@end
@block frag
uniform vec4 shade;
void frag()
{
color = texture(sampler2D(diffuse,smp), uv);
if (color.a < 0.1) discard;
color *= shade;
}
@end
#include <base.cg>

3
shaders/stdvert.cg Normal file
View file

@ -0,0 +1,3 @@
@block vert
void vert(){}
@end

10
shaders/text_base.cg Normal file
View file

@ -0,0 +1,10 @@
#include <stdvert.cg>
@block frag
void frag()
{
color = color0;
}
@end
#include <basetext.cg>

View file

@ -1,541 +1,25 @@
#include "2dphysics.h" #include "2dphysics.h"
#include "gameobject.h" #include "gameobject.h"
#include <string.h>
#include "debugdraw.h"
#include "stb_ds.h" #include "stb_ds.h"
#include <assert.h>
#include <chipmunk/chipmunk_unsafe.h>
#include <math.h>
#include "2dphysics.h"
#include "jsffi.h" #include "jsffi.h"
#include "script.h"
#include "log.h"
cpSpace *space = NULL; cpSpace *space = NULL;
struct rgba color_white = {255,255,255,255}; static JSValue *fns = NULL;
struct rgba color_black = {0,0,0,255}; static JSValue *hits = NULL;
struct rgba color_clear = {0,0,0,0};
struct rgba disabled_color = {148,148,148,255};
struct rgba sleep_color = {255,140,228,255};
struct rgba dynamic_color = {255,70,46,255};
struct rgba kinematic_color = {255, 194, 64, 255};
struct rgba static_color = {73,209,80,255};
static JSValue fns[100];
static JSValue hits[100];
static int cb_idx = 0;
static const unsigned char col_alpha = 40;
static const float sensor_seg = 10;
cpTransform m3_to_cpt(HMM_Mat3 m)
{
cpTransform t;
t.a = m.Columns[0].x;
t.b = m.Columns[0].y;
t.tx = m.Columns[2].x;
t.c = m.Columns[1].x;
t.d = m.Columns[1].y;
t.ty = m.Columns[2].y;
return t;
}
cpShape *phys2d_query_pos(cpVect pos) {
return cpSpacePointQueryNearest(space, pos, 0.f, CP_SHAPE_FILTER_ALL, NULL);
}
static int qhit;
void qpoint(cpShape *shape, cpFloat dist, cpVect point, int *data)
{
qhit++;
}
void bbhit(cpShape *shape, int *data)
{
qhit++;
}
int query_point(HMM_Vec2 pos)
{
qhit = 0;
// cpSpacePointQuery(space, pos.cp, 0, filter, qpoint, &qhit);
cpSpaceBBQuery(space, cpBBNewForCircle(pos.cp, 2), CP_SHAPE_FILTER_ALL, bbhit, &qhit);
return qhit;
}
int p_compare(void *a, void *b)
{
if (a > b) return 1;
if (a < b) return -1;
return 0;
}
gameobject **clean_ids(gameobject **ids)
{
qsort(ids, arrlen(ids), sizeof(*ids), p_compare);
gameobject *curid = NULL;
for (int i = arrlen(ids)-1; i >= 0; i--)
if (ids[i] == curid)
arrdelswap(ids, i);
else
curid = ids[i];
return ids;
}
int cpshape_enabled(cpShape *c) {
cpShapeFilter filter = cpShapeGetFilter(c);
if (filter.categories == ~CP_ALL_CATEGORIES && filter.mask == ~CP_ALL_CATEGORIES)
return 0;
return 1;
}
struct rgba shape_color(cpShape *shape) {
if (!cpshape_enabled(shape)) return disabled_color;
switch (cpBodyGetType(cpShapeGetBody(shape))) {
case CP_BODY_TYPE_DYNAMIC:
// cpBodySleep(cpShapeGetBody(shape));
if (cpBodyIsSleeping(cpShapeGetBody(shape)))
return sleep_color;
return dynamic_color;
case CP_BODY_TYPE_KINEMATIC:
return kinematic_color;
case CP_BODY_TYPE_STATIC:
return static_color;
}
return static_color;
}
static warp_gravity *space_gravity;
void phys2d_init() void phys2d_init()
{ {
space = cpSpaceNew(); space = cpSpaceNew();
cpSpaceSetSleepTimeThreshold(space, 1);
cpSpaceSetCollisionSlop(space, 0.01);
cpSpaceSetCollisionBias(space, cpfpow(1.0-0.5, 165.f));
space_gravity = warp_gravity_make();
}
void phys2d_set_gravity(HMM_Vec2 v)
{
float str = HMM_LenV2(v);
HMM_Vec2 dir = HMM_NormV2(v);
space_gravity->strength = str;
space_gravity->t.scale = (HMM_Vec3){v.x,v.y, 0};
space_gravity->planar_force = (HMM_Vec3){v.x,v.y,0};
}
constraint *constraint_make(cpConstraint *c)
{
constraint *cp = malloc(sizeof(*cp));
cp->c = c;
cp->break_cb = JS_UNDEFINED;
cp->remove_cb = JS_UNDEFINED;
cpSpaceAddConstraint(space,c);
cpConstraintSetUserData(c, cp);
return cp;
}
void constraint_break(constraint *constraint)
{
if (!constraint->c) return;
cpSpaceRemoveConstraint(space, constraint->c);
cpConstraintFree(constraint->c);
constraint->c = NULL;
script_call_sym(constraint->break_cb,0,NULL);
}
void constraint_free(constraint *constraint)
{
constraint_break(constraint);
free(constraint);
}
void constraint_test(cpConstraint *constraint, float *dt)
{
float max = cpConstraintGetMaxForce(constraint);
if (!isfinite(max)) return;
float force = cpConstraintGetImpulse(constraint)/ *dt;
if (force > max)
constraint_break(cpConstraintGetUserData(constraint));
} }
void phys2d_update(float deltaT) { void phys2d_update(float deltaT) {
cpSpaceStep(space, deltaT); cpSpaceStep(space, deltaT);
cpSpaceEachConstraint(space, constraint_test, &deltaT); arrsetlen(fns,0);
cb_idx = 0; arrsetlen(hits,0);
} }
void init_phys2dshape(struct phys2d_shape *shape, gameobject *go, void *data) {
shape->go = go;
shape->data = data;
shape->t.scale = (HMM_Vec2){1.0,1.0};
go_shape_apply(go->body, shape->shape, go);
cpShapeSetCollisionType(shape->shape, (cpCollisionType)go);
cpShapeSetUserData(shape->shape, shape);
}
void phys2d_shape_del(struct phys2d_shape *shape) {
if (!shape->shape) return;
cpSpaceRemoveShape(space, shape->shape);
cpShapeFree(shape->shape);
}
/***************** CIRCLE2D *****************/
struct phys2d_circle *Make2DCircle(gameobject *go) {
struct phys2d_circle *new = malloc(sizeof(struct phys2d_circle));
new->radius = 10.f;
new->offset = v2zero;
new->shape.shape = cpSpaceAddShape(space, cpCircleShapeNew(go->body, new->radius, cpvzero));
new->shape.debugdraw = phys2d_dbgdrawcircle;
new->shape.moi = phys2d_circle_moi;
new->shape.apply = phys2d_applycircle;
new->shape.free = NULL;
init_phys2dshape(&new->shape, go, new);
phys2d_applycircle(new);
return new;
}
float phys2d_circle_moi(struct phys2d_circle *c) {
float m = c->shape.go->mass;
return cpMomentForCircle(m, 0, cpCircleShapeGetRadius(c->shape.shape), cpCircleShapeGetOffset(c->shape.shape));
}
void phys2d_circledel(struct phys2d_circle *c) { phys2d_shape_del(&c->shape); }
void circle2d_free(circle2d *c) { phys2d_circledel(c); }
void phys2d_dbgdrawcpcirc(cpShape *c) {
HMM_Vec2 pos = mat_t_pos(t_go2world(shape2go(c)), (HMM_Vec2)cpCircleShapeGetOffset(c));
float radius = cpCircleShapeGetRadius(c);
struct rgba color = shape_color(c);
float seglen = cpShapeGetSensor(c) ? 5 : -1;
draw_circle(pos, radius, 1, color, seglen);
color.a = col_alpha;
draw_circle(pos,radius,radius,color,-1);
}
void phys2d_shape_apply(struct phys2d_shape *s)
{
float moment = cpBodyGetMoment(s->go->body);
float moi = s->moi(s->data);
s->apply(s->data);
float newmoi = s->moi(s->data);
moment-=moi;
moment += newmoi;
if (moment < 0) moment = 0;
cpBodySetMoment(s->go->body, moment);
}
void phys2d_dbgdrawcircle(struct phys2d_circle *circle) {
phys2d_dbgdrawcpcirc(circle->shape.shape);
}
void phys2d_applycircle(struct phys2d_circle *circle) {
gameobject *go = circle->shape.go;
float radius = circle->radius * HMM_MAX(HMM_ABS(go->scale.X), HMM_ABS(go->scale.Y));
cpCircleShapeSetRadius(circle->shape.shape, radius);
cpCircleShapeSetOffset(circle->shape.shape, circle->offset.cp);
}
/************** POLYGON ************/
struct phys2d_poly *Make2DPoly(gameobject *go) {
struct phys2d_poly *new = malloc(sizeof(struct phys2d_poly));
new->points = NULL;
arrsetlen(new->points, 0);
new->radius = 0.f;
new->shape.shape = cpSpaceAddShape(space, cpPolyShapeNewRaw(go->body, 0, (cpVect*)new->points, new->radius));
new->shape.debugdraw = phys2d_dbgdrawpoly;
new->shape.moi = phys2d_poly_moi;
new->shape.free = phys2d_poly_free;
new->shape.apply = phys2d_applypoly;
init_phys2dshape(&new->shape, go, new);
return new;
}
void phys2d_poly_free(struct phys2d_poly *poly)
{
arrfree(poly->points);
free(poly);
}
float phys2d_poly_moi(struct phys2d_poly *poly) {
float m = poly->shape.go->mass;
int len = cpPolyShapeGetCount(poly->shape.shape);
if (!len) {
YughWarn("Cannot evaluate the MOI of a polygon of length %d.", len);
return 0;
}
cpVect points[len];
for (int i = 0; i < len; i++)
points[i] = cpPolyShapeGetVert(poly->shape.shape, i);
float moi = cpMomentForPoly(m, len, points, cpvzero, poly->radius);
if (!isfinite(moi))
return 0;
return moi;
}
void phys2d_polydel(struct phys2d_poly *poly) {
arrfree(poly->points);
phys2d_shape_del(&poly->shape);
}
void phys2d_polyaddvert(struct phys2d_poly *poly) {
arrput(poly->points, v2zero);
}
void phys2d_poly_setverts(struct phys2d_poly *poly, HMM_Vec2 *verts) {
if (!verts) return;
if (poly->points)
arrfree(poly->points);
arrsetlen(poly->points, arrlen(verts));
for (int i = 0; i < arrlen(verts); i++)
poly->points[i] = verts[i];
phys2d_shape_apply(&poly->shape);
}
void phys2d_applypoly(struct phys2d_poly *poly) {
if (arrlen(poly->points) <= 0) return;
assert(sizeof(poly->points[0]) == sizeof(cpVect));
struct gameobject *go = poly->shape.go;
transform2d t = go2t(shape2go(poly->shape.shape));
t.pos.cp = cpvzero;
t.angle = 0;
cpTransform T = m3_to_cpt(transform2d2mat(t));
cpPolyShapeSetVerts(poly->shape.shape, arrlen(poly->points), (cpVect*)poly->points, T);
cpPolyShapeSetRadius(poly->shape.shape, poly->radius);
cpSpaceReindexShapesForBody(space, cpShapeGetBody(poly->shape.shape));
}
void phys2d_dbgdrawpoly(struct phys2d_poly *poly) {
struct rgba color = shape_color(poly->shape.shape);
struct rgba line_color = color;
color.a = col_alpha;
if (arrlen(poly->points) >= 3) {
int n = cpPolyShapeGetCount(poly->shape.shape);
HMM_Vec2 points[n+1];
transform2d t = go2t(shape2go(poly->shape.shape));
t.scale = (HMM_Vec2){1,1};
HMM_Mat3 rt = transform2d2mat(t);
for (int i = 0; i < n; i++)
points[i] = mat_t_pos(rt, (HMM_Vec2)cpPolyShapeGetVert(poly->shape.shape, i));
points[n] = points[0];
draw_poly(points, n, color);
float seglen = cpShapeGetSensor(poly->shape.shape) ? sensor_seg : 0;
draw_line(points, n, line_color, seglen, 0);
}
}
/****************** EDGE 2D**************/
struct phys2d_edge *Make2DEdge(gameobject *go) {
struct phys2d_edge *new = malloc(sizeof(struct phys2d_edge));
new->points = NULL;
arrsetlen(new->points, 0);
new->thickness = 0.f;
new->shapes = NULL;
arrsetlen(new->shapes, 0);
new->shape.go = go;
new->shape.data = new;
new->shape.debugdraw = phys2d_dbgdrawedge;
new->shape.moi = phys2d_edge_moi;
new->shape.shape = NULL;
new->shape.apply = NULL;
new->shape.free = phys2d_edge_free;
new->draws = 0;
phys2d_applyedge(new);
return new;
}
void phys2d_edge_free(struct phys2d_edge *edge)
{
for (int i = 0; i < arrlen(edge->shapes); i++)
cpShapeSetUserData(edge->shapes[i], NULL);
arrfree(edge->points);
arrfree(edge->shapes);
free(edge);
}
float phys2d_edge_moi(struct phys2d_edge *edge) {
float m = edge->shape.go->mass;
float moi = 0;
for (int i = 0; i < arrlen(edge->points) - 1; i++)
moi += cpMomentForSegment(m, edge->points[i].cp, edge->points[i + 1].cp, edge->thickness);
return moi;
}
void phys2d_edgedel(struct phys2d_edge *edge) { phys2d_shape_del(&edge->shape); }
void phys2d_edgeaddvert(struct phys2d_edge *edge, HMM_Vec2 v) {
arrput(edge->points, v);
if (arrlen(edge->points) > 1)
arrput(edge->shapes, cpSpaceAddShape(space, cpSegmentShapeNew(edge->shape.go->body, cpvzero, cpvzero, edge->thickness)));
}
void phys2d_edge_rmvert(struct phys2d_edge *edge, int index) {
if (index>arrlen(edge->points) || index < 0) return;
arrdel(edge->points, index);
if (arrlen(edge->points) == 0) return;
if (index == 0) {
cpSpaceRemoveShape(space, edge->shapes[index]);
cpShapeFree(edge->shapes[index]);
arrdel(edge->shapes, index);
return;
}
if (index != arrlen(edge->points))
cpSegmentShapeSetEndpoints(edge->shapes[index - 1], edge->points[index - 1].cp, edge->points[index].cp);
cpSpaceRemoveShape(space, edge->shapes[index - 1]);
cpShapeFree(edge->shapes[index - 1]);
arrdel(edge->shapes, index - 1);
}
void phys2d_edge_setvert(struct phys2d_edge *edge, int index, cpVect val) {
assert(arrlen(edge->points) > index && index >= 0);
edge->points[index].cp = val;
}
void phys2d_edge_update_verts(struct phys2d_edge *edge, HMM_Vec2 *verts)
{
if (arrlen(edge->points) == arrlen(verts)) {
for (int i = 0; i < arrlen(verts); i++)
phys2d_edge_setvert(edge,i,verts[i].cp);
} else {
int vertchange = arrlen(verts)-arrlen(edge->points);
phys2d_edge_clearverts(edge);
phys2d_edge_addverts(edge,verts);
}
phys2d_applyedge(edge);
}
void phys2d_edge_clearverts(struct phys2d_edge *edge) {
for (int i = arrlen(edge->points) - 1; i >= 0; i--)
phys2d_edge_rmvert(edge, i);
}
void phys2d_edge_addverts(struct phys2d_edge *edge, HMM_Vec2 *verts) {
for (int i = 0; i < arrlen(verts); i++)
phys2d_edgeaddvert(edge, verts[i]);
}
/* Calculates all true positions of verts, links them up, and so on */
void phys2d_applyedge(struct phys2d_edge *edge) {
struct gameobject *go = edge->shape.go;
for (int i = 0; i < arrlen(edge->shapes); i++) {
/* Points must be scaled with gameobject, */
HMM_Vec2 a = HMM_MulV2(go->scale.xy, edge->points[i]);
HMM_Vec2 b = HMM_MulV2(go->scale.xy, edge->points[i+1]);
cpSegmentShapeSetEndpoints(edge->shapes[i], a.cp, b.cp);
cpSegmentShapeSetRadius(edge->shapes[i], edge->thickness);
if (i > 0 && i < arrlen(edge->shapes) - 1)
cpSegmentShapeSetNeighbors(edge->shapes[i], HMM_MulV2(go->scale.xy,edge->points[i-1]).cp, HMM_MulV2(go->scale.xy,edge->points[i+2]).cp);
go_shape_apply(NULL, edge->shapes[i], go);
cpShapeSetUserData(edge->shapes[i], &edge->shape);
}
cpSpaceReindexShapesForBody(space, edge->shape.go->body);
}
void phys2d_dbgdrawedge(struct phys2d_edge *edge) {
edge->draws++;
if (edge->draws > 1) {
if (edge->draws >= arrlen(edge->shapes))
edge->draws = 0;
return;
}
if (arrlen(edge->shapes) < 1) return;
HMM_Vec2 drawpoints[arrlen(edge->points)];
struct gameobject *go = edge->shape.go;
HMM_Mat3 g2w = t_go2world(go);
for (int i = 0; i < arrlen(edge->points); i++)
drawpoints[i] = mat_t_pos(g2w, edge->points[i]);
float seglen = cpShapeGetSensor(edge->shapes[0]) ? sensor_seg : 0;
struct rgba color = shape_color(edge->shapes[0]);
struct rgba line_color = color;
color.a = col_alpha;
draw_edge(drawpoints, arrlen(edge->points), color, edge->thickness * 2, 0, line_color, seglen);
draw_points(drawpoints, arrlen(edge->points), 2, kinematic_color);
}
/************ COLLIDER ****************/
void shape_enabled(struct phys2d_shape *shape, int enabled) {
cpShapeFilter set = enabled ? CP_SHAPE_FILTER_ALL : CP_SHAPE_FILTER_NONE;
if (!shape->shape) {
struct phys2d_edge *edge = shape->data;
for (int i = 0; i < arrlen(edge->shapes[i]); i++)
cpShapeSetFilter(edge->shapes[i], set);
} else
cpShapeSetFilter(shape->shape, set);
}
int shape_is_enabled(struct phys2d_shape *shape) {
if (cpshape_enabled(shape->shape))
return 1;
return 0;
}
void shape_set_sensor(struct phys2d_shape *shape, int sensor) {
if (!shape->shape) {
struct phys2d_edge *edge = shape->data;
for (int i = 0; i < arrlen(edge->shapes); i++)
cpShapeSetSensor(edge->shapes[i], sensor);
} else
cpShapeSetSensor(shape->shape, sensor);
}
int shape_get_sensor(struct phys2d_shape *shape) {
if (!shape->shape) {
struct phys2d_edge *edge = shape->data;
if (arrlen(edge->shapes) > 0) return cpShapeGetSensor(edge->shapes[0]);
return 0;
}
return cpShapeGetSensor(shape->shape);
}
void phys2d_reindex_body(cpBody *body) { cpSpaceReindexShapesForBody(space, body); }
JSValue arb2js(cpArbiter *arb) JSValue arb2js(cpArbiter *arb)
{ {
cpBody *body1; cpBody *body1;
@ -546,18 +30,21 @@ JSValue arb2js(cpArbiter *arb)
cpShape *shape2; cpShape *shape2;
cpArbiterGetShapes(arb, &shape1, &shape2); cpArbiterGetShapes(arb, &shape1, &shape2);
struct phys2d_shape *pshape = cpShapeGetUserData(shape2); JSValue j = *(JSValue*)cpShapeGetUserData(shape2);
gameobject *go2 = cpBodyGetUserData(body2);
JSValue obj = JS_NewObject(js); JSValue jg = body2go(body2)->ref;
JS_SetPropertyStr(js, obj, "normal", vec22js((HMM_Vec2)cpArbiterGetNormal(arb)));
JS_SetPropertyStr(js, obj, "obj", JS_DupValue(js,go2->ref));
JS_SetPropertyStr(js, obj, "shape", JS_DupValue(js, pshape->ref));
// JS_SetPropertyStr(js, obj, "point", vec22js((HMM_Vec2)cpArbiterGetPointA(arb, 0)));
HMM_Vec2 srfv; HMM_Vec2 srfv;
srfv.cp = cpArbiterGetSurfaceVelocity(arb); srfv.cp = cpArbiterGetSurfaceVelocity(arb);
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "normal", vec22js((HMM_Vec2)cpArbiterGetNormal(arb)));
JS_SetPropertyStr(js, obj, "obj", JS_DupValue(js,jg));
JS_SetPropertyStr(js, obj, "shape", JS_DupValue(js, j));
JS_SetPropertyStr(js, obj, "point", vec22js((HMM_Vec2)cpArbiterGetPointA(arb, 0)));
JS_SetPropertyStr(js, obj, "velocity", vec22js(srfv)); JS_SetPropertyStr(js, obj, "velocity", vec22js(srfv));
JS_SetPropertyStr(js, obj, "impulse", vec22js((HMM_Vec2)cpArbiterTotalImpulse(arb)));
JS_SetPropertyStr(js, obj, "ke", number2js(cpArbiterTotalKE(arb)));
return obj; return obj;
} }
@ -575,31 +62,21 @@ void register_hit(cpArbiter *arb, gameobject *go, const char *name)
JSValue cb = JS_GetPropertyStr(js, go->ref, name); JSValue cb = JS_GetPropertyStr(js, go->ref, name);
if (!JS_IsUndefined(cb)) { if (!JS_IsUndefined(cb)) {
JSValue jarb = arb2js(arb); JSValue jarb = arb2js(arb);
fns[cb_idx] = JS_DupValue(js, cb); arrput(fns, JS_DupValue(js,cb));
hits[cb_idx] = jarb; arrput(hits, jarb);
cpSpaceAddPostStepCallback(space, phys_run_post, &fns[cb_idx], &hits[cb_idx]); cpSpaceAddPostStepCallback(space, phys_run_post, fns+arrlen(fns)-1, hits+arrlen(hits)-1);
cb_idx++;
} }
cpShape *s1, *s2; cpShape *s1, *s2;
cpArbiterGetShapes(arb, &s1, &s2); cpArbiterGetShapes(arb, &s1, &s2);
gameobject *g1, *g2; JSValue j1 = *(JSValue*)cpShapeGetUserData(s1);
g1 = shape2go(s1); JSValue j2 = *(JSValue*)cpShapeGetUserData(s2);
g2 = shape2go(g2); cb = JS_GetPropertyStr(js, j1, name);
if (!g1) return;
if (!g2) return;
if (JS_IsUndefined(g1->ref)) return;
if (JS_IsUndefined(g2->ref)) return;
struct phys2d_shape *pshape1 = cpShapeGetUserData(s1);
if (JS_IsUndefined(pshape1->ref)) return;
cb = JS_GetPropertyStr(js, pshape1->ref, name);
if (!JS_IsUndefined(cb)) { if (!JS_IsUndefined(cb)) {
JSValue jarb = arb2js(arb); JSValue jarb = arb2js(arb);
fns[cb_idx] = JS_DupValue(js,cb); arrput(fns, JS_DupValue(js,cb));
hits[cb_idx] = jarb; arrput(hits, jarb);
cpSpaceAddPostStepCallback(space, phys_run_post, &fns[cb_idx], &hits[cb_idx]); cpSpaceAddPostStepCallback(space, phys_run_post, fns+arrlen(fns)-1, hits+arrlen(hits)-1);
cb_idx++;
} }
} }

View file

@ -1,130 +1,15 @@
#ifndef TWODPHYSICS_H #ifndef TWODPHYSICS_H
#define TWODPHYSICS_H #define TWODPHYSICS_H
#include "script.h"
#include <chipmunk/chipmunk.h> #include <chipmunk/chipmunk.h>
#include "gameobject.h" #include "gameobject.h"
#include "render.h" #include "script.h"
#include "transform.h"
extern float phys2d_gravity;
extern int physOn;
extern cpSpace *space; extern cpSpace *space;
extern struct rgba disabled_color;
extern struct rgba dynamic_color;
extern struct rgba kinematic_color;
extern struct rgba static_color;
extern struct rgba sleep_color;
typedef struct constraint {
cpConstraint *c;
JSValue break_cb; /* function called when it is forcibly broken */
JSValue remove_cb; /* called when it is removed at all */
} constraint;
constraint *constraint_make(cpConstraint *c);
void constraint_break(constraint *constraint);
void constraint_free(constraint *constraint);
struct phys2d_shape {
cpShape *shape; /* user data is this phys2d_shape */
JSValue ref;
transform2d t;
gameobject *go;
void *data; /* The specific subtype; phys2d_circle, etc */
void (*debugdraw)(void *data);
float (*moi)(void *data);
void (*apply)(void *data);
void (*free)(void *data);
};
void phys2d_shape_apply(struct phys2d_shape *s);
/* Circles are the fastest colldier type */
struct phys2d_circle {
float radius;
HMM_Vec2 offset;
struct phys2d_shape shape;
};
typedef struct phys2d_circle circle2d;
/* A convex polygon; defined as the convex hull around the given set of points */
struct phys2d_poly {
HMM_Vec2 *points;
transform2d t;
float radius;
struct phys2d_shape shape;
};
/* An edge with no volume. Cannot collide with each other. Join to make levels. Static only. */
struct phys2d_edge {
HMM_Vec2 *points; /* Points defined relative to the gameobject */
float thickness;
cpShape **shapes;
struct phys2d_shape shape;
int draws;
};
struct phys2d_circle *Make2DCircle(gameobject *go);
void phys2d_circledel(struct phys2d_circle *c);
void circle2d_free(circle2d *c);
void phys2d_applycircle(struct phys2d_circle *circle);
void phys2d_dbgdrawcircle(struct phys2d_circle *circle);
float phys2d_circle_moi(struct phys2d_circle *c);
struct phys2d_poly *Make2DPoly(gameobject *go);
void phys2d_poly_free(struct phys2d_poly *poly);
void phys2d_polydel(struct phys2d_poly *poly);
void phys2d_applypoly(struct phys2d_poly *poly);
void phys2d_dbgdrawpoly(struct phys2d_poly *poly);
void phys2d_polyaddvert(struct phys2d_poly *poly);
void phys2d_poly_setverts(struct phys2d_poly *poly, HMM_Vec2 *verts);
float phys2d_poly_moi(struct phys2d_poly *poly);
struct phys2d_edge *Make2DEdge(gameobject *go);
void phys2d_edge_free(struct phys2d_edge *edge);
void phys2d_edgedel(struct phys2d_edge *edge);
void phys2d_applyedge(struct phys2d_edge *edge);
void phys2d_dbgdrawedge(struct phys2d_edge *edge);
void phys2d_edgeaddvert(struct phys2d_edge *edge, HMM_Vec2 v);
void phys2d_edge_rmvert(struct phys2d_edge *edge, int index);
float phys2d_edge_moi(struct phys2d_edge *edge);
void phys2d_edge_setvert(struct phys2d_edge *edge, int index, cpVect val);
void phys2d_edge_clearverts(struct phys2d_edge *edge);
void phys2d_edge_rmvert(struct phys2d_edge *edge, int index);
void phys2d_edge_update_verts(struct phys2d_edge *edge, HMM_Vec2 *verts);
void phys2d_edge_addverts(struct phys2d_edge *edge, HMM_Vec2 *verts);
void phys2d_edge_set_sensor(struct phys2d_edge *edge, int sensor);
void phys2d_edge_set_enabled(struct phys2d_edge *edge, int enabled);
void phys2d_init(); void phys2d_init();
void phys2d_update(float deltaT); void phys2d_update(float deltaT);
cpShape *phys2d_query_pos(cpVect pos);
void phys2d_query_ray(HMM_Vec2 start, HMM_Vec2 end, float radius, cpShapeFilter filter, JSValue cb);
struct shape_cb {
struct phys2d_shape *shape;
struct phys_cbs cbs;
};
void fire_hits();
void phys2d_set_gravity(HMM_Vec2 v);
void shape_enabled(struct phys2d_shape *shape, int enabled);
int shape_is_enabled(struct phys2d_shape *shape);
void shape_set_sensor(struct phys2d_shape *shape, int sensor);
int shape_get_sensor(struct phys2d_shape *shape);
struct rgba shape_color_s(cpShape *shape);
void shape_gui(struct phys2d_shape *shape);
void phys2d_setup_handlers(gameobject *go); void phys2d_setup_handlers(gameobject *go);
int query_point(HMM_Vec2 pos);
void phys2d_reindex_body(cpBody *body); JSValue arb2js(cpArbiter *arb);
#endif #endif

View file

@ -1,201 +0,0 @@
#include "light.h"
#include <stdbool.h>
/*
void Light::serialize(FILE * file)
{
GameObject::serialize(file);
SerializeFloat(file, &strength);
SerializeVec3(file, (float *) &color);
SerializeBool(file, &dynamic);
}
void Light::deserialize(FILE * file)
{
GameObject::deserialize(file);
DeserializeFloat(file, &strength);
DeserializeVec3(file, (float *) &color);
DeserializeBool(file, &dynamic);
}
static const mfloat_t dlight_init_rot[3] = { 80.f, 120.f, 165.f };
struct mDirectionalLight *dLight = NULL;
struct mDirectionalLight *MakeDLight()
{
if (dLight != NULL) {
dLight =
(struct mDirectionalLight *)
malloc(sizeof(struct mDirectionalLight));
quat_from_euler(dLight->light.obj.transform.rotation,
dlight_init_rot);
return dLight;
}
return dLight;
}
void dlight_prepshader(struct mDirectionalLight *light,
struct shader *shader)
{
mfloat_t fwd[3] = { 0.f };
trans_forward(fwd, &light->light.obj.transform);
shader_setvec3(shader, "dirLight.direction", fwd);
shader_setvec3(shader, "dirLight.color", light->light.color);
shader_setfloat(shader, "dirLight.strength", light->light.strength);
}
static struct mPointLight *pointLights[4];
static int numLights = 0;
struct mPointLight *MakePointlight()
{
if (numLights < 4) {
struct mPointLight *light =
(struct mPointLight *) malloc(sizeof(struct mPointLight));
pointLights[numLights++] = light;
light->light.strength = 0.2f;
light->constant = 1.f;
light->linear = 0.9f;
light->quadratic = 0.032f;
return light;
}
return NULL;
}
static void prepstring(char *buffer, char *prepend, const char *append)
{
snprintf(buffer, 100, "%s%s", prepend, append);
}
void pointlights_prepshader(struct shader *shader)
{
for (int i = 0; i < numLights; i++)
pointlight_prepshader(pointLights[i], shader, i);
}
void pointlight_prepshader(struct mPointLight *light,
struct shader *shader, int num)
{
shader_use(shader);
char prepend[100] = { '\0' };
snprintf(prepend, 100, "%s%d%s", "pointLights[", num, "].");
char str[100] = { '\0' };
prepstring(str, prepend, "position");
shader_setvec3(shader, str, light->light.obj.transform.position);
prepstring(str, prepend, "constant");
shader_setfloat(shader, str, light->constant);
prepstring(str, prepend, "linear");
shader_setfloat(shader, str, light->linear);
prepstring(str, prepend, "quadratic");
shader_setfloat(shader, str, light->quadratic);
prepstring(str, prepend, "strength");
shader_setfloat(shader, str, light->light.strength);
prepstring(str, prepend, "color");
shader_setvec3(shader, str, light->light.color);
}
static struct mSpotLight *spotLights[4];
static int numSpots = 0;
struct mSpotLight *MakeSpotlight()
{
if (numSpots < 4) {
struct mSpotLight *light =
(struct mSpotLight *) malloc(sizeof(struct mSpotLight));
spotLights[numSpots++] = light;
return light;
}
return NULL;
}
void spotlights_prepshader(struct shader *shader)
{
for (int i = 0; i < numSpots; i++)
spotlight_prepshader(spotLights[i], shader, i);
}
void spotlight_prepshader(struct mSpotLight *light, struct shader *shader,
int num)
{
mfloat_t fwd[3] = { 0.f };
trans_forward(fwd, &light->light.obj.transform);
shader_use(shader);
shader_setvec3(shader, "spotLight.position",
light->light.obj.transform.position);
shader_setvec3(shader, "spotLight.direction", fwd);
shader_setvec3(shader, "spotLight.color", light->light.color);
shader_setfloat(shader, "spotLight.strength", light->light.strength);
shader_setfloat(shader, "spotLight.cutoff", light->cutoff);
shader_setfloat(shader, "spotLight.distance", light->distance);
shader_setfloat(shader, "spotLight.outerCutoff", light->outerCutoff);
shader_setfloat(shader, "spotLight.linear", light->linear);
shader_setfloat(shader, "spotLight.quadratic", light->quadratic);
shader_setfloat(shader, "spotLight.constant", light->constant);
}
*/
/*
void light_gui(struct mLight *light)
{
object_gui(&light->obj);
if (nk_tree_push(ctx, NK_TREE_NODE, "Light", NK_MINIMIZED)) {
nk_property_float(ctx, "Strength", 0.f, &light->strength, 1.f, 0.01f, 0.001f);
// ImGui::ColorEdit3("Color", &light->color[0]);
nk_checkbox_label(ctx, "Dynamic", (bool *) &light->dynamic);
nk_tree_pop(ctx);
}
}
void pointlight_gui(struct mPointLight *light)
{
light_gui(&light->light);
if (nk_tree_push(ctx, NK_TREE_NODE, "Point Light", NK_MINIMIZED)) {
nk_property_float(ctx, "Constant", 0.f, &light->constant, 1.f, 0.01f, 0.001f);
nk_property_float(ctx, "Linear", 0.f, &light->linear, 0.3f, 0.01f, 0.001f);
nk_property_float(ctx, "Quadratic", 0.f, &light->quadratic, 0.3f, 0.01f, 0.001f);
nk_tree_pop(ctx);
}
}
void spotlight_gui(struct mSpotLight *spot)
{
light_gui(&spot->light);
if (nk_tree_push(ctx, NK_TREE_NODE, "Spotlight", NK_MINIMIZED)) {
nk_property_float(ctx, "Linear", 0.f, &spot->linear, 1.f, 0.01f, 0.001f);
nk_property_float(ctx, "Quadratic", 0.f, &spot->quadratic, 1.f, 0.01f, 0.001f);
nk_property_float(ctx, "Distance", 0.f, &spot->distance, 200.f, 1.f, 0.1f, 200.f);
nk_property_float(ctx, "Cutoff Degrees", 0.f, &spot->cutoff, 0.7f, 0.01f, 0.001f);
nk_property_float(ctx, "Outer Cutoff Degrees", 0.f, &spot->outerCutoff, 0.7f, 0.01f, 0.001f);
nk_tree_pop(ctx);
}
}
*/

View file

@ -1,63 +0,0 @@
#ifndef LIGHT_H
#define LIGHT_H
#include <stdint.h>
/*
struct mLight {
struct gameobject *go;
uint8_t color[3];
float strength;
int dynamic;
int on;
};
struct mPointLight {
struct mLight light;
float constant;
float linear;
float quadratic;
};
struct mPointLight *MakePointlight();
void pointlight_prepshader(struct mPointLight *light,
struct shader *shader, int num);
void pointlights_prepshader(struct shader *shader);
struct mSpotLight {
struct mLight light;
float constant;
float linear;
float quadratic;
float distance;
float cutoff;
float outerCutoff;
};
struct mSpotLight *MakeSpotlight();
void spotlight_gui(struct mSpotLight *light);
void spotlight_prepshader(struct mSpotLight *light, struct shader *shader,
int num);
void spotlights_prepshader(struct shader *shader);
struct mDirectionalLight {
struct mLight light;
};
void dlight_prepshader(struct mDirectionalLight *light,
struct shader *shader);
struct mDirectionalLight *MakeDLight();
extern struct mDirectionalLight *dLight;
void light_gui(struct mLight *light);
void pointlight_gui(struct mPointLight *light);
void spotlight_gui(struct mSpotLight *spot);
*/
#endif

View file

@ -1,364 +0,0 @@
#include "model.h"
#include "log.h"
#include "resources.h"
#include "stb_ds.h"
#include "font.h"
#include "gameobject.h"
//#include "diffuse.sglsl.h"
#include "unlit.sglsl.h"
#include "render.h"
#include "HandmadeMath.h"
#include "math.h"
#include "time.h"
#define CGLTF_IMPLEMENTATION
#include <cgltf.h>
#include <stdlib.h>
#include <string.h>
#include "texture.h"
#include "sokol/sokol_gfx.h"
static void processnode();
static void processmesh();
static void processtexture();
static sg_shader model_shader;
static sg_pipeline model_pipe;
struct bone_weights {
char b1;
char b2;
char b3;
char b4;
};
struct mesh_v {
HMM_Vec3 pos;
struct uv_n uv;
uint32_t norm;
struct bone_weights bones;
};
void model_init() {
model_shader = sg_make_shader(unlit_shader_desc(sg_query_backend()));
model_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = model_shader,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT3,
[1].format = SG_VERTEXFORMAT_USHORT2N,
[1].buffer_index = 1,
},
},
.index_type = SG_INDEXTYPE_UINT16,
.cull_mode = SG_CULLMODE_FRONT,
.depth.write_enabled = true,
.depth.compare = SG_COMPAREFUNC_LESS_EQUAL
});
}
cgltf_attribute *get_attr_type(cgltf_primitive *p, cgltf_attribute_type t)
{
for (int i = 0; i < p->attributes_count; i++) {
if (p->attributes[i].type == t)
return &p->attributes[i];
}
return NULL;
}
unsigned short pack_short_texcoord(float x, float y)
{
unsigned short s;
char xc = x*255;
char yc = y*255;
return (((unsigned short)yc) << 8) | xc;
}
unsigned short pack_short_tex(float c) { return c * USHRT_MAX; }
uint32_t pack_int10_n2(float *norm)
{
uint32_t ni[3];
for (int i = 0; i < 3; i++) {
ni[i] = fabs(norm[i]) * 511.0 + 0.5;
ni[i] = (ni[i] > 511) ? 511 : ni[i];
ni[i] = ( norm[i] < 0.0 ) ? -ni[i] : ni[i];
}
return (ni[0] & 0x3FF) | ( (ni[1] & 0x3FF) << 10) | ( (ni[2] & 0x3FF) << 20) | ( (0 & 0x3) << 30);
}
void mesh_add_material(mesh *mesh, cgltf_material *mat)
{
if (!mat) return;
if (mat && mat->has_pbr_metallic_roughness) {
cgltf_image *img = mat->pbr_metallic_roughness.base_color_texture.texture->image;
if (img->buffer_view) {
cgltf_buffer_view *buf = img->buffer_view;
mesh->bind.fs.images[0] = texture_fromdata(buf->buffer->data, buf->size)->id;
} else
mesh->bind.fs.images[0] = texture_from_file(img->uri)->id;
} else
mesh->bind.fs.images[0] = texture_from_file("icons/moon.gif")->id;
mesh->bind.fs.samplers[0] = sg_make_sampler(&(sg_sampler_desc){});
/*
cgltf_texture *tex;
if (tex = mat->normal_texture.texture)
mesh->bind.fs.images[1] = texture_from_file(tex->image->uri)->id;
else
mesh->bind.fs.images[1] = texture_from_file("k")->id;*/
}
sg_buffer texcoord_floats(float *f, int verts, int comp)
{
int n = verts*comp;
unsigned short packed[n];
for (int i = 0, v = 0; i < n; i++)
packed[i] = pack_short_tex(f[i]);
return sg_make_buffer(&(sg_buffer_desc){
.data.ptr = packed,
.data.size = sizeof(unsigned short) * verts,
.label = "tex coord vert buffer",
});
}
sg_buffer normal_floats(float *f, int verts, int comp)
{
uint32_t packed_norms[verts];
for (int v = 0, i = 0; v < verts; v++, i+= comp)
packed_norms[v] = pack_int10_n2(f+i);
return sg_make_buffer(&(sg_buffer_desc){
.data.ptr = packed_norms,
.data.size = sizeof(uint32_t) * verts,
.label = "normal vert buffer",
});
}
HMM_Vec3 index_to_vert(uint32_t idx, float *f)
{
return (HMM_Vec3){f[idx*3], f[idx*3+1], f[idx*3+2]};
}
void mesh_add_primitive(mesh *mesh, cgltf_primitive *prim)
{
uint16_t *idxs;
if (prim->indices) {
int c = prim->indices->count;
idxs = malloc(sizeof(*idxs)*c);
memcpy(idxs, cgltf_buffer_view_data(prim->indices->buffer_view), sizeof(uint16_t) * c);
mesh->bind.index_buffer = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = idxs,
.data.size = sizeof(uint16_t) * c,
.type = SG_BUFFERTYPE_INDEXBUFFER,
.label = "mesh index buffer",
});
mesh->idx_count = c;
} else {
YughWarn("Model does not have indices. Generating them.");
int c = prim->attributes[0].data->count;
mesh->idx_count = c;
idxs = malloc(sizeof(*idxs)*c);
for (int z = 0; z < c; z++)
idxs[z] = z;
mesh->bind.index_buffer = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = idxs,
.data.size = sizeof(uint16_t) * c,
.type = SG_BUFFERTYPE_INDEXBUFFER});
}
free(idxs);
mesh_add_material(mesh, prim->material);
int has_norm = 0;
for (int k = 0; k < prim->attributes_count; k++) {
cgltf_attribute attribute = prim->attributes[k];
int n = cgltf_accessor_unpack_floats(attribute.data, NULL, 0); /* floats per vertex x num elements. In other words, total floats pulled */
int comp = cgltf_num_components(attribute.data->type);
int verts = n/comp;
float vs[n];
cgltf_accessor_unpack_floats(attribute.data, vs, n);
switch (attribute.type) {
case cgltf_attribute_type_position:
mesh->bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = vs,
.data.size = sizeof(float) * n,
.label = "mesh vert buffer"
});
break;
case cgltf_attribute_type_normal:
// has_norm = 1;
// mesh->bind.vertex_buffers[2] = normal_floats(vs, verts, comp);
break;
case cgltf_attribute_type_tangent:
break;
case cgltf_attribute_type_color:
break;
case cgltf_attribute_type_weights:
break;
case cgltf_attribute_type_joints:
break;
case cgltf_attribute_type_texcoord:
mesh->bind.vertex_buffers[1] = texcoord_floats(vs, verts, comp);
break;
case cgltf_attribute_type_invalid:
YughWarn("Invalid type.");
break;
case cgltf_attribute_type_custom:
break;
case cgltf_attribute_type_max_enum:
break;
}
}
/*
if (!has_norm) {
cgltf_attribute *pa = get_attr_type(prim, cgltf_attribute_type_position);
int n = cgltf_accessor_unpack_floats(pa->data, NULL,0);
int comp = 3;
int verts = n/comp;
uint32_t face_norms[verts];
float ps[n];
cgltf_accessor_unpack_floats(pa->data,ps,n);
for (int i = 0; i < verts; i+=3) {
HMM_Vec3 a = index_to_vert(i,ps);
HMM_Vec3 b = index_to_vert(i+1,ps);
HMM_Vec3 c = index_to_vert(i+2,ps);
HMM_Vec3 norm = HMM_NormV3(HMM_Cross(HMM_SubV3(b,a), HMM_SubV3(c,a)));
uint32_t packed_norm = pack_int10_n2(norm.Elements);
face_norms[i] = face_norms[i+1] = face_norms[i+2] = packed_norm;
}
mesh->bind.vertex_buffers[2] = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = face_norms,
.data.size = sizeof(uint32_t) * verts});
}*/
}
void model_add_cgltf_mesh(model *model, cgltf_mesh *gltf_mesh)
{
mesh mesh = {0};
for (int i = 0; i < gltf_mesh->primitives_count; i++)
mesh_add_primitive(&mesh, &gltf_mesh->primitives[i]);
arrput(model->meshes,mesh);
}
void model_add_cgltf_anim(model *model, cgltf_animation *anim)
{
}
void model_add_cgltf_skin(model *model, cgltf_skin *skin)
{
}
void model_process_node(model *model, cgltf_node *node)
{
if (node->has_matrix)
memcpy(model->matrix.Elements, node->matrix, sizeof(float)*16);
if (node->mesh)
model_add_cgltf_mesh(model, node->mesh);
if (node->skin)
model_add_cgltf_skin(model, node->skin);
}
void model_process_scene(model *model, cgltf_scene *scene)
{
for (int i = 0; i < scene->nodes_count; i++)
model_process_node(model, scene->nodes[i]);
}
struct model *model_make(const char *path)
{
YughInfo("Making the model from %s.", path);
cgltf_options options = {0};
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse_file(&options, path, &data);
if (result) {
YughError("CGLTF could not parse file %s, err %d.", path, result);
return NULL;
}
result = cgltf_load_buffers(&options, data, path);
if (result) {
YughError("CGLTF could not load buffers for file %s, err %d.", path, result);
return NULL;
}
struct model *model = calloc(1, sizeof(*model));
if (data->scenes_count == 0 || data->scenes_count > 1) return NULL;
model_process_scene(model, data->scene);
for (int i = 0; i < data->meshes_count; i++)
model_add_cgltf_mesh(model, &data->meshes[i]);
for (int i = 0; i < data->animations_count; i++)
model_add_cgltf_anim(model, &data->animations[i]);
return model;
}
void model_free(model *m)
{
}
void model_draw_go(model *model, gameobject *go, gameobject *cam)
{
HMM_Mat4 view = t3d_go2world(cam);
HMM_Mat4 proj = HMM_Perspective_RH_NO(20, 1, 0.01, 10000);
HMM_Mat4 vp = HMM_MulM4(proj, view);
vs_p_t vs_p;
memcpy(vs_p.vp, vp.Elements, sizeof(float)*16);
memcpy(vs_p.model, t3d_go2world(go).Elements, sizeof(float)*16);
sg_apply_pipeline(model_pipe);
sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_p, SG_RANGE_REF(vs_p));
for (int i = 0; i < arrlen(model->meshes); i++) {
sg_apply_bindings(&model->meshes[i].bind);
sg_draw(0, model->meshes[i].idx_count, 1);
}
}
void material_free(material *mat)
{
}

View file

@ -1,45 +0,0 @@
#ifndef MODEL_H
#define MODEL_H
#include "HandmadeMath.h"
#include "transform.h"
#include "sokol/sokol_gfx.h"
#include "gameobject.h"
extern HMM_Vec3 eye;
typedef struct material {
} material;
struct model;
/* A single mesh */
typedef struct mesh {
sg_bindings bind; /* Encapsulates material, norms, etc */
uint32_t idx_count;
} mesh;
/* A collection of meshes which create a full figure */
typedef struct model {
struct mesh *meshes;
HMM_Mat4 matrix;
} model;
typedef struct bone {
transform3d t;
struct bone *children;
} bone;
/* Make a Model struct */
struct model *model_make(const char *path);
void model_free(model *m);
void model_draw_go(model *m, gameobject *go, gameobject *cam);
void model_init();
material *material_make();
void material_free(material *mat);
#endif

1943
source/engine/HandmadeMath.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,36 +2,35 @@
#include "log.h" #include "log.h"
#include "stb_ds.h" #include "stb_ds.h"
void sampler_add(sampler *s, float time, HMM_Vec4 val) void animation_run(struct animation *anim, float now)
{ {
arrput(s->times,time); float elapsed = now - anim->time;
arrput(s->data,val); elapsed = fmod(elapsed,2);
if (!anim->channels) return;
for (int i = 0; i < arrlen(anim->channels); i++) {
struct anim_channel *ch = anim->channels+i;
HMM_Vec4 s = sample_sampler(ch->sampler, elapsed);
*(ch->target) = s;
}
} }
HMM_Vec4 sample_cubicspline(sampler *sampler, float t, int prev, int next) HMM_Vec4 sample_cubicspline(sampler *sampler, float t, int prev, int next)
{ {
float t2 = t*t; return (HMM_Vec4)HMM_SLerp(HMM_QV4(sampler->data[prev]), t, HMM_QV4(sampler->data[next]));
float t3 = t2*t;
float td = sampler->times[next]-sampler->times[prev];
HMM_Vec4 v = HMM_MulV4F(sampler->data[prev*3+1], (2*t3-3*t2+1));
v = HMM_AddV4(v, HMM_MulV4F(sampler->data[prev*3+2], td*(t3-2*t2+t)));
v = HMM_AddV4(v, HMM_MulV4F(sampler->data[next*3+1], 3*t2-2*t3));
v = HMM_AddV4(v, HMM_MulV4F(sampler->data[next*3], td*(t3-t2)));
return v;
} }
HMM_Vec4 sample_sampler(sampler *sampler, float time) HMM_Vec4 sample_sampler(sampler *sampler, float time)
{ {
if (arrlen(sampler->data) == 0) return (HMM_Vec4){0,0,0,0}; if (arrlen(sampler->data) == 0) return v4zero;
if (arrlen(sampler->data) == 1) return sampler->data[0]; if (arrlen(sampler->data) == 1) return sampler->data[0];
int previous_time=0; int previous_time=0;
int next_time=0; int next_time=0;
for (int i = 1; i < arrlen(sampler->times); i++) { for (int i = 1; i < arrlen(sampler->times); i++) {
if (time < sampler->times[i]) { if (time < sampler->times[i]) {
previous_time = sampler->times[i-1]; previous_time = i-1;
next_time = sampler->times[i]; next_time = i;
break; break;
} }
} }

View file

@ -13,12 +13,6 @@ struct keyframe {
#define CUBICSPLINE 2 #define CUBICSPLINE 2
#define SLERP 3 #define SLERP 3
typedef struct samplerf {
float *times;
float *data;
int type;
} samplerf;
typedef struct sampler { typedef struct sampler {
float *times; float *times;
HMM_Vec4 *data; HMM_Vec4 *data;
@ -26,6 +20,8 @@ typedef struct sampler {
} sampler; } sampler;
struct anim_channel { struct anim_channel {
HMM_Vec4 *target;
int comps;
sampler *sampler; sampler *sampler;
}; };
@ -33,10 +29,10 @@ struct animation {
char *name; char *name;
double time; double time;
struct anim_channel *channels; struct anim_channel *channels;
sampler *samplers;
}; };
void sampler_add(sampler *s, float time, HMM_Vec4 val); void animation_run(struct animation *anim, float now);
HMM_Vec4 sample_sampler(sampler *sampler, float time); HMM_Vec4 sample_sampler(sampler *sampler, float time);
#endif #endif

View file

@ -1,4 +1,4 @@
#include "render.h" #include "config.h"
#define SOKOL_TRACE_HOOKS #define SOKOL_TRACE_HOOKS
#define SOKOL_IMPL #define SOKOL_IMPL
@ -7,7 +7,6 @@
#include "sokol/sokol_args.h" #include "sokol/sokol_args.h"
#include "sokol/sokol_gfx.h" #include "sokol/sokol_gfx.h"
#include "sokol/sokol_app.h" #include "sokol/sokol_app.h"
#include "sokol_gfx_ext.h"
#define MSF_GIF_IMPL #define MSF_GIF_IMPL
#include "msf_gif.h" #include "msf_gif.h"
@ -25,11 +24,11 @@
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG #define STBI_FAILURE_USERMSG
#define STBI_NO_STDIO #define STBI_NO_STDIO
#ifdef __TINYC__
#define STBI_NO_SIMD
#endif
#include "stb_image.h" #include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION
#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_BOX #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_BOX
#include "stb_image_write.h" #include "stb_image_write.h"
@ -41,3 +40,15 @@
#define NANOSVGRAST_IMPLEMENTATION #define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg.h" #include "nanosvg.h"
#include "nanosvgrast.h" #include "nanosvgrast.h"
#define CGLTF_IMPLEMENTATION
#include "cgltf.h"
#define PAR_SHAPES_IMPLEMENTATION
#include "par/par_shapes.h"
#define PAR_STREAMLINES_IMPLEMENTATION
#include "par/par_streamlines.h"
#define QOI_IMPLEMENTATION
#include "qoi.h"

View file

@ -4,12 +4,18 @@
#define MAXPATH 256 /* 255 chars + null */ #define MAXPATH 256 /* 255 chars + null */
#define MAXNAME 50 #define MAXNAME 50
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 720
#define PI 3.14159265358979323846264338327950288f #define PI 3.14159265358979323846264338327950288f
#define DEG2RADS 0.0174532925199432957692369076848861271344287188854172545609719144f #define DEG2RADS 0.0174532925199432957692369076848861271344287188854172545609719144f
#define RAD2DEGS 57.2958f #define RAD2DEGS 57.2958f
#if defined __linux__
#define SOKOL_GLCORE
#elif __EMSCRIPTEN__
#define SOKOL_WGPU
#elif __WIN32
#define SOKOL_D3D11
#elif __APPLE__
#define SOKOL_METAL
#endif
#endif #endif

View file

@ -13,15 +13,16 @@
#include "font.h" #include "font.h"
#include "render.h" #include "render.h"
#include "mpeg2.sglsl.h"
#include "cbuf.h" #include "cbuf.h"
#include "sokol/sokol_gfx.h" #include "sokol/sokol_gfx.h"
sg_shader vid_shader; void datastream_free(datastream *ds)
sg_pipeline vid_pipeline; {
sg_bindings vid_bind; sg_destroy_image(ds->img);
plm_destroy(ds->plm);
free(ds);
}
void soundstream_fillbuf(struct datastream *ds, soundbyte *buf, int frames) { void soundstream_fillbuf(struct datastream *ds, soundbyte *buf, int frames) {
for (int i = 0; i < frames*CHANNELS; i++) for (int i = 0; i < frames*CHANNELS; i++)
@ -29,17 +30,14 @@ void soundstream_fillbuf(struct datastream *ds, soundbyte *buf, int frames) {
} }
static void render_frame(plm_t *mpeg, plm_frame_t *frame, struct datastream *ds) { static void render_frame(plm_t *mpeg, plm_frame_t *frame, struct datastream *ds) {
return; if (ds->dirty) return;
uint8_t rgb[frame->height*frame->width*4]; uint8_t rgb[frame->height*frame->width*4];
memset(rgb,255,frame->height*frame->width*4);
plm_frame_to_rgba(frame, rgb, frame->width*4); plm_frame_to_rgba(frame, rgb, frame->width*4);
sg_image_data imgd; sg_image_data imgd = {0};
sg_range ir = { imgd.subimage[0][0] = SG_RANGE(rgb);
.ptr = rgb,
.size = frame->height*frame->width*4*sizeof(uint8_t)
};
imgd.subimage[0][0] = ir;
sg_update_image(ds->img, &imgd); sg_update_image(ds->img, &imgd);
ds->dirty = true;
} }
static void render_audio(plm_t *mpeg, plm_samples_t *samples, struct datastream *ds) { static void render_audio(plm_t *mpeg, plm_samples_t *samples, struct datastream *ds) {
@ -64,19 +62,24 @@ struct datastream *ds_openvideo(const char *path)
path, path,
plm_get_framerate(ds->plm), plm_get_framerate(ds->plm),
plm_get_samplerate(ds->plm), plm_get_samplerate(ds->plm),
plm_get_num_audio_streams(ds->plm), plm_get_num_audio_streams(ds->plm),
plm_get_duration(ds->plm)); plm_get_duration(ds->plm));
ds->img = sg_make_image(&(sg_image_desc){ ds->img = sg_make_image(&(sg_image_desc){
.width = plm_get_width(ds->plm), .width = plm_get_width(ds->plm),
.height = plm_get_height(ds->plm) .height = plm_get_height(ds->plm),
.usage = SG_USAGE_STREAM,
.type = SG_IMAGETYPE_2D,
.pixel_format = SG_PIXELFORMAT_RGBA8,
}); });
plm_set_video_decode_callback(ds->plm, render_frame, ds);
return ds;
ds->ring = ringnew(ds->ring, 8192); ds->ring = ringnew(ds->ring, 8192);
plugin_node(make_node(ds, soundstream_fillbuf, NULL), masterbus); plugin_node(make_node(ds, soundstream_fillbuf, NULL), masterbus);
plm_set_video_decode_callback(ds->plm, render_frame, ds);
plm_set_audio_decode_callback(ds->plm, render_audio, ds); plm_set_audio_decode_callback(ds->plm, render_audio, ds);
plm_set_loop(ds->plm, false); plm_set_loop(ds->plm, false);
@ -86,17 +89,12 @@ struct datastream *ds_openvideo(const char *path)
// Adjust the audio lead time according to the audio_spec buffer size // Adjust the audio lead time according to the audio_spec buffer size
plm_set_audio_lead_time(ds->plm, BUF_FRAMES / SAMPLERATE); plm_set_audio_lead_time(ds->plm, BUF_FRAMES / SAMPLERATE);
ds->playing = true;
return ds; return ds;
} }
void MakeDatastream() {
vid_shader = sg_make_shader(mpeg2_shader_desc(sg_query_backend()));
}
void ds_advance(struct datastream *ds, double s) { void ds_advance(struct datastream *ds, double s) {
if (ds->playing) plm_decode(ds->plm, s); ds->dirty = false;
plm_decode(ds->plm, s);
} }
void ds_seek(struct datastream *ds, double time) { void ds_seek(struct datastream *ds, double time) {
@ -110,31 +108,6 @@ void ds_advanceframes(struct datastream *ds, int frames) {
} }
} }
void ds_pause(struct datastream *ds) {
ds->playing = false;
}
void ds_stop(struct datastream *ds) {
if (ds->plm != NULL) {
plm_destroy(ds->plm);
ds->plm = NULL;
}
ds->playing = false;
}
// TODO: Must be a better way
int ds_videodone(struct datastream *ds) {
return (ds->plm == NULL) || plm_get_time(ds->plm) >= plm_get_duration(ds->plm);
}
double ds_remainingtime(struct datastream *ds) {
if (ds->plm != NULL)
return plm_get_duration(ds->plm) - plm_get_time(ds->plm);
else
return 0.f;
}
double ds_length(struct datastream *ds) { double ds_length(struct datastream *ds) {
return plm_get_duration(ds->plm); return plm_get_duration(ds->plm);
} }

View file

@ -11,17 +11,22 @@ struct soundstream;
struct datastream { struct datastream {
plm_t *plm; plm_t *plm;
double last_time;
int playing;
sg_image img; sg_image img;
sg_image y;
sg_image cr;
sg_image cb;
int width; int width;
int height; int height;
int dirty;
soundbyte *ring; soundbyte *ring;
}; };
typedef struct datastream datastream;
struct texture; struct texture;
void MakeDatastream(); void datastream_free(datastream *ds);
struct datastream *ds_openvideo(const char *path); struct datastream *ds_openvideo(const char *path);
struct texture *ds_maketexture(struct datastream *); struct texture *ds_maketexture(struct datastream *);
void ds_advance(struct datastream *ds, double); void ds_advance(struct datastream *ds, double);

View file

@ -1,656 +0,0 @@
#include "debugdraw.h"
#include "render.h"
#include "yugine.h"
#include "log.h"
#include <assert.h>
#include "window.h"
#include "2dphysics.h"
#include "stb_ds.h"
#include "sokol/sokol_gfx.h"
#include "point.sglsl.h"
#include "poly.sglsl.h"
#include "circle.sglsl.h"
#include "line.sglsl.h"
#include "grid.sglsl.h"
#include "grid3d.sglsl.h"
#define PAR_STREAMLINES_IMPLEMENTATION
#include "par/par_streamlines.h"
#include "font.h"
#define v_amt 500000
struct flush {
sg_shader shader;
sg_pipeline pipe;
sg_bindings bind;
void *verts;
int c;
int v;
int sc;
int sv;
};
typedef struct flush flush;
static flush fpoint;
static flush circle;
void flushview(flush *f, HMM_Mat4 *view)
{
sg_apply_pipeline(f->pipe);
sg_apply_bindings(&f->bind);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(*view));
sg_draw(f->sc, f->c, 1);
}
void flushpass(flush *f)
{
f->sc = f->c;
f->c = 0;
f->sv = f->v;
f->v = 0;
}
static sg_shader point_shader;
static sg_pipeline point_pipe;
static sg_bindings point_bind;
struct point_vertex {
struct draw_p3 pos;
struct rgba color;
float radius;
};
static int point_c = 0;
static int point_sc = 0;
static sg_shader line_shader;
static sg_pipeline line_pipe;
static sg_bindings line_bind;
struct line_vert {
struct draw_p pos;
float dist;
struct rgba color;
float seg_len;
float seg_speed;
};
static int line_c = 0;
static int line_v = 0;
static int line_sc = 0;
static int line_sv = 0;
static sg_pipeline grid_pipe;
static sg_bindings grid_bind;
static sg_shader grid_shader;
static int grid_c = 0;
static sg_pipeline poly_pipe;
static sg_bindings poly_bind;
static sg_shader poly_shader;
static int poly_c = 0;
static int poly_v = 0;
static int poly_sc = 0;
static int poly_sv = 0;
struct poly_vertex {
struct draw_p pos;
float uv[2];
struct rgba color;
};
static sg_pipeline circle_pipe;
static sg_bindings circle_bind;
static sg_shader csg;
static int circle_count = 0;
static int circle_sc = 0;
struct circle_vertex {
struct draw_p pos;
float radius;
struct rgba color;
float segsize;
float fill;
};
static sg_pipeline g3_pipe;
static sg_shader g3_shader;
void debug_nextpass()
{
point_sc = point_c;
point_c = 0;
circle_sc = circle_count;
circle_count = 0;
line_sv = line_v;
line_v = 0;
line_sc = line_c;
line_c = 0;
poly_sc = poly_c;
poly_c = 0;
poly_sv = poly_v;
poly_v = 0;
}
/* Writes debug data to buffers, and draws */
void debug_flush(HMM_Mat4 *view)
{
if (poly_c != 0) {
sg_apply_pipeline(poly_pipe);
sg_apply_bindings(&poly_bind);
sg_apply_uniforms(SG_SHADERSTAGE_VS,0,SG_RANGE_REF(*view));
sg_draw(poly_sc,poly_c,1);
}
if (point_c != 0) {
sg_apply_pipeline(point_pipe);
sg_apply_bindings(&point_bind);
sg_apply_uniforms(SG_SHADERSTAGE_VS,0,SG_RANGE_REF(*view));
sg_draw(point_sc,point_c,1);
}
if (line_c != 0) {
sg_apply_pipeline(line_pipe);
sg_apply_bindings(&line_bind);
sg_apply_uniforms(SG_SHADERSTAGE_VS,0,SG_RANGE_REF(*view));
lfs_params_t lt;
lt.time = apptime();
sg_apply_uniforms(SG_SHADERSTAGE_FS,0,SG_RANGE_REF(lt));
sg_draw(line_sc,line_c,1);
}
if (circle_count != 0) {
sg_apply_pipeline(circle_pipe);
sg_apply_bindings(&circle_bind);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(*view));
sg_draw(circle_sc,4,circle_count);
}
debug_nextpass();
}
void debug_newframe()
{
point_sc = 0;
point_c = 0;
circle_sc = circle_count = line_sv = line_v = line_sc = line_c = poly_sc = poly_c = 0;
poly_sv = poly_v = 0;
}
static sg_shader_uniform_block_desc projection_ubo = {
.size = sizeof(useproj),
.uniforms = {
[0] = { .name = "proj", .type = SG_UNIFORMTYPE_MAT4 },
}
};
static sg_shader_uniform_block_desc time_ubo = {
.size = sizeof(float),
.uniforms = {
[0] = { .name = "time", .type = SG_UNIFORMTYPE_FLOAT },
}
};
void debugdraw_init()
{
/*
g3_shader = sg_make_shader(grid3d_shader_desc(sg_query_backend()));
g3_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = g3_shader,
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
.index_type = SG_INDEXTYPE_UINT32
});
*/
point_shader = sg_make_shader(point_shader_desc(sg_query_backend()));
point_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = point_shader,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2, /* pos */
[1].format = SG_VERTEXFORMAT_UBYTE4N, /* color */
[2].format = SG_VERTEXFORMAT_FLOAT /* radius */
}
},
.primitive_type = SG_PRIMITIVETYPE_POINTS,
.colors[0].blend = blend_trans,
.label = "dbg point",
});
point_bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct point_vertex)*v_amt,
.usage = SG_USAGE_STREAM,
.label = "point vertex buffer"
});
line_shader = sg_make_shader(line_shader_desc(sg_query_backend()));
line_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = line_shader,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT3, /* pos */
[1].format = SG_VERTEXFORMAT_FLOAT, /* dist */
[2].format = SG_VERTEXFORMAT_UBYTE4N, /* color */
[3].format = SG_VERTEXFORMAT_FLOAT, /* seg length */
[4].format = SG_VERTEXFORMAT_FLOAT /* dashed line speed */
}
},
.primitive_type = SG_PRIMITIVETYPE_LINES,
.index_type = SG_INDEXTYPE_UINT16,
.colors[0].blend = blend_trans,
.label = "dbg line",
});
line_bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct line_vert)*v_amt,
.usage = SG_USAGE_STREAM,
.label = "line vertex buffer",
});
line_bind.index_buffer = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(uint16_t)*v_amt,
.usage = SG_USAGE_STREAM,
.type = SG_BUFFERTYPE_INDEXBUFFER,
.label = "line index buffer",
});
csg = sg_make_shader(circle_shader_desc(sg_query_backend()));
circle_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = csg,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
[0].buffer_index = 1,
[1].format = SG_VERTEXFORMAT_FLOAT2,
[2].format = SG_VERTEXFORMAT_FLOAT,
[3].format = SG_VERTEXFORMAT_UBYTE4N,
[4].format = SG_VERTEXFORMAT_FLOAT,
[5].format = SG_VERTEXFORMAT_FLOAT
},
.buffers[0].step_func = SG_VERTEXSTEP_PER_INSTANCE,
},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
.cull_mode = SG_CULLMODE_BACK,
.colors[0].blend = blend_trans,
.label = "circle pipeline"
});
circle_bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct circle_vertex)*v_amt,
.usage = SG_USAGE_STREAM,
.label = "circle vert buffer",
});
float circleverts[8] = {
-1,-1,
-1,1,
1,-1,
1,1
};
circle_bind.vertex_buffers[1] = sg_make_buffer(&(sg_buffer_desc){
.data = (sg_range){.ptr = circleverts, .size = sizeof(float)*8},
.usage = SG_USAGE_IMMUTABLE,
.label = "circle quarter buffer",
});
grid_shader = sg_make_shader(grid_shader_desc(sg_query_backend()));
grid_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = grid_shader,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2, /* pos */
}
},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
.cull_mode = SG_CULLMODE_BACK,
.label = "grid pipeline",
.colors[0].blend = blend_trans,
});
grid_bind.vertex_buffers[0] = circle_bind.vertex_buffers[1];
poly_shader = sg_make_shader(poly_shader_desc(sg_query_backend()));
poly_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = poly_shader,
.layout = {
.attrs = { [0].format = SG_VERTEXFORMAT_FLOAT2, /* pos */
[1].format = SG_VERTEXFORMAT_FLOAT2, /* uv */
[2].format = SG_VERTEXFORMAT_UBYTE4N /* color rgba */
}
},
.index_type = SG_INDEXTYPE_UINT32,
.colors[0].blend = blend_trans,
// .cull_mode = SG_CULLMODE_FRONT,
.label = "dbg poly",
});
poly_bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct poly_vertex)*v_amt,
.usage = SG_USAGE_STREAM,
.type = SG_BUFFERTYPE_VERTEXBUFFER,
.label = "poly vert buffer",
});
poly_bind.index_buffer = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(uint32_t)*6*v_amt,
.usage = SG_USAGE_STREAM,
.type = SG_BUFFERTYPE_INDEXBUFFER,
.label = "poly index buffer"
});
}
void draw_line3d(HMM_Vec3 *points, int n, struct rgba color, float seg_len, float seg_speed)
{
if (n < 2) return;
seg_speed = 1;
struct line_vert v[n];
float dist = 0;
for (int i = 0; i < n; i++) {
v[i].pos.x = points[i].x;
v[i].pos.y = points[i].y;
v[i].color = color;
v[i].seg_len = seg_len;
v[i].seg_speed = seg_speed;
}
v[0].dist = 0;
for (int i = 1; i < n; i++) {
dist += HMM_DistV3(points[i-1], points[i]);
v[i].dist = dist;
}
int i_c = (n-1)*2;
uint16_t idxs[i_c];
for (int i = 0, d = 0; i < n-1; i++, d+=2) {
idxs[d] = i + line_v + line_sv;
idxs[d+1] = idxs[d]+1;
}
sg_range vr = {
.ptr = v,
.size = sizeof(struct line_vert)*n
};
sg_range ir = {
.ptr = idxs,
.size = sizeof(uint16_t)*i_c
};
if (sg_query_buffer_will_overflow(line_bind.vertex_buffers[0], vr.size) || sg_query_buffer_will_overflow(line_bind.index_buffer, ir.size)) return;
sg_append_buffer(line_bind.vertex_buffers[0], &vr);
sg_append_buffer(line_bind.index_buffer, &ir);
line_c += i_c;
line_v += n;
}
void draw_line(HMM_Vec2 *points, int n, struct rgba color, float seg_len, float seg_speed)
{
if (n < 2) return;
HMM_Vec3 points3[n];
for (int i = 0; i < n; i++)
points3[i].xy = points[i];
draw_line3d(points3, n, color, seg_len, seg_speed);
}
HMM_Vec2 center_of_vects(HMM_Vec2 *v, int n)
{
HMM_Vec2 c;
for (int i = 0; i < n; i++) {
c.x += v[i].x;
c.y += v[i].y;
}
c.x /= n;
c.y /= n;
return c;
}
/* Given a series of points p, computes a new series with them expanded on either side by d */
HMM_Vec2 *inflatepoints(HMM_Vec2 *p, float d, int n)
{
if (d == 0) {
HMM_Vec2 *ret = NULL;
arraddn(ret,n);
for (int i = 0; i < n; i++)
ret[i] = p[i];
return ret;
}
parsl_position par_v[n];
uint16_t spine_lens[] = {n};
for (int i = 0; i < n; i++) {
par_v[i].x = p[i].x;
par_v[i].y = p[i].y;
};
parsl_context *par_ctx = parsl_create_context((parsl_config){
.thickness = d,
.flags= PARSL_FLAG_ANNOTATIONS,
.u_mode = PAR_U_MODE_DISTANCE
});
parsl_mesh *mesh = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){
.num_vertices = n,
.num_spines = 1,
.vertices = par_v,
.spine_lengths = spine_lens,
.closed = 0,
});
HMM_Vec2 *ret = NULL;
arraddn(ret,mesh->num_vertices);
for (int i = 0; i < mesh->num_vertices; i++) {
ret[i].x = mesh->positions[i].x;
ret[i].y = mesh->positions[i].y;
};
return ret;
}
/* Given a strip of points, draws them as segments. So 5 points is 4 segments, and ultimately 8 vertices */
void draw_edge(HMM_Vec2 *points, int n, struct rgba color, float thickness, int flags, struct rgba line_color, float line_seg)
{
int closed = 0;
if (thickness <= 0) {
draw_line(points,n,line_color,0,0);
return;
}
/* todo: should be dashed, and filled. use a texture. */
/* draw polygon outline */
if (HMM_EqV2(points[0], points[n-1])) {
closed = true;
n--;
}
parsl_position par_v[n];
for (int i = 0; i < n; i++) {
par_v[i].x = points[i].x;
par_v[i].y = points[i].y;
}
uint16_t spine_lens[] = {n};
parsl_context *par_ctx = parsl_create_context((parsl_config){
.thickness = thickness,
.flags = PARSL_FLAG_ANNOTATIONS,
});
parsl_mesh *mesh = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){
.num_vertices = n,
.num_spines = 1,
.vertices = par_v,
.spine_lengths = spine_lens,
.closed = closed
});
for (int i = 0; i < mesh->num_triangles*3; i++)
mesh->triangle_indices[i] += (poly_v+poly_sv);
struct poly_vertex vertices[mesh->num_vertices];
for (int i = 0; i < mesh->num_vertices; i++) {
vertices[i].pos = (struct draw_p){ .x = mesh->positions[i].x, .y = mesh->positions[i].y };
vertices[i].uv[0] = mesh->annotations[i].u_along_curve;
vertices[i].uv[1] = mesh->annotations[i].v_across_curve;
vertices[i].color = color;
}
sg_append_buffer(poly_bind.vertex_buffers[0], &(sg_range){.ptr = vertices, .size = sizeof(struct poly_vertex)*mesh->num_vertices});
sg_append_buffer(poly_bind.index_buffer, &(sg_range){.ptr = mesh->triangle_indices, sizeof(uint32_t)*mesh->num_triangles*3});
poly_c += mesh->num_triangles*3;
poly_v += mesh->num_vertices;
parsl_destroy_context(par_ctx);
/* Now drawing the line outlines */
if (thickness == 1) {
draw_line(points,n,line_color,line_seg, 0);
} else {
HMM_Vec2 in_p[n];
HMM_Vec2 out_p[n];
for (int i = 1, v = 0; i < n*2+1; i+=2, v++) {
in_p[v].x = vertices[i].pos.x;
in_p[v].y = vertices[i].pos.y;
}
for (int i = 0, v = 0; i < n*2; i+=2,v++) {
out_p[v].x = vertices[i].pos.x;
out_p[v].y = vertices[i].pos.y;
}
if (!closed) {
HMM_Vec2 p[n*2];
for (int i = 0; i < n; i++)
p[i] = in_p[i];
for (int i = n-1, v = n; i >= 0; i--,v++)
p[v] = out_p[i];
draw_line(p,n*2,line_color,line_seg,0);
return;
}
draw_line(in_p,n,line_color,line_seg,0);
draw_line(out_p,n,line_color,line_seg,0);
}
}
void draw_circle(HMM_Vec2 pos, float radius, float pixels, struct rgba color, float seg)
{
struct circle_vertex cv;
cv.pos.x = pos.x;
cv.pos.y = pos.y;
cv.radius = radius;
cv.color = color;
cv.segsize = seg/radius;
cv.fill = pixels/radius;
sg_append_buffer(circle_bind.vertex_buffers[0], &(sg_range){.ptr = &cv, .size = sizeof(struct circle_vertex)});
circle_count++;
}
void draw_box(HMM_Vec2 c, HMM_Vec2 wh, struct rgba color)
{
float hw = wh.x / 2.f;
float hh = wh.y / 2.f;
HMM_Vec2 verts[4] = {
{ .x = c.x-hw, .y = c.y-hh },
{ .x = c.x+hw, .y = c.y-hh },
{ .x = c.x+hw, .y = c.y+hh },
{ .x = c.x-hw, .y = c.y+hh }
};
draw_poly(verts, 4, color);
}
void draw_grid(float width, float span, struct rgba color)
{
HMM_Vec2 offset = campos;
offset = HMM_MulV2F(offset, 1/camzoom);
float ubo[4];
ubo[0] = offset.x;
ubo[1] = offset.y;
ubo[2] = mainwin.size.x;
ubo[3] = mainwin.size.y;
sg_apply_pipeline(grid_pipe);
sg_apply_bindings(&grid_bind);
float col[4];
rgba2floats(col,color);
fs_params_t pt;
pt.thickness = (float)width;
pt.span = span/camzoom;
memcpy(&pt.color, col, sizeof(float)*4);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(ubo));
sg_apply_uniforms(SG_SHADERSTAGE_FS, 0, SG_RANGE_REF(pt));
sg_draw(0,4,1);
}
void draw_cppoint(HMM_Vec2 point, float r, struct rgba color)
{
struct point_vertex p = {
.color = color,
.radius = r
};
p.pos.x = point.X;
p.pos.y = point.Y;
sg_append_buffer(point_bind.vertex_buffers[0], &(sg_range){.ptr = &p, .size = sizeof(struct point_vertex)});
point_c++;
}
void draw_points(HMM_Vec2 *points, int n, float size, struct rgba color)
{
for (int i = 0; i < n; i++)
draw_cppoint(points[i], size, color);
}
void draw_poly(HMM_Vec2 *points, int n, struct rgba color)
{
/* Find polygon mesh */
int tric = n - 2;
if (tric < 1) return;
uint32_t tridxs[tric*3];
for (int i = 2, ti = 0; i < n; i++, ti+=3) {
tridxs[ti] = 0;
tridxs[ti+1] = i-1;
tridxs[ti+2] = i;
}
for (int i = 0; i < tric*3; i++)
tridxs[i] += poly_v+poly_sv;
struct poly_vertex polyverts[n];
for (int i = 0; i < n; i++) {
polyverts[i].pos = (struct draw_p) { .x = points[i].x, .y = points[i].y};
polyverts[i].uv[0] = 0.0;
polyverts[i].uv[1] = 0.0;
polyverts[i].color = color;
}
sg_append_buffer(poly_bind.vertex_buffers[0], &(sg_range){.ptr = polyverts, .size = sizeof(struct poly_vertex)*n});
sg_append_buffer(poly_bind.index_buffer, &(sg_range){.ptr = tridxs, sizeof(uint32_t)*3*tric});
poly_c += tric*3;
poly_v += n;
}

View file

@ -1,27 +0,0 @@
#ifndef DEBUGDRAW_H
#define DEBUGDRAW_H
#include "HandmadeMath.h"
struct rgba;
void debugdraw_init();
void draw_cppoint(HMM_Vec2 point, float r, struct rgba color);
void draw_points(HMM_Vec2 *points, int n, float size, struct rgba color);
void draw_line(HMM_Vec2 *points, int n, struct rgba color, float seg_len, float seg_speed);
void draw_line3d(HMM_Vec3 *points, int n, struct rgba color, float seg_len, float seg_speed);
void draw_edge(HMM_Vec2 *points, int n, struct rgba color, float thickness, int flags, struct rgba line_color, float line_seg);
/* pixels - how many pixels thick, segsize - dashed line seg len */
void draw_circle(HMM_Vec2 c, float radius, float pixels, struct rgba color, float seg);
void draw_box(HMM_Vec2 c, HMM_Vec2 wh, struct rgba color);
void draw_poly(HMM_Vec2 *points, int n, struct rgba color);
void draw_grid(float width, float span, struct rgba color);
void debug_flush(HMM_Mat4 *view);
void debug_newframe();
HMM_Vec2 *inflatepoints(HMM_Vec2 *p, float d, int n);
#endif

View file

@ -10,8 +10,6 @@
#include <string.h> #include <string.h>
#include <window.h> #include <window.h>
#include "resources.h" #include "resources.h"
#include "debugdraw.h"
#include "text.sglsl.h"
#include "render.h" #include "render.h"
#include "stb_image_write.h" #include "stb_image_write.h"
@ -22,64 +20,25 @@
struct sFont *use_font; struct sFont *use_font;
#define max_chars 100000 sg_buffer text_ssbo;
static sg_shader fontshader;
static sg_bindings bind_text;
static sg_pipeline pipe_text;
struct text_vert { struct text_vert {
struct draw_p pos; HMM_Vec2 pos;
struct draw_p wh; HMM_Vec2 wh;
struct uv_n uv; HMM_Vec2 uv;
struct uv_n st; HMM_Vec2 st;
struct rgba color; HMM_Vec4 color;
}; };
static struct text_vert *text_buffer; static struct text_vert *text_buffer;
void font_init() { void font_init() {
text_buffer = malloc(sizeof(*text_buffer)*max_chars); text_ssbo = sg_make_buffer(&(sg_buffer_desc){
fontshader = sg_make_shader(text_shader_desc(sg_query_backend())); .size = sizeof(struct text_vert),
pipe_text = sg_make_pipeline(&(sg_pipeline_desc){ .type = SG_BUFFERTYPE_STORAGEBUFFER,
.shader = fontshader, .usage = SG_USAGE_STREAM,
.layout = { .label = "text buffer"
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2, /* verts */
[0].buffer_index = 1,
[1].format = SG_VERTEXFORMAT_FLOAT2, /* pos */
[2].format = SG_VERTEXFORMAT_FLOAT2, /* width and height */
[3].format = SG_VERTEXFORMAT_USHORT2N, /* uv pos */
[4].format = SG_VERTEXFORMAT_USHORT2N, /* uv width and height */
[5].format = SG_VERTEXFORMAT_UBYTE4N, /* color */
},
.buffers[0].step_func = SG_VERTEXSTEP_PER_INSTANCE
},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
.colors[0].blend = blend_trans,
.label = "text",
});
float text_verts[8] = {
0,0,
0,1,
1,0,
1,1
};
bind_text.vertex_buffers[1] = sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(text_verts),
.usage = SG_USAGE_IMMUTABLE,
.label = "text rectangle buffer",
}); });
bind_text.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct text_vert)*max_chars,
.type = SG_BUFFERTYPE_VERTEXBUFFER,
.usage = SG_USAGE_STREAM,
.label = "text buffer"
});
bind_text.fs.samplers[0] = sg_make_sampler(&(sg_sampler_desc){});
} }
void font_free(font *f) void font_free(font *f)
@ -91,7 +50,6 @@ void font_free(font *f)
void font_set(font *f) void font_set(font *f)
{ {
use_font = f; use_font = f;
bind_text.fs.images[0] = f->texID;
} }
struct sFont *MakeSDFFont(const char *fontfile, int height) struct sFont *MakeSDFFont(const char *fontfile, int height)
@ -154,7 +112,8 @@ struct sFont *MakeFont(const char *fontfile, int height) {
newfont->emscale = stbtt_ScaleForPixelHeight(&fontinfo, height); newfont->emscale = stbtt_ScaleForPixelHeight(&fontinfo, height);
newfont->linegap = (newfont->ascent - newfont->descent) * newfont->emscale*1.5; newfont->linegap = (newfont->ascent - newfont->descent) * newfont->emscale*1.5;
newfont->texID = sg_make_image(&(sg_image_desc){ newfont->texture = malloc(sizeof(texture));
newfont->texture->id = sg_make_image(&(sg_image_desc){
.type = SG_IMAGETYPE_2D, .type = SG_IMAGETYPE_2D,
.width = packsize, .width = packsize,
.height = packsize, .height = packsize,
@ -166,6 +125,9 @@ struct sFont *MakeFont(const char *fontfile, int height) {
} }
}); });
newfont->texture->width = packsize;
newfont->texture->height = packsize;
for (unsigned char c = 32; c < 127; c++) { for (unsigned char c = 32; c < 127; c++) {
stbtt_packedchar glyph = glyphs[c - 32]; stbtt_packedchar glyph = glyphs[c - 32];
@ -192,8 +154,6 @@ struct sFont *MakeFont(const char *fontfile, int height) {
return newfont; return newfont;
} }
static int curchar = 0;
void draw_underline_cursor(HMM_Vec2 pos, float scale, struct rgba color) void draw_underline_cursor(HMM_Vec2 pos, float scale, struct rgba color)
{ {
pos.Y -= 2; pos.Y -= 2;
@ -213,29 +173,31 @@ void draw_char_box(struct Character c, HMM_Vec2 cursor, float scale, struct rgba
HMM_Vec2 b; HMM_Vec2 b;
b.x = cursor.X + wh.x/2; b.x = cursor.X + wh.x/2;
b.y = cursor.Y + wh.y/2; b.y = cursor.Y + wh.y/2;
draw_box(b, wh, color);
} }
void text_flush(HMM_Mat4 *proj) { int text_flush() {
if (curchar == 0) return; if (arrlen(text_buffer) == 0) return 0;
sg_range verts; sg_range verts;
verts.ptr = text_buffer; verts.ptr = text_buffer;
verts.size = sizeof(struct text_vert) * curchar; verts.size = sizeof(struct text_vert) * arrlen(text_buffer);
int offset = sg_append_buffer(bind_text.vertex_buffers[0], &verts); if (sg_query_buffer_will_overflow(text_ssbo, verts.size)) {
sg_destroy_buffer(text_ssbo);
text_ssbo = sg_make_buffer(&(sg_buffer_desc){
.size = verts.size,
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_STREAM,
.label = "text buffer"
});
}
bind_text.vertex_buffer_offsets[0] = offset; sg_append_buffer(text_ssbo, &verts);
sg_apply_pipeline(pipe_text); int n = arrlen(text_buffer);
sg_apply_bindings(&bind_text); arrsetlen(text_buffer, 0);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(*proj)); return n;
sg_draw(0, 4, curchar);
curchar = 0;
} }
void sdrawCharacter(struct Character c, HMM_Vec2 cursor, float scale, struct rgba color) { void sdrawCharacter(struct Character c, HMM_Vec2 cursor, float scale, struct rgba color) {
if (curchar-10 >= max_chars)
return;
struct rgba colorbox = {0,0,0,255}; struct rgba colorbox = {0,0,0,255};
struct text_vert vert; struct text_vert vert;
@ -249,14 +211,13 @@ void sdrawCharacter(struct Character c, HMM_Vec2 cursor, float scale, struct rgb
// if (vert.pos.x > frame.l || vert.pos.y > frame.t || (vert.pos.y + vert.wh.y) < frame.b || (vert.pos.x + vert.wh.x) < frame.l) return; // if (vert.pos.x > frame.l || vert.pos.y > frame.t || (vert.pos.y + vert.wh.y) < frame.b || (vert.pos.x + vert.wh.x) < frame.l) return;
vert.uv.u = c.rect.x*USHRT_MAX; vert.uv.x = c.rect.x;
vert.uv.v = c.rect.y*USHRT_MAX; vert.uv.y = c.rect.y;
vert.st.u = c.rect.w*USHRT_MAX; vert.st.x = c.rect.w;
vert.st.v = c.rect.h*USHRT_MAX; vert.st.y = c.rect.h;
vert.color = color; rgba2floats(vert.color.e, color);
memcpy(text_buffer + curchar, &vert, sizeof(struct text_vert)); arrput(text_buffer, vert);
curchar++;
} }
const char *esc_color(const char *c, struct rgba *color, struct rgba defc) const char *esc_color(const char *c, struct rgba *color, struct rgba defc)
@ -389,7 +350,7 @@ int renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, f
if (*wordstart == '\e') if (*wordstart == '\e')
wordstart = esc_color(wordstart, &usecolor, color); wordstart = esc_color(wordstart, &usecolor, color);
sdrawCharacter(use_font->Characters[*wordstart], HMM_AddV2(cursor, HMM_MulV2F((HMM_Vec2){1,-1},scale)), scale, (rgba){0,0,0,255}); //sdrawCharacter(use_font->Characters[*wordstart], HMM_AddV2(cursor, HMM_MulV2F((HMM_Vec2){1,-1},scale)), scale, (rgba){0,0,0,255});
sdrawCharacter(use_font->Characters[*wordstart], cursor, scale, usecolor); sdrawCharacter(use_font->Characters[*wordstart], cursor, scale, usecolor);
cursor.X += use_font->Characters[*wordstart].Advance * tracking * scale; cursor.X += use_font->Characters[*wordstart].Advance * tracking * scale;

View file

@ -8,6 +8,8 @@
struct shader; struct shader;
struct window; struct window;
extern sg_buffer text_ssbo;
/// Holds all state information relevant to a character as loaded using FreeType /// Holds all state information relevant to a character as loaded using FreeType
struct Character { struct Character {
float Size[2]; // Size of glyph float Size[2]; // Size of glyph
@ -26,6 +28,7 @@ struct sFont {
float emscale; float emscale;
struct Character Characters[256]; struct Character Characters[256];
sg_image texID; sg_image texID;
texture *texture;
}; };
typedef struct sFont font; typedef struct sFont font;
@ -40,7 +43,6 @@ void text_settype(struct sFont *font);
struct boundingbox text_bb(const char *text, float scale, float lw, float tracking); struct boundingbox text_bb(const char *text, float scale, float lw, float tracking);
int renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, float lw, int caret, float tracking); int renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, float lw, int caret, float tracking);
// void text_frame(); int text_flush();
void text_flush(HMM_Mat4 *proj);
#endif #endif

View file

@ -2,105 +2,37 @@
#include "2dphysics.h" #include "2dphysics.h"
#include <string.h> #include <string.h>
#include "debugdraw.h"
#include "log.h" #include "log.h"
#include "math.h" #include "math.h"
#include "stb_ds.h" #include "stb_ds.h"
gameobject *body2go(cpBody *body) { return cpBodyGetUserData(body); } transform go2t(gameobject *go)
gameobject *shape2go(cpShape *shape) {
struct phys2d_shape *pshape = cpShapeGetUserData(shape);
if (!pshape) return NULL;
return pshape->go;
}
HMM_Vec2 go_pos(gameobject *go)
{ {
cpVect p = cpBodyGetPosition(go->body); transform t = {0};
return (HMM_Vec2){p.x, p.y};
}
float go_angle(gameobject *go) { return cpBodyGetAngle(go->body); }
transform3d go2t3(gameobject *go)
{
transform3d t;
HMM_Vec2 p = go_pos(go);
t.pos.X = p.X;
t.pos.Y = p.Y;
t.pos.Z = go->drawlayer;
t.scale = go->scale;
t.scale.Z = go->scale.X;
t.rotation = go->quat;
return t;
}
HMM_Vec2 go2world(gameobject *go, HMM_Vec2 pos) { return mat_t_pos(t_go2world(go), pos); }
HMM_Vec2 world2go(gameobject *go, HMM_Vec2 pos) { return mat_t_pos(t_world2go(go), pos); }
HMM_Mat3 t_go2world(gameobject *go) { return transform2d2mat(go2t(go)); }
HMM_Mat3 t_world2go(gameobject *go) { return HMM_InvGeneralM3(t_go2world(go)); }
HMM_Mat4 t3d_go2world(gameobject *go) { return transform3d2mat(go2t3(go)); }
HMM_Mat4 t3d_world2go(gameobject *go) { return HMM_InvGeneralM4(t3d_go2world(go)); }
transform2d go2t(gameobject *go)
{
transform2d t;
t.pos.cp = cpBodyGetPosition(go->body); t.pos.cp = cpBodyGetPosition(go->body);
t.angle = cpBodyGetAngle(go->body); t.rotation = angle2rotation(cpBodyGetAngle(go->body));
t.scale = go->scale.XY; t.scale = go->t->scale;
if (!isfinite(t.scale.X)) t.scale.X = 1;
if (!isfinite(t.scale.Y)) t.scale.Y = 1;
return t; return t;
} }
void go_shape_apply(cpBody *body, cpShape *shape, gameobject *go) { gameobject *body2go(cpBody *b)
cpShapeSetFriction(shape, go->friction); {
cpShapeSetElasticity(shape, go->elasticity); return cpBodyGetUserData(b);
cpShapeSetCollisionType(shape, (cpCollisionType)go);
cpShapeFilter filter;
filter.group = (cpCollisionType)go;
filter.categories = go->categories;
filter.mask = go->mask;
// filter.mask = CP_ALL_CATEGORIES;
cpShapeSetFilter(shape, filter);
struct phys2d_shape *ape = cpShapeGetUserData(shape);
if (ape && ape->apply)
ape->apply(ape->data);
} }
void go_shape_moi(cpBody *body, cpShape *shape, gameobject *go) { gameobject *shape2go(cpShape *s)
float moment = cpBodyGetMoment(body); {
struct phys2d_shape *s = cpShapeGetUserData(shape); cpBody *b = cpShapeGetBody(s);
if (!s) { return cpBodyGetUserData(b);
cpBodySetMoment(body, moment + 1);
return;
}
moment += s->moi(s->data);
if (moment < 0) moment = 0;
cpBodySetMoment(body, moment);
} }
void gameobject_apply(gameobject *go) { void gameobject_apply(gameobject *go) { *go->t = go2t(go); }
YughSpam("Applying gameobject %p", go);
cpBodySetType(go->body, go->phys);
cpBodyEachShape(go->body, go_shape_apply, go);
if (go->phys == CP_BODY_TYPE_DYNAMIC) {
cpBodySetMass(go->body, go->mass);
cpBodySetMoment(go->body, 0.f);
cpBodyEachShape(go->body, go_shape_moi, go);
if (cpBodyGetMoment(go->body) <= 0.f)
cpBodySetMoment(go->body, 1.f);
}
}
static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt) static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt)
{ {
gameobject *go = body2go(body); gameobject *go = body2go(body);
gameobject_apply(go);
cpVect pos = cpBodyGetPosition(body); cpVect pos = cpBodyGetPosition(body);
HMM_Vec2 g = warp_force((HMM_Vec3){pos.x, pos.y, 0}, go->warp_mask).xy; HMM_Vec2 g = warp_force((HMM_Vec3){pos.x, pos.y, 0}, go->warp_mask).xy;
if (!go) { if (!go) {
@ -126,23 +58,15 @@ static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt
gameobject *MakeGameobject() { gameobject *MakeGameobject() {
gameobject *ngo = malloc(sizeof(*ngo)); gameobject *ngo = malloc(sizeof(*ngo));
gameobject go = { gameobject go = {
.scale = (HMM_Vec3){1.f,1.f,1.f},
.phys = CP_BODY_TYPE_STATIC,
.maxvelocity = INFINITY, .maxvelocity = INFINITY,
.maxangularvelocity = INFINITY, .maxangularvelocity = INFINITY,
.mass = 1.f,
.next = -1,
.drawlayer = 0,
.damping = INFINITY, .damping = INFINITY,
.timescale = 1.0, .timescale = 1.0,
.ref = JS_UNDEFINED, .ref = JS_UNDEFINED,
.mask = ~0,
.categories = 1,
.warp_mask = ~0, .warp_mask = ~0,
.quat = HMM_QFromAxisAngle_RH((HMM_Vec3){0,1,0}, 0)
}; };
go.body = cpSpaceAddBody(space, cpBodyNew(go.mass, 1.f)); go.body = cpSpaceAddBody(space, cpBodyNew(1, 1));
cpBodySetVelocityUpdateFunc(go.body, velocityFn); cpBodySetVelocityUpdateFunc(go.body, velocityFn);
*ngo = go; *ngo = go;
@ -152,26 +76,12 @@ gameobject *MakeGameobject() {
} }
void rm_body_shapes(cpBody *body, cpShape *shape, void *data) { void rm_body_shapes(cpBody *body, cpShape *shape, void *data) {
struct phys2d_shape *s = cpShapeGetUserData(shape);
if (s) {
JS_FreeValue(js, s->ref);
s->ref = JS_UNDEFINED;
if (s->free)
s->free(s->data);
else
free(s->data);
}
cpShapeSetFilter(shape, CP_SHAPE_FILTER_NONE);
cpSpaceRemoveShape(space, shape); cpSpaceRemoveShape(space, shape);
cpShapeFree(shape);
} }
void rm_body_constraints(cpBody *body, cpConstraint *constraint, void *data) void rm_body_constraints(cpBody *body, cpConstraint *c, void *data)
{ {
constraint_break(cpConstraintGetUserData(constraint)); cpSpaceRemoveConstraint(space, c);
} }
void gameobject_free(gameobject *go) { void gameobject_free(gameobject *go) {
@ -182,31 +92,3 @@ void gameobject_free(gameobject *go) {
cpBodyFree(go->body); cpBodyFree(go->body);
free(go); free(go);
} }
void gameobject_setangle(gameobject *go, float angle) {
cpBodySetAngle(go->body, angle);
phys2d_reindex_body(go->body);
}
void gameobject_setpos(gameobject *go, cpVect vec) {
if (!go || !go->body) return;
cpBodySetPosition(go->body, vec);
phys2d_reindex_body(go->body);
}
void body_draw_shapes_dbg(cpBody *body, cpShape *shape, void *data) {
struct phys2d_shape *s = cpShapeGetUserData(shape);
s->debugdraw(s->data);
}
HMM_Vec3 go_pos3d(gameobject *go)
{
HMM_Vec2 pos2d = go_pos(go);
return (HMM_Vec3){pos2d.x, pos2d.y, go->drawlayer};
}
void gameobject_draw_debug(gameobject *go) {
if (!go || !go->body) return;
cpBodyEachShape(go->body, body_draw_shapes_dbg, NULL);
}

View file

@ -27,25 +27,15 @@
}while(0) }while(0)
struct gameobject { struct gameobject {
cpBodyType phys;
cpBody *body; /* NULL if this object is dead; has 2d position and rotation, relative to global 0 */ cpBody *body; /* NULL if this object is dead; has 2d position and rotation, relative to global 0 */
HMM_Vec3 scale; /* local */
HMM_Quat quat;
int next;
float mass;
float friction;
float elasticity;
float damping; float damping;
float timescale; float timescale;
float maxvelocity; float maxvelocity;
float maxangularvelocity; float maxangularvelocity;
unsigned int layer; unsigned int layer;
cpBitmask categories;
cpBitmask mask;
unsigned int warp_mask; unsigned int warp_mask;
JSValue ref; JSValue ref;
HMM_Mat4 world; transform *t; // the transform this body controls
float drawlayer;
}; };
/* /*
@ -68,31 +58,13 @@ typedef struct gameobject gameobject;
gameobject *MakeGameobject(); gameobject *MakeGameobject();
void gameobject_apply(gameobject *go); void gameobject_apply(gameobject *go);
void gameobject_free(gameobject *go); void gameobject_free(gameobject *go);
transform go2t(gameobject *go);
transform2d go2t(gameobject *go); HMM_Vec3 go_pos(gameobject *go);
transform3d go2t3(gameobject *go);
HMM_Vec2 go2world(gameobject *go, HMM_Vec2 pos); gameobject *shape2go(cpShape *s);
HMM_Vec2 world2go(gameobject *go, HMM_Vec2 pos); gameobject *body2go(cpBody *b);
HMM_Mat3 t_go2world(gameobject *go);
HMM_Mat3 t_world2go(gameobject *go);
HMM_Mat4 t3d_go2world(gameobject *go);
HMM_Mat4 t3d_world2go(gameobject *go);
HMM_Vec3 go_pos3d(gameobject *go);
HMM_Vec2 go_pos(gameobject *go);
void gameobject_setpos(gameobject *go, cpVect vec);
float go_angle(gameobject *go);
void gameobject_setangle(gameobject *go, float angle);
gameobject *body2go(cpBody *body);
gameobject *shape2go(cpShape *shape);
void go_shape_apply(cpBody *body, cpShape *shape, gameobject *go); void go_shape_apply(cpBody *body, cpShape *shape, gameobject *go);
/* Tries a few methods to select a gameobject; if none is selected returns -1 */
void gameobject_draw_debug(gameobject *go);
#endif #endif

106
source/engine/gui.cpp Normal file
View file

@ -0,0 +1,106 @@
#include "gui.h"
#include "render.h"
#include "sokol/sokol_app.h"
#include "imgui.h"
#define SOKOL_IMPL
#include "sokol/util/sokol_imgui.h"
#include "sokol/util/sokol_gfx_imgui.h"
static sgimgui_t sgimgui;
#include "jsffi.h"
JSC_CCALL(imgui_begin,
char *title = js2strdup(argv[0]);
bool active = true;
ImGui::Begin(title, &active, ImGuiWindowFlags_MenuBar);
free(title);
return boolean2js(active);
)
JSC_CCALL(imgui_end, ImGui::End())
JSC_CCALL(imgui_beginmenu,
char *title = js2strdup(argv[0]);
bool active = ImGui::BeginMenu(title);
free(title);
return boolean2js(active);
)
JSC_CCALL(imgui_menuitem,
char *name = js2strdup(argv[0]);
char *hotkey = js2strdup(argv[1]);
if (ImGui::MenuItem(name,hotkey))
script_call_sym(argv[2], 0, NULL);
free(name);
free(hotkey);
)
JSC_CCALL(imgui_beginmenubar, ImGui::BeginMenuBar())
JSC_CCALL(imgui_endmenubar, ImGui::EndMenuBar())
JSC_SSCALL(imgui_textinput,
char buffer[512];
strncpy(buffer, str2, 512);
ImGui::InputText(str, buffer, sizeof(buffer));
ret = str2js(buffer);
)
static const JSCFunctionListEntry js_imgui_funcs[] = {
MIST_FUNC_DEF(imgui, begin, 1),
MIST_FUNC_DEF(imgui, end,0),
MIST_FUNC_DEF(imgui, beginmenu, 1),
MIST_FUNC_DEF(imgui, menuitem, 3),
MIST_FUNC_DEF(imgui, beginmenubar, 0),
MIST_FUNC_DEF(imgui, endmenubar, 0),
MIST_FUNC_DEF(imgui, textinput, 2),
};
static int started = 0;
JSValue gui_init(JSContext *js)
{
simgui_desc_t sdesc = {0};
simgui_setup(&sdesc);
sgimgui_desc_t desc = {0};
sgimgui_init(&sgimgui, &desc);
JSValue imgui = JS_NewObject(js);
JS_SetPropertyFunctionList(js, imgui, js_imgui_funcs, countof(js_imgui_funcs));
started = 1;
return imgui;
}
void gui_input(sapp_event *e)
{
if (started)
simgui_handle_event(e);
}
void gui_newframe(int x, int y, float dt)
{
simgui_frame_desc_t frame = {
.width = x,
.height = y,
.delta_time = dt
};
simgui_new_frame(&frame);
}
void gfx_gui()
{
sgimgui_draw(&sgimgui);
sgimgui_draw_menu(&sgimgui, "sokol-gfx");
}
void gui_endframe()
{
simgui_render();
}
void gui_exit()
{
sgimgui_discard(&sgimgui);
}

22
source/engine/gui.h Normal file
View file

@ -0,0 +1,22 @@
#ifndef GUI_H
#define GUI_H
#include "jsffi.h"
#include "sokol/sokol_app.h"
#ifdef __cplusplus
extern "C" {
#endif
JSValue gui_init(JSContext *js);
void gui_newframe(int x, int y, float dt);
void gfx_gui();
void gui_input(sapp_event *e);
void gui_endframe();
void gui_exit();
#ifdef __cplusplus
}
#endif
#endif

View file

@ -44,7 +44,7 @@ static char *touch_jstrn(char *dest, int len, sapp_touchpoint *touch, int n)
char touchdest[512] = {0}; char touchdest[512] = {0};
strncat(dest,"[", 512); strncat(dest,"[", 512);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
snprintf(touchdest, 512, "{id:%p, x:%g, y:%g},", touch[i].identifier, touch[i].pos_x, touch[i].pos_y); snprintf(touchdest, 512, "{id:%zd, x:%g, y:%g},", touch[i].identifier, touch[i].pos_x, touch[i].pos_y);
strncat(dest,touchdest,512); strncat(dest,touchdest,512);
} }
strncat(dest,"]", 512); strncat(dest,"]", 512);

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,22 @@
#ifndef FFI_H #ifndef FFI_H
#define FFI_H #define FFI_H
#ifdef __cplusplus
extern "C" {
#endif
#include "quickjs/quickjs.h" #include "quickjs/quickjs.h"
#include "HandmadeMath.h" #include "HandmadeMath.h"
#include <stdarg.h> #include <stdarg.h>
#include <chipmunk/chipmunk.h>
extern JSValue cpShape2js(cpShape *s);
#define MIST_CFUNC_DEF(name, length, func1) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } } #define MIST_CFUNC_DEF(name, length, func1) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } }
#define MIST_FUNC_DEF(TYPE, FN, LEN) MIST_CFUNC_DEF(#FN, LEN, js_##TYPE##_##FN) #define MIST_FUNC_DEF(TYPE, FN, LEN) MIST_CFUNC_DEF(#FN, LEN, js_##TYPE##_##FN)
#define GETFN(ID, ENTRY, TYPE) JSC_CCALL(ID##_##ENTRY, return TYPE##2js(js2##ID (this)->ENTRY)) #define JS_SETSIG JSContext *js, JSValue self, JSValue val
#define JSC_SCALL(NAME, FN) JSC_CCALL(NAME, \ #define JSC_SCALL(NAME, FN) JSC_CCALL(NAME, \
const char *str = js2str(argv[0]); \ const char *str = js2str(argv[0]); \
@ -34,52 +41,52 @@
#define CGETSET_ADD(ID, ENTRY) MIST_CGETSET_DEF(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY) #define CGETSET_ADD(ID, ENTRY) MIST_CGETSET_DEF(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY)
#define CGETSET_ADD_HID(ID, ENTRY) MIST_CGETSET_BASE(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY, JS_PROP_CONFIGURABLE) #define CGETSET_ADD_HID(ID, ENTRY) MIST_CGETSET_BASE(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY, JS_PROP_CONFIGURABLE)
#define JSC_CCALL(NAME, FN) JSValue js_##NAME (JSContext *js, JSValue this, int argc, JSValue *argv) { \ #define JSC_CCALL(NAME, FN) JSValue js_##NAME (JSContext *js, JSValue self, int argc, JSValue *argv) { \
JSValue ret = JS_UNDEFINED; \ JSValue ret = JS_UNDEFINED; \
{FN;} \ {FN;} \
return ret; \ return ret; \
} \ } \
#define GETSETPAIR(ID, ENTRY, TYPE, FN) \ #define GETSETPAIR(ID, ENTRY, TYPE, FN) \
JSValue js_##ID##_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \ JSValue js_##ID##_set_##ENTRY (JS_SETSIG) { \
js2##ID (this)->ENTRY = js2##TYPE (val); \ js2##ID (self)->ENTRY = js2##TYPE (val); \
{FN;} \ {FN;} \
return JS_UNDEFINED; \ return JS_UNDEFINED; \
} \ } \
\ \
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue this) { \ JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \
return TYPE##2js(js2##ID (this)->ENTRY); \ return TYPE##2js(js2##ID (self)->ENTRY); \
} \ } \
#define JSC_GETSET(ID, ENTRY, TYPE) GETSETPAIR( ID , ENTRY , TYPE , ; ) #define JSC_GETSET(ID, ENTRY, TYPE) GETSETPAIR( ID , ENTRY , TYPE , ; )
#define JSC_GETSET_APPLY(ID, ENTRY, TYPE) GETSETPAIR(ID, ENTRY, TYPE, ID##_apply(js2##ID (this));) #define JSC_GETSET_APPLY(ID, ENTRY, TYPE) GETSETPAIR(ID, ENTRY, TYPE, ID##_apply(js2##ID (self));)
#define JSC_GETSET_HOOK(ID, ENTRY) GETSETPAIR(ID, ENTRY, JSValue, ;) #define JSC_GETSET_HOOK(ID, ENTRY) GETSETPAIR(ID, ENTRY, JSValue, ;)
#define JSC_GETSET_GLOBAL(ENTRY, TYPE) \ #define JSC_GETSET_GLOBAL(ENTRY, TYPE) \
JSValue js_global_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \ JSValue js_global_set_##ENTRY (JS_SETSIG) { \
ENTRY = js2##TYPE (val); \ ENTRY = js2##TYPE (val); \
return JS_UNDEFINED; \ return JS_UNDEFINED; \
} \ } \
\ \
JSValue js_global_get_##ENTRY (JSContext *js, JSValue this) { \ JSValue js_global_get_##ENTRY (JSContext *js, JSValue self) { \
return TYPE##2js(ENTRY); \ return TYPE##2js(ENTRY); \
} \ } \
#define JSC_GETSET_BODY(ENTRY, CPENTRY, TYPE) \ #define JSC_GETSET_BODY(ENTRY, CPENTRY, TYPE) \
JSValue js_gameobject_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \ JSValue js_gameobject_set_##ENTRY (JS_SETSIG) { \
cpBody *b = js2gameobject(this)->body; \ cpBody *b = js2gameobject(self)->body; \
cpBodySet##CPENTRY (b, js2##TYPE (val)); \ cpBodySet##CPENTRY (b, js2##TYPE (val)); \
return JS_UNDEFINED; \ return JS_UNDEFINED; \
} \ } \
\ \
JSValue js_gameobject_get_##ENTRY (JSContext *js, JSValue this) { \ JSValue js_gameobject_get_##ENTRY (JSContext *js, JSValue self) { \
cpBody *b = js2gameobject(this)->body; \ cpBody *b = js2gameobject(self)->body; \
return TYPE##2js (cpBodyGet##CPENTRY (b)); \ return TYPE##2js (cpBodyGet##CPENTRY (b)); \
} \ } \
#define JSC_GET(ID, ENTRY, TYPE) \ #define JSC_GET(ID, ENTRY, TYPE) \
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue this) { \ JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \
return TYPE##2js(js2##ID (this)->ENTRY); } \ return TYPE##2js(js2##ID (self)->ENTRY); } \
#define QJSCLASS(TYPE)\ #define QJSCLASS(TYPE)\
static JSClassID js_##TYPE##_id;\ static JSClassID js_##TYPE##_id;\
@ -91,14 +98,17 @@ static JSClassDef js_##TYPE##_class = {\
#TYPE,\ #TYPE,\
.finalizer = js_##TYPE##_finalizer,\ .finalizer = js_##TYPE##_finalizer,\
};\ };\
static TYPE *js2##TYPE (JSValue val) { return JS_GetOpaque(val,js_##TYPE##_id); }\ TYPE *js2##TYPE (JSValue val) { \
static JSValue TYPE##2js(TYPE *n) { \ assert(JS_GetClassID(val) == js_##TYPE##_id); \
return JS_GetOpaque(val,js_##TYPE##_id); \
}\
JSValue TYPE##2js(TYPE *n) { \
JSValue j = JS_NewObjectClass(js,js_##TYPE##_id);\ JSValue j = JS_NewObjectClass(js,js_##TYPE##_id);\
YughSpam("Created " #TYPE " at %p", n); \ YughSpam("Created " #TYPE " at %p", n); \
JS_SetOpaque(j,n);\ JS_SetOpaque(j,n);\
return j; }\ return j; }\
\ \
static JSValue js_##TYPE##_memid (JSContext *js, JSValue this) { return str2js("%p", js2##TYPE(this)); } \ static JSValue js_##TYPE##_memid (JSContext *js, JSValue self) { return str2js("%p", js2##TYPE(self)); } \
#define QJSGLOBALCLASS(NAME) \ #define QJSGLOBALCLASS(NAME) \
JSValue NAME = JS_NewObject(js); \ JSValue NAME = JS_NewObject(js); \
@ -124,6 +134,9 @@ void ffi_load();
JSValue vec22js(HMM_Vec2 v); JSValue vec22js(HMM_Vec2 v);
HMM_Vec2 js2vec2(JSValue v); HMM_Vec2 js2vec2(JSValue v);
const char *js2str(JSValue v);
char *js2strdup(JSValue v);
JSValue bitmask2js(cpBitmask mask); JSValue bitmask2js(cpBitmask mask);
cpBitmask js2bitmask(JSValue v); cpBitmask js2bitmask(JSValue v);
int js_print_exception(JSValue v); int js_print_exception(JSValue v);
@ -131,8 +144,29 @@ int js_print_exception(JSValue v);
struct rgba js2color(JSValue v); struct rgba js2color(JSValue v);
double js2number(JSValue v); double js2number(JSValue v);
JSValue number2js(double g); JSValue number2js(double g);
uint64_t js2uint64(JSValue v);
JSValue str2js(const char *c, ...); JSValue str2js(const char *c, ...);
void nota_int(char *blob); void nota_int(char *blob);
JSValue js_getpropidx(JSValue v, uint32_t i);
JSValue js_getpropstr(JSValue v, const char *str);
void jsfreestr(const char *str);
int js_arrlen(JSValue v);
void js_setpropstr(JSValue v, const char *str, JSValue p);
int js2boolean(JSValue v);
JSValue boolean2js(int b);
#define PREP_PARENT(TYPE, PARENT) \
TYPE##_proto = JS_NewObject(js); \
JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYPE##_funcs)); \
JS_SetPrototype(js, TYPE##_proto, PARENT##_proto); \
#ifdef __cplusplus
}
#endif
#endif #endif

View file

@ -15,6 +15,10 @@
#include "script.h" #include "script.h"
#ifdef __WIN32
#include "debugapi.h"
#endif
#define ESC "\033[" #define ESC "\033["
#define BLACK 30 #define BLACK 30
#define RED 31 #define RED 31
@ -25,7 +29,7 @@
#define CYAN 36 #define CYAN 36
#define WHITE 37 #define WHITE 37
#define COLOR(TXT, _C) ESC #_C "m" #TXT ESC "0m" #define COLOR(TXT, _C) ESC "22;" #_C "m" #TXT ESC "0m"
char *logstr[] = { "spam", "debug", "info", "warn", "error", "panic"}; char *logstr[] = { "spam", "debug", "info", "warn", "error", "panic"};
char *logcolor[] = { COLOR(spam,37), COLOR(debug,32), COLOR(info,36), COLOR(warn,33), COLOR(error,31), COLOR(panic,45) }; char *logcolor[] = { COLOR(spam,37), COLOR(debug,32), COLOR(info,36), COLOR(warn,33), COLOR(error,31), COLOR(panic,45) };
@ -43,7 +47,6 @@ void log_init()
if (!fexists(".prosperon")) { if (!fexists(".prosperon")) {
logout = tmpfile(); logout = tmpfile();
dump = tmpfile(); dump = tmpfile();
writeout = stdout;
} }
else { else {
logout = fopen(".prosperon/log.txt", "w"); logout = fopen(".prosperon/log.txt", "w");
@ -104,8 +107,8 @@ void mYughLog(int category, int priority, int line, const char *file, const char
void log_print(const char *str) void log_print(const char *str)
{ {
#ifndef NDEBUG #ifndef NDEBUG
fprintf(stdout, str); fprintf(stdout, "%s", str);
fprintf(writeout, str); fprintf(writeout, "%s", str);
fflush(stdout); fflush(stdout);
#else #else
printf(str); printf(str);

624
source/engine/model.c Normal file
View file

@ -0,0 +1,624 @@
#include "model.h"
#include "log.h"
#include "resources.h"
#include "stb_ds.h"
#include "gameobject.h"
#include "render.h"
#include "HandmadeMath.h"
#include "math.h"
#include "time.h"
#include <cgltf.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "yugine.h"
#include "jsffi.h"
#include "texture.h"
#include "sokol/sokol_gfx.h"
static void processnode();
static void processmesh();
static void processtexture();
static cgltf_data *cdata;
static char *cpath;
cgltf_attribute *get_attr_type(cgltf_primitive *p, cgltf_attribute_type t)
{
for (int i = 0; i < p->attributes_count; i++) {
if (p->attributes[i].type == t)
return &p->attributes[i];
}
return NULL;
}
unsigned short pack_short_tex(float c) { return c * USHRT_MAX; }
void mesh_add_material(primitive *prim, cgltf_material *mat)
{
if (!mat) return;
prim->mat = calloc(sizeof(*prim->mat), 1);
material *pmat = prim->mat;
if (mat->has_pbr_metallic_roughness && mat->pbr_metallic_roughness.base_color_texture.texture) {
cgltf_image *img = mat->pbr_metallic_roughness.base_color_texture.texture->image;
if (img->buffer_view) {
cgltf_buffer_view *buf = img->buffer_view;
pmat->diffuse = texture_fromdata(buf->buffer->data, buf->size);
} else {
char *path = makepath(dirname(cpath), img->uri);
pmat->diffuse = texture_from_file(path);
free(path);
}
} else
pmat->diffuse = texture_from_file("icons/moon.gif");
}
sg_buffer texcoord_floats(float *f, int n)
{
unsigned short packed[n];
for (int i = 0; i < n; i++) {
float v = f[i];
if (v < 0) v = 0;
if (v > 1) v = 1;
packed[i] = pack_short_tex(v);
}
return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(packed),
.label = "tex coord vert buffer",
});
}
sg_buffer par_idx_buffer(uint32_t *p, int v)
{
uint16_t idx[v];
for (int i = 0; i < v; i++) idx[i] = p[i];
return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(idx),
.type = SG_BUFFERTYPE_INDEXBUFFER
});
}
sg_buffer float_buffer(float *f, int v)
{
return sg_make_buffer(&(sg_buffer_desc){
.data = (sg_range){
.ptr = f,
.size = sizeof(*f)*v
}
});
}
sg_buffer index_buffer(float *f, int verts)
{
uint16_t idxs[verts];
for (int i = 0; i < verts; i++)
idxs[i] = f[i];
return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(idxs),
.type = SG_BUFFERTYPE_INDEXBUFFER,
});
}
uint32_t pack_int10_n2(float *norm)
{
uint32_t ret = 0;
for (int i = 0; i < 3; i++) {
int n = (norm[i]+1.0)*511;
ret |= (n & 0x3ff) << (10*i);
}
return ret;
}
sg_buffer normal_floats(float *f, int n)
{
return float_buffer(f, n);
uint32_t packed_norms[n/3];
for (int v = 0, i = 0; v < n/3; v++, i+= 3)
packed_norms[v] = pack_int10_n2(f+i);
return sg_make_buffer(&(sg_buffer_desc){
.data = SG_RANGE(packed_norms),
.label = "normal vert buffer",
});
}
sg_buffer ubyten_buffer(float *f, int v)
{
unsigned char b[v];
for (int i = 0; i < (v); i++)
b[i] = f[i]*255;
return sg_make_buffer(&(sg_buffer_desc){.data=SG_RANGE(b)});
}
sg_buffer ubyte_buffer(float *f, int v)
{
unsigned char b[v];
for (int i = 0; i < (v); i++)
b[i] = f[i];
return sg_make_buffer(&(sg_buffer_desc){.data=SG_RANGE(b)});
}
sg_buffer joint_buf(float *f, int v)
{
char joints[v];
for (int i = 0; i < (v); i++)
joints[i] = f[i];
return sg_make_buffer(&(sg_buffer_desc){ .data = SG_RANGE(joints)});
}
sg_buffer weight_buf(float *f, int v)
{
unsigned char weights[v];
for (int i = 0; i < (v); i++)
weights[i] = f[i]*255;
return sg_make_buffer(&(sg_buffer_desc){ .data = SG_RANGE(weights)});
}
HMM_Vec3 index_to_vert(uint32_t idx, float *f)
{
return (HMM_Vec3){f[idx*3], f[idx*3+1], f[idx*3+2]};
}
void primitive_gen_indices(primitive *prim)
{
if (prim->idx_count != 0) return;
uint16_t *idxs = malloc(sizeof(*idxs)*prim->idx_count);
for (int z = 0; z < prim->idx_count; z++)
idxs[z] = z;
prim->index = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = idxs,
.data.size = sizeof(uint16_t) * prim->idx_count,
.type = SG_BUFFERTYPE_INDEXBUFFER});
free(idxs);
}
struct primitive mesh_add_primitive(cgltf_primitive *prim)
{
primitive retp = (primitive){0};
uint16_t *idxs;
if (prim->indices) {
int n = cgltf_accessor_unpack_floats(prim->indices, NULL, 0);
float fidx[n];
cgltf_accessor_unpack_floats(prim->indices, fidx, n);
idxs = malloc(sizeof(*idxs)*n);
for (int i = 0; i < n; i++)
idxs[i] = fidx[i];
retp.index = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = idxs,
.data.size = sizeof(*idxs) * n,
.type = SG_BUFFERTYPE_INDEXBUFFER,
.label = "mesh index buffer",
});
retp.idx_count = n;
free(idxs);
} else {
retp.idx_count = cgltf_accessor_unpack_floats(prim->attributes[0].data, NULL, 0);
primitive_gen_indices(&retp);
}
printf("adding material\n");
mesh_add_material(&retp, prim->material);
for (int k = 0; k < prim->attributes_count; k++) {
cgltf_attribute attribute = prim->attributes[k];
int n = cgltf_accessor_unpack_floats(attribute.data, NULL, 0); /* floats per vertex x num elements. In other words, total floats pulled */
int comp = cgltf_num_components(attribute.data->type);
int verts = n/comp;
float vs[n];
cgltf_accessor_unpack_floats(attribute.data, vs, n);
switch (attribute.type) {
case cgltf_attribute_type_position:
retp.pos = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = vs,
.data.size = sizeof(float) * n,
.label = "mesh vert buffer"
});
break;
case cgltf_attribute_type_normal:
retp.norm = normal_floats(vs, n);
break;
case cgltf_attribute_type_tangent:
break;
case cgltf_attribute_type_color:
retp.color = ubyten_buffer(vs,n);
break;
case cgltf_attribute_type_weights:
retp.weight = ubyten_buffer(vs, n);
break;
case cgltf_attribute_type_joints:
retp.bone = ubyte_buffer(vs, n);
break;
case cgltf_attribute_type_texcoord:
retp.uv = texcoord_floats(vs, n);
break;
case cgltf_attribute_type_invalid:
YughWarn("Invalid type.");
break;
case cgltf_attribute_type_custom:
break;
case cgltf_attribute_type_max_enum:
break;
}
}
if (!retp.bone.id) {
char joints[retp.idx_count*4];
memset(joints, 0, retp.idx_count*4);
retp.bone = sg_make_buffer(&(sg_buffer_desc){ .data = SG_RANGE(joints)});
}
if (!retp.weight.id) {
char weights[retp.idx_count*4];
memset(weights,0,retp.idx_count*4);
retp.weight = sg_make_buffer(&(sg_buffer_desc){ .data = SG_RANGE(weights)});
}
if (!retp.color.id) {
char colors[retp.idx_count*4];
memset(colors,0,retp.idx_count*4);
retp.color = sg_make_buffer(&(sg_buffer_desc) { .data = SG_RANGE(colors) });
}
if (!retp.norm.id) {
YughInfo("Making normals.");
cgltf_attribute *pa = get_attr_type(prim, cgltf_attribute_type_position);
int n = cgltf_accessor_unpack_floats(pa->data, NULL,0);
int comp = 3;
int verts = n/comp;
uint32_t face_norms[verts];
float ps[n];
cgltf_accessor_unpack_floats(pa->data,ps,n);
for (int i = 0; i < verts; i+=3) {
HMM_Vec3 a = index_to_vert(i,ps);
HMM_Vec3 b = index_to_vert(i+1,ps);
HMM_Vec3 c = index_to_vert(i+2,ps);
HMM_Vec3 norm = HMM_NormV3(HMM_Cross(HMM_SubV3(b,a), HMM_SubV3(c,a)));
uint32_t packed_norm = pack_int10_n2(norm.Elements);
face_norms[i] = face_norms[i+1] = face_norms[i+2] = packed_norm;
}
retp.norm = sg_make_buffer(&(sg_buffer_desc){
.data.ptr = face_norms,
.data.size = sizeof(uint32_t) * verts});
}
return retp;
}
void model_add_cgltf_mesh(mesh *m, cgltf_mesh *gltf_mesh)
{
for (int i = 0; i < gltf_mesh->primitives_count; i++)
arrput(m->primitives, mesh_add_primitive(gltf_mesh->primitives+i));
}
void packFloats(float *src, float *dest, int srcLength) {
int i, j;
for (i = 0, j = 0; i < srcLength; i += 3, j += 4) {
dest[j] = src[i];
dest[j + 1] = src[i + 1];
dest[j + 2] = src[i + 2];
dest[j + 3] = 0.0f;
}
}
void model_add_cgltf_anim(model *model, cgltf_animation *anim)
{
YughInfo("FOUND ANIM, using %d channels and %d samplers", anim->channels_count, anim->samplers_count);
struct animation an = (struct animation){0};
arrsetlen(an.samplers, anim->samplers_count);
for (int i = 0; i < anim->samplers_count; i++) {
cgltf_animation_sampler s = anim->samplers[i];
sampler samp = (sampler){0};
int n = cgltf_accessor_unpack_floats(s.input, NULL, 0);
arrsetlen(samp.times, n);
cgltf_accessor_unpack_floats(s.input, samp.times, n);
n = cgltf_accessor_unpack_floats(s.output, NULL, 0);
int comp = cgltf_num_components(s.output->type);
arrsetlen(samp.data, n/comp);
if (comp == 4)
cgltf_accessor_unpack_floats(s.output, samp.data, n);
else {
float *out = malloc(sizeof(*out)*n);
cgltf_accessor_unpack_floats(s.output, out, n);
packFloats(out, samp.data, n);
free(out);
}
samp.type = s.interpolation;
if (samp.type == LINEAR && comp == 4)
samp.type = SLERP;
an.samplers[i] = samp;
}
for (int i = 0; i < anim->channels_count; i++) {
cgltf_animation_channel ch = anim->channels[i];
struct anim_channel ach = (struct anim_channel){0};
md5joint *md = model->nodes+(ch.target_node-cdata->nodes);
switch(ch.target_path) {
case cgltf_animation_path_type_translation:
ach.target = &md->pos;
break;
case cgltf_animation_path_type_rotation:
ach.target = &md->rot;
break;
case cgltf_animation_path_type_scale:
ach.target = &md->scale;
break;
default: break;
}
ach.sampler = an.samplers+(ch.sampler-anim->samplers);
arrput(an.channels, ach);
}
model->anim = an;
model->anim.time = apptime();
}
void model_add_cgltf_skin(model *model, cgltf_skin *skin)
{
int n = cgltf_accessor_unpack_floats(skin->inverse_bind_matrices, NULL, 0);
struct skin sk = (struct skin){0};
arrsetlen(sk.invbind, n/16);
cgltf_accessor_unpack_floats(skin->inverse_bind_matrices, sk.invbind, n);
YughInfo("FOUND SKIN, of %d bones, and %d vert comps", skin->joints_count, n);
cgltf_node *root = skin->skeleton;
arrsetlen(sk.joints, skin->joints_count);
sk.root = model->nodes+(skin->skeleton-cdata->nodes);
for (int i = 0; i < 50; i++)
sk.binds[i] = MAT1;
for (int i = 0; i < skin->joints_count; i++) {
int offset = skin->joints[i]-cdata->nodes;
sk.joints[i] = model->nodes+offset;
md5joint *j = sk.joints[i];
cgltf_node *n = skin->joints[i];
for (int i = 0; i < 3; i++) {
j->pos.e[i] = n->translation[i];
j->scale.e[i] = n->scale[i];
}
for (int i = 0; i < 4; i++)
j->rot.e[i] = n->rotation[i];
}
model->skin = sk;
}
void model_process_node(model *model, cgltf_node *node)
{
int n = node-cdata->nodes;
cgltf_node_transform_world(node, model->nodes[n].t.e);
model->nodes[n].parent = model->nodes+(node->parent-cdata->nodes);
if (node->mesh) {
int meshn = node->mesh-cdata->meshes;
arrsetlen(model->meshes, meshn+1);
model->meshes[meshn].m = &model->nodes[n].t;
model_add_cgltf_mesh(model->meshes+meshn, node->mesh);
}
}
struct model *model_make(const char *path)
{
YughInfo("Making the model from %s.", path);
cpath = path;
cgltf_options options = {0};
cgltf_data *data = NULL;
cgltf_result result = cgltf_parse_file(&options, path, &data);
struct model *model = NULL;
if (result) {
YughError("CGLTF could not parse file %s, err %d.", path, result);
goto CLEAN;
}
result = cgltf_load_buffers(&options, data, path);
if (result) {
YughError("CGLTF could not load buffers for file %s, err %d.", path, result);
goto CLEAN;
}
cdata = data;
model = calloc(1, sizeof(*model));
arrsetlen(model->nodes, data->nodes_count);
for (int i = 0; i < data->nodes_count; i++)
model_process_node(model, data->nodes+i);
for (int i = 0; i < data->animations_count; i++)
model_add_cgltf_anim(model, data->animations+i);
for (int i = 0; i < data->skins_count; i++)
model_add_cgltf_skin(model, data->skins+i);
CLEAN:
cgltf_free(data);
return model;
}
void model_free(model *m)
{
}
sg_bindings primitive_bind(primitive *p)
{
sg_bindings b = {0};
b.vertex_buffers[MAT_POS] = p->pos;
b.vertex_buffers[MAT_UV] = p->uv;
b.vertex_buffers[MAT_NORM] = p->norm;
b.vertex_buffers[MAT_BONE] = p->bone;
b.vertex_buffers[MAT_WEIGHT] = p->weight;
b.vertex_buffers[MAT_COLOR] = p->color;
b.index_buffer = p->index;
b.fs.images[0] = p->mat->diffuse->id;
b.fs.samplers[0] = tex_sampler;
return b;
}
void model_draw_go(model *model, transform *go)
{
HMM_Mat4 gom = transform2mat(*go);
animation_run(&model->anim, apptime());
skin *sk = &model->skin;
for (int i = 0; i < arrlen(sk->joints); i++) {
md5joint *md = sk->joints[i];
HMM_Mat4 local = HMM_M4TRS(md->pos.xyz, md->rot, md->scale.xyz);
if (md->parent)
local = HMM_MulM4(md->parent->t, local);
md->t = local;
sk->binds[i] = HMM_MulM4(md->t, sk->invbind[i]);
}
/*sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_skinv, &(sg_range){
.ptr = sk->binds,
.size = sizeof(*sk->binds)*50
});
*/
for (int i = 0; i < arrlen(model->meshes); i++) {
HMM_Mat4 mod = *model->meshes[i].m;
mod = HMM_MulM4(mod, gom);
mesh msh = model->meshes[i];
for (int j = 0; j < arrlen(msh.primitives); j++) {
sg_bindings b = primitive_bind(msh.primitives+j);
sg_apply_bindings(&b);
sg_draw(0, msh.primitives[j].idx_count, 1);
}
}
}
int mat2type(int mat)
{
switch(mat) {
case MAT_POS:
case MAT_NORM:
return SG_VERTEXFORMAT_FLOAT3;
case MAT_PPOS:
case MAT_WH:
case MAT_ST:
return SG_VERTEXFORMAT_FLOAT2;
case MAT_UV:
case MAT_TAN:
return SG_VERTEXFORMAT_USHORT2N;
return SG_VERTEXFORMAT_UINT10_N2;
case MAT_BONE:
return SG_VERTEXFORMAT_UBYTE4;
case MAT_WEIGHT:
case MAT_COLOR:
return SG_VERTEXFORMAT_UBYTE4N;
case MAT_ANGLE:
case MAT_SCALE:
return SG_VERTEXFORMAT_FLOAT;
};
return 0;
}
int mat2step(int mat)
{
switch(mat) {
case MAT_POS:
case MAT_UV:
case MAT_TAN:
case MAT_NORM:
case MAT_BONE:
case MAT_WEIGHT:
return SG_VERTEXSTEP_PER_VERTEX;
};
return SG_VERTEXSTEP_PER_INSTANCE;
}
sg_buffer mat2buffer(int mat, primitive *p)
{
switch(mat) {
case MAT_POS: return p->pos;
case MAT_NORM: return p->norm;
case MAT_UV: return p->uv;
case MAT_BONE: return p->bone;
case MAT_WEIGHT: return p->weight;
case MAT_COLOR: return p->color;
};
return p->pos;
}
sg_bindings primitive_bindings(primitive *p, JSValue v)
{
sg_bindings b = {0};
JSValue inputs = js_getpropstr(js_getpropstr(v, "vs"), "inputs");
for (int i = 0; i < js_arrlen(inputs); i++) {
JSValue attr = js_getpropidx(inputs, i);
int mat = js2number(js_getpropstr(attr, "mat"));
int slot = js2number(js_getpropstr(attr, "slot"));
sg_buffer buf = mat2buffer(mat,p);
if (!buf.id) {
// ERROR
}
b.vertex_buffers[slot] = buf;
}
b.index_buffer = p->index;
return b;
}
void primitive_free(primitive *prim)
{
}
void material_free(material *mat)
{
}

109
source/engine/model.h Normal file
View file

@ -0,0 +1,109 @@
#ifndef MODEL_H
#define MODEL_H
#include "HandmadeMath.h"
#include "transform.h"
#include "sokol/sokol_gfx.h"
#include "gameobject.h"
#include "anim.h"
#include "texture.h"
#define MAT_POS 0
#define MAT_UV 1
#define MAT_NORM 2
#define MAT_BONE 3
#define MAT_WEIGHT 4
#define MAT_COLOR 5
#define MAT_TAN 6
#define MAT_ANGLE 7
#define MAT_WH 8
#define MAT_ST 9
#define MAT_PPOS 10
#define MAT_SCALE 11
typedef struct material {
struct texture *diffuse;
struct texture *metalrough;
float metal;
float rough;
struct texture *normal;
float nrm;
struct texture *occlusion;
float occl;
struct texture *emissive;
HMM_Vec3 emis;
} material;
struct model;
typedef struct primitive {
sg_buffer pos;
sg_buffer norm;
sg_buffer uv;
sg_buffer bone;
sg_buffer weight;
sg_buffer color;
sg_buffer index;
material *mat;
uint32_t idx_count;
} primitive;
/* A single mesh */
typedef struct mesh {
primitive *primitives;
HMM_Mat4 *m;
} mesh;
typedef struct joint {
int me;
struct joint *children;
} joint_t;
typedef struct md5joint {
struct md5joint *parent;
HMM_Vec4 pos;
HMM_Quat rot;
HMM_Vec4 scale;
HMM_Mat4 t;
} md5joint;
typedef struct skin {
md5joint **joints;
HMM_Mat4 *invbind;
HMM_Mat4 binds[50]; /* binds = joint * invbind */
md5joint *root;
} skin;
/* A collection of meshes which create a full figure */
typedef struct model {
struct mesh *meshes;
md5joint *nodes;
material *mats;
skin skin;
struct animation anim;
} model;
/* Make a Model struct */
struct model *model_make(const char *path);
void model_free(model *m);
void model_draw_go(model *m, transform *go);
sg_bindings primitive_bindings(primitive *p, JSValue pipe);
void primitive_gen_indices(primitive *prim);
int mat2type(int mat);
sg_buffer float_buffer(float *f, int v);
sg_buffer index_buffer(float *f, int verts);
sg_buffer texcoord_floats(float *f, int n);
sg_buffer par_idx_buffer(uint32_t *i, int v);
sg_buffer normal_floats(float *f, int n);
sg_buffer ubyten_buffer(float *f, int v);
sg_buffer ubyte_buffer(float *f, int v);
sg_buffer joint_buf(float *f, int v);
sg_buffer weight_buf(float *f, int v);
void primitive_free(primitive *prim);
material *material_make();
void material_free(material *mat);
#endif

View file

@ -1,124 +1,37 @@
#include "particle.h" #include "particle.h"
#include "stb_ds.h" #include "stb_ds.h"
#include "render.h" #include "render.h"
#include "particle.sglsl.h"
#include "2dphysics.h" #include "2dphysics.h"
#include "log.h"
#include "simplex.h"
#include "pthread.h"
#include "math.h" #include "math.h"
#include "log.h"
#define SCHED_IMPLEMENTATION
#include "sched.h"
static emitter **emitters;
static sg_shader par_shader;
static sg_pipeline par_pipe;
static sg_bindings par_bind;
static int draw_count;
#define MAX_PARTICLES 1000000
struct scheduler sched;
void *mem;
struct par_vert {
HMM_Vec2 pos;
float angle;
HMM_Vec2 scale;
struct rgba color;
};
typedef struct par_vert par_vert;
void particle_init()
{
sched_size needed;
scheduler_init(&sched, &needed, 1, NULL);
mem = calloc(needed, 1);
scheduler_start(&sched,mem);
par_shader = sg_make_shader(particle_shader_desc(sg_query_backend()));
par_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = par_shader,
.layout = {
.attrs = {
[1].format = SG_VERTEXFORMAT_FLOAT2,
[2].format = SG_VERTEXFORMAT_FLOAT,
[3].format = SG_VERTEXFORMAT_FLOAT2,
[4].format = SG_VERTEXFORMAT_UBYTE4N,
[0].format = SG_VERTEXFORMAT_FLOAT2,
[0].buffer_index = 1
},
.buffers[0].step_func = SG_VERTEXSTEP_PER_INSTANCE,
},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
.label = "particle pipeline",
.cull_mode = SG_CULLMODE_BACK,
.colors[0].blend = blend_trans,
.depth = {
.write_enabled = true,
.compare = SG_COMPAREFUNC_LESS_EQUAL
}
});
par_bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(par_vert)*MAX_PARTICLES,
.type = SG_BUFFERTYPE_VERTEXBUFFER,
.usage = SG_USAGE_STREAM,
.label = "particle buffer"
});
float circleverts[8] = {
0,0,
0,1,
1,0,
1,1,
};
par_bind.vertex_buffers[1] = sg_make_buffer(&(sg_buffer_desc){
.data = (sg_range){.ptr = circleverts, .size = sizeof(float)*8},
.usage = SG_USAGE_IMMUTABLE,
.label = "particle quater buffer"
});
par_bind.fs.samplers[0] = sg_make_sampler(&(sg_sampler_desc){});
}
emitter *make_emitter() { emitter *make_emitter() {
emitter *e = calloc(sizeof(*e),1); emitter *e = calloc(sizeof(*e),1);
e->max = 20; e->max = 20;
arrsetcap(e->particles, e->max); arrsetcap(e->particles, 10);
for (int i = 0; i < arrlen(e->particles); i++) for (int i = 0; i < arrlen(e->particles); i++)
e->particles[i].life = 0; e->particles[i].life = 0;
e->life = 10; e->life = 10;
e->tte = lerp(e->explosiveness, e->life/e->max, 0); e->tte = lerp(e->explosiveness, e->life/e->max, 0);
sampler_add(&e->color, 0, (HMM_Vec4){1,1,1,1});
e->scale = 1; e->scale = 1;
e->speed = 20; e->speed = 20;
e->texture = NULL; e->buffer = sg_make_buffer(&(sg_buffer_desc){
arrpush(emitters,e); .size = sizeof(struct par_vert),
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_STREAM
});
return e; return e;
} }
void emitter_free(emitter *e) void emitter_free(emitter *e)
{ {
YughWarn("kill emitter");
arrfree(e->particles); arrfree(e->particles);
for (int i = arrlen(emitters)-1; i >= 0; i--) arrfree(e->verts);
if (emitters[i] == e) { free(e);
arrdelswap(emitters,i);
break;
}
} }
void start_emitter(emitter *e) { e->on = 1; }
void stop_emitter(emitter *e) { e->on = 0; }
/* Variate a value around variance. Variance between 0 and 1. */ /* Variate a value around variance. Variance between 0 and 1. */
float variate(float val, float variance) float variate(float val, float variance)
@ -126,84 +39,69 @@ float variate(float val, float variance)
return val + val*(frand(variance)-(variance/2)); return val + val*(frand(variance)-(variance/2));
} }
int emitter_spawn(emitter *e) int emitter_spawn(emitter *e, transform *t)
{ {
particle p; if (arrlen(e->particles) == e->max) return 0;
particle p = {0};
p.life = e->life; p.life = e->life;
p.pos = (HMM_Vec4){e->t.pos.x,e->t.pos.y,0,0}; p.pos = (HMM_Vec4){t->pos.x,t->pos.y,t->pos.z,0};
float newan = e->t.rotation.Elements[0]+(2*HMM_PI*(frand(e->divergence)-(e->divergence/2))); HMM_Vec3 up = transform_direction(t, vFWD);
HMM_Vec2 norm = HMM_V2Rotate((HMM_Vec2){0,1}, newan); float newan = (frand(e->divergence)-(e->divergence/2))*HMM_TurnToRad;
p.v = HMM_MulV4F((HMM_Vec4){norm.x,norm.y,0,0}, variate(e->speed, e->variation)); HMM_Vec2 v2n = HMM_V2Rotate((HMM_Vec2){0,1}, newan);
p.angle = 0; HMM_Vec3 norm = (HMM_Vec3){v2n.x, v2n.y,0};
p.scale = variate(e->scale, e->scale_var); p.v = HMM_MulV4F((HMM_Vec4){norm.x,norm.y,norm.z,0}, variate(e->speed, e->variation));
// p.av = 1; p.angle = 0.25;
p.scale = variate(e->scale*t->scale.x, e->scale_var);
arrput(e->particles,p); arrput(e->particles,p);
return 1; return 1;
} }
void emitter_emit(emitter *e, int count) void emitter_emit(emitter *e, int count, transform *t)
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
emitter_spawn(e); emitter_spawn(e, t);
} }
void emitters_step(double dt) void emitter_draw(emitter *e)
{ {
for (int i = 0; i < arrlen(emitters); i++) if (arrlen(e->particles) == 0) return;
emitter_step(emitters[i], dt); arrsetlen(e->verts, arrlen(e->particles));
} for (int i = 0; i < arrlen(e->particles); i++) {
static struct par_vert pv[MAX_PARTICLES];
void parallel_pv(emitter *e, struct scheduler *sched, struct sched_task_partition t, sched_uint thread_num)
{
for (int i=t.start; i < t.end; i++) {
if (e->particles[i].time >= e->particles[i].life) continue; if (e->particles[i].time >= e->particles[i].life) continue;
particle *p = &e->particles[i]; particle *p = e->particles+i;
pv[i].pos = p->pos.xy; e->verts[i].pos = p->pos.xy;
pv[i].angle = p->angle; e->verts[i].angle = p->angle;
float s = p->scale; e->verts[i].scale = p->scale;
if (p->time < e->grow_for) /* if (p->time < e->grow_for)
s = lerp(p->time/e->grow_for, 0, p->scale); e->verts[i].scale = lerp(p->time/e->grow_for, 0, p->scale);
else if (p->time > (p->life - e->shrink_for)) else if (p->time > (p->life - e->shrink_for))
s = lerp((p->time-(p->life-e->shrink_for))/e->shrink_for, p->scale, 0); e->verts[i].scale = lerp((p->time-(p->life-e->shrink_for))/e->shrink_for, p->scale, 0);*/
pv[i].scale = HMM_ScaleV2((HMM_Vec2){e->texture->width,e->texture->height}, s); e->verts[i].color = p->color;
pv[i].color = vec2rgba(p->color);
}
}
void emitters_draw(HMM_Mat4 *proj)
{
if (arrlen(emitters) == 0) return;
int draw_count = 0;
for (int i = 0; i < arrlen(emitters); i++) {
emitter *e = emitters[i];
par_bind.fs.images[0] = e->texture->id;
struct sched_task task;
scheduler_add(&sched, &task, parallel_pv, e, arrlen(e->particles), arrlen(e->particles)/sched.threads_num);
scheduler_join(&sched, &task);
sg_append_buffer(par_bind.vertex_buffers[0], &(sg_range){.ptr=&pv, .size=sizeof(struct par_vert)*arrlen(e->particles)});
draw_count += arrlen(e->particles);
} }
sg_apply_pipeline(par_pipe); sg_range verts;
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(*proj)); verts.ptr = e->verts;
sg_apply_bindings(&par_bind); verts.size = sizeof(*e->verts)*arrlen(e->verts);
sg_draw(0, 4, draw_count); if (sg_query_buffer_will_overflow(e->buffer, verts.size)) {
sg_destroy_buffer(e->buffer);
e->buffer = sg_make_buffer(&(sg_buffer_desc){
.size = verts.size,
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_STREAM
});
}
sg_append_buffer(e->buffer, &verts);
} }
static double dt; void emitter_step(emitter *e, double dt, transform *t) {
static HMM_Vec4 g_accel; HMM_Vec4 g_accel = HMM_MulV4F((HMM_Vec4){cpSpaceGetGravity(space).x, cpSpaceGetGravity(space).y, 0, 0}, dt);
void parallel_step(emitter *e, struct scheduler *shed, struct sched_task_partition t, sched_uint thread_num) for (int i = 0; i < arrlen(e->particles); i++) {
{
for (int i = t.end-1; i >=0; i--) {
if (e->particles[i].time >= e->particles[i].life) continue; if (e->particles[i].time >= e->particles[i].life) continue;
if (e->warp_mask & gravmask) //if (e->warp_mask & gravmask)
e->particles[i].v = HMM_AddV4(e->particles[i].v, g_accel); // e->particles[i].v = HMM_AddV4(e->particles[i].v, g_accel);
e->particles[i].pos = HMM_AddV4(e->particles[i].pos, HMM_MulV4F(e->particles[i].v, dt)); e->particles[i].pos = HMM_AddV4(e->particles[i].pos, HMM_MulV4F(e->particles[i].v, dt));
e->particles[i].angle += e->particles[i].av*dt; e->particles[i].angle += e->particles[i].av*dt;
@ -213,23 +111,14 @@ void parallel_step(emitter *e, struct scheduler *shed, struct sched_task_partiti
if (e->particles[i].time >= e->particles[i].life) if (e->particles[i].time >= e->particles[i].life)
arrdelswap(e->particles, i); arrdelswap(e->particles, i);
else if (query_point(e->particles[i].pos.xy)) // else if (query_point(e->particles[i].pos.xy))
arrdelswap(e->particles,i); // arrdelswap(e->particles,i);
} }
}
void emitter_step(emitter *e, double mdt) {
dt = mdt;
g_accel = HMM_MulV4F((HMM_Vec4){cpSpaceGetGravity(space).x, cpSpaceGetGravity(space).y, 0, 0}, dt);
if (arrlen(e->particles) == 0) return;
struct sched_task task;
scheduler_add(&sched, &task, parallel_step, e, arrlen(e->particles), arrlen(e->particles)/sched.threads_num);
scheduler_join(&sched, &task);
if (!e->on) return;
e->tte-=dt; e->tte-=dt;
if (e->tte <= 0) { float step = lerp(e->explosiveness, e->life/e->max,0);
emitter_spawn(e); while (e->tte <= 0) {
e->tte = lerp(e->explosiveness, e->life/e->max,0); e->tte += step;
if (!emitter_spawn(e, t)) break;
} }
} }

View file

@ -7,6 +7,7 @@
#include "texture.h" #include "texture.h"
#include "anim.h" #include "anim.h"
#include "gameobject.h" #include "gameobject.h"
#include "render.h"
typedef struct particle { typedef struct particle {
HMM_Vec4 pos; HMM_Vec4 pos;
@ -23,10 +24,16 @@ typedef struct particle {
#define CLOUD 1 #define CLOUD 1
#define MESH 2 #define MESH 2
typedef struct par_vert {
HMM_Vec2 pos;
float angle;
float scale;
HMM_Vec4 color;
} par_vert;
typedef struct emitter { typedef struct emitter {
struct particle *particles; struct particle *particles;
transform3d t; par_vert *verts;
gameobject *go;
HMM_Vec3 *mesh; /* list of points to optionally spawn from */ HMM_Vec3 *mesh; /* list of points to optionally spawn from */
HMM_Vec3 *norm; /* norm at each point */ HMM_Vec3 *norm; /* norm at each point */
int type; /* spray, cloud, or mesh */ int type; /* spray, cloud, or mesh */
@ -45,8 +52,6 @@ typedef struct emitter {
float scale_var; float scale_var;
float grow_for; /* seconds to grow from small until scale */ float grow_for; /* seconds to grow from small until scale */
float shrink_for; /* seconds to shrink to small prior to its death */ float shrink_for; /* seconds to shrink to small prior to its death */
/* PARTICLE TYPE */
texture *texture;
/* ROTATION AND COLLISION */ /* ROTATION AND COLLISION */
int collision_mask; /* mask for collision */ int collision_mask; /* mask for collision */
float bounce; /* bounce back after collision */ float bounce; /* bounce back after collision */
@ -56,21 +61,15 @@ typedef struct emitter {
float persist_var; float persist_var;
/* TRAILS */ /* TRAILS */
warpmask warp_mask; warpmask warp_mask;
int on;
double tte; /* time to emit */ double tte; /* time to emit */
sg_buffer buffer;
} emitter; } emitter;
void particle_init();
emitter *make_emitter(); emitter *make_emitter();
void emitter_free(emitter *e); void emitter_free(emitter *e);
void start_emitter(emitter *e); void emitter_emit(emitter *e, int count, transform *t);
void stop_emitter(emitter *e); void emitter_step(emitter *e, double dt, transform *t);
void emitter_draw(emitter *e);
void emitter_emit(emitter *e, int count);
void emitters_step(double dt);
void emitters_draw(HMM_Mat4 *proj);
void emitter_step(emitter *e, double dt);
#endif #endif

View file

@ -2,11 +2,9 @@
#include "config.h" #include "config.h"
#include "datastream.h" #include "datastream.h"
#include "debugdraw.h"
#include "font.h" #include "font.h"
#include "gameobject.h" #include "gameobject.h"
#include "log.h" #include "log.h"
#include "sprite.h"
#include "particle.h" #include "particle.h"
#include "window.h" #include "window.h"
#include "model.h" #include "model.h"
@ -18,118 +16,33 @@
#include "sokol/sokol_glue.h" #include "sokol/sokol_glue.h"
#include "stb_image_write.h" #include "stb_image_write.h"
#include "box.sglsl.h"
#include "shadow.sglsl.h"
#include "sokol/sokol_gfx.h" #include "sokol/sokol_gfx.h"
#include "sokol_gfx_ext.h"
#include "crt.sglsl.h" #include "gui.h"
#include "msf_gif.h" static HMM_Vec2 lastuse = {0};
HMM_Vec2 campos = {0,0}; HMM_Vec2 campos = {0,0};
float camzoom = 1; float camzoom = 1;
static struct { viewstate globalview = {0};
sg_swapchain swap;
sg_pipeline pipe;
sg_bindings bind;
sg_shader shader;
sg_image img;
sg_image depth;
} sg_gif;
static struct { sg_sampler std_sampler;
sg_pipeline pipe; sg_sampler nofilter_sampler;
sg_bindings bind; sg_sampler tex_sampler;
sg_shader shader;
} sg_crt;
static struct { int TOPLEFT = 0;
int w;
int h;
int cpf;
int depth;
double timer;
double spf;
int rec;
uint8_t *buffer;
} gif;
MsfGifState gif_state = {}; sg_pass offscreen;
void gif_rec_start(int w, int h, int cpf, int bitdepth)
{
gif.w = w;
gif.h = h;
gif.depth = bitdepth;
msf_gif_begin(&gif_state, gif.w, gif.h);
gif.cpf = cpf;
gif.spf = cpf/100.0;
gif.rec = 1;
gif.timer = apptime();
if (gif.buffer) free(gif.buffer);
gif.buffer = malloc(gif.w*gif.h*4);
sg_destroy_image(sg_gif.img);
sg_destroy_image(sg_gif.depth);
sg_gif.img = sg_make_image(&(sg_image_desc){
.render_target = true,
.width = gif.w,
.height = gif.h,
.pixel_format = SG_PIXELFORMAT_RGBA8,
.label = "gif rt",
});
sg_gif.depth = sg_make_image(&(sg_image_desc){
.render_target = true,
.width = gif.w,
.height = gif.h,
.label = "gif depth",
});
sg_gif.swap = sglue_swapchain();
}
void gif_rec_end(const char *path)
{
if (!gif.rec) return;
MsfGifResult gif_res = msf_gif_end(&gif_state);
if (gif_res.data) {
FILE *f = fopen(path, "wb");
fwrite(gif_res.data, gif_res.dataSize, 1, f);
fclose(f);
}
msf_gif_free(gif_res);
gif.rec = 0;
}
void capture_screen(int x, int y, int w, int h, const char *path)
{
int n = 4;
void *data = malloc(w*h*n);
sg_query_pixels(0,0,w,h,1,data,w*h*sizeof(char)*n);
// sg_query_image_pixels(crt_post.img, crt_post.bind.fs.samplers[0], data, w*h*4);
stbi_write_png("cap.png", w, h, n, data, n*w);
// stbi_write_bmp("cap.bmp", w, h, n, data);
free(data);
}
#include "sokol/sokol_app.h" #include "sokol/sokol_app.h"
#include "HandmadeMath.h" #include "HandmadeMath.h"
sg_pass_action pass_action = {0}; sg_pass_action pass_action = {0};
sg_pass_action off_action = {0};
static struct { sg_image screencolor = {0};
sg_pass_action pass_action; sg_image screendepth = {0};
sg_pass pass;
sg_pipeline pipe;
sg_shader shader;
} sg_shadow;
void trace_apply_pipeline(sg_pipeline pip, void *data) void trace_apply_pipeline(sg_pipeline pip, void *data)
{ {
@ -217,159 +130,103 @@ static sg_trace_hooks hooks = {
}; };
void render_init() { void render_init() {
mainwin.size = (HMM_Vec2){sapp_width(), sapp_height()};
sg_setup(&(sg_desc){ sg_setup(&(sg_desc){
.environment = sglue_environment(), .environment = sglue_environment(),
.logger = { .func = sg_logging }, .logger = { .func = sg_logging },
.buffer_pool_size = 1024 .buffer_pool_size = 1024
}); });
std_sampler = sg_make_sampler(&(sg_sampler_desc){});
tex_sampler = sg_make_sampler(&(sg_sampler_desc){
.min_filter = SG_FILTER_LINEAR,
.mag_filter = SG_FILTER_LINEAR,
.mipmap_filter = SG_FILTER_LINEAR,
.wrap_u = SG_WRAP_REPEAT,
.wrap_v = SG_WRAP_REPEAT
});
#ifndef NDEBUG #ifndef NDEBUG
sg_trace_hooks hh = sg_install_trace_hooks(&hooks); sg_trace_hooks hh = sg_install_trace_hooks(&hooks);
#endif #endif
font_init(); font_init();
debugdraw_init();
sprite_initialize();
model_init(); sg_features feat = sg_query_features();
TOPLEFT = feat.origin_top_left;
sg_color c = (sg_color){0,0,0,1}; sg_color c = (sg_color){0,0,0,1};
pass_action = (sg_pass_action){ pass_action = (sg_pass_action){
.colors[0] = {.load_action = SG_LOADACTION_CLEAR, .clear_value = c}, .colors[0] = {.load_action = SG_LOADACTION_CLEAR, .clear_value = c},
}; };
sg_gif.shader = sg_make_shader(box_shader_desc(sg_query_backend())); sg_color oc = (sg_color){35.0/255,60.0/255,92.0/255,1};
off_action = (sg_pass_action){
sg_gif.pipe = sg_make_pipeline(&(sg_pipeline_desc){ .colors[0] = {.load_action = SG_LOADACTION_CLEAR, .clear_value = oc},
.shader = sg_gif.shader, .depth = {
.layout = { .load_action = SG_LOADACTION_CLEAR,
.attrs = { .clear_value = 1
[0].format = SG_VERTEXFORMAT_FLOAT2, //.store_action = SG_STOREACTION_STORE
[1].format = SG_VERTEXFORMAT_FLOAT2 }
}
},
.colors[0].pixel_format = SG_PIXELFORMAT_RGBA8,
.label = "gif pipe",
});
float crt_quad[] = {
-1, 1, 0, 1,
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, -1, 1, 0,
1, 1, 1, 1
}; };
float gif_quad[] = { screencolor = sg_make_image(&(sg_image_desc){
-1, 1, 0, 1, .render_target = true,
-1, -1, 0, 0, .width = 500,
1, -1, 1, 0, .height = 500,
-1, 1, 0, 1, .pixel_format = sapp_color_format(),
1, -1, 1, 0, .sample_count = 1,
1, 1, 1, 1 });
screendepth = sg_make_image(&(sg_image_desc){
.render_target = true,
.width =500,
.height=500,
.pixel_format = sapp_depth_format(),
.sample_count = 1
});
offscreen = (sg_pass){
.attachments = sg_make_attachments(&(sg_attachments_desc){
.colors[0].image = screencolor,
.depth_stencil.image = screendepth,
}),
.action = off_action,
}; };
sg_gif.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(gif_quad),
.data = gif_quad,
.label = "gif vert buffer",
});
sg_gif.bind.fs.samplers[0] = sg_make_sampler(&(sg_sampler_desc){});
sg_crt.shader = sg_make_shader(crt_shader_desc(sg_query_backend()));
sg_crt.pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = sg_crt.shader,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2
}
},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
});
sg_crt.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(crt_quad),
.type = SG_BUFFERTYPE_VERTEXBUFFER,
.usage = SG_USAGE_IMMUTABLE,
.data = SG_RANGE(crt_quad),
.label = "crt vert buffer",
});
} }
HMM_Mat4 projection = {0.f}; HMM_Mat4 projection = {0.f};
HMM_Mat4 hudproj = {0.f}; HMM_Mat4 hudproj = {0.f};
HMM_Mat4 useproj = {0}; HMM_Mat4 useproj = {0};
#define MODE_STRETCH 0 void openglRender(HMM_Vec2 usesize) {
#define MODE_KEEP 1 if (usesize.x != lastuse.x || usesize.y != lastuse.y) {
#define MODE_WIDTH 2 sg_destroy_image(screencolor);
#define MODE_HEIGHT 3 sg_destroy_image(screendepth);
#define MODE_EXPAND 4 sg_destroy_attachments(offscreen.attachments);
#define MODE_FULL 5 screencolor = sg_make_image(&(sg_image_desc){
.render_target = true,
void openglRender(struct window *window, gameobject *cam, float zoom) { .width = usesize.x,
sg_swapchain sch = sglue_swapchain(); .height = usesize.y,
sg_begin_pass(&(sg_pass){ .pixel_format = sapp_color_format(),
.action = pass_action, .sample_count = 1,
.swapchain = sglue_swapchain(),
.label = "window pass"
}); });
screendepth = sg_make_image(&(sg_image_desc){
.render_target = true,
.width =usesize.x,
.height=usesize.y,
.pixel_format = sapp_depth_format(),
.sample_count = 1
});
offscreen = (sg_pass){
.attachments = sg_make_attachments(&(sg_attachments_desc){
.colors[0].image = screencolor,
.depth_stencil.image = screendepth
}),
.action = off_action,
};
}
lastuse = usesize;
HMM_Vec2 usesize = window->rendersize; sg_begin_pass(&offscreen);
switch(window->mode) {
case MODE_STRETCH:
sg_apply_viewportf(0,0,window->size.x,window->size.y,1);
break;
case MODE_WIDTH:
sg_apply_viewportf(0, window->top, window->size.x, window->psize.y,1); // keep width
break;
case MODE_HEIGHT:
sg_apply_viewportf(window->left,0,window->psize.x, window->size.y,1); // keep height
break;
case MODE_KEEP:
sg_apply_viewportf(0,0,window->rendersize.x, window->rendersize.y, 1); // no scaling
break;
case MODE_EXPAND:
if (window->aspect < window->raspect)
sg_apply_viewportf(0, window->top, window->size.x, window->psize.y,1); // keep width
else
sg_apply_viewportf(window->left,0,window->psize.x, window->size.y,1); // keep height
break;
case MODE_FULL:
usesize = window->size;
break;
}
// 2D projection
campos = go_pos(cam);
camzoom = zoom;
projection = HMM_Orthographic_RH_NO(
campos.x - camzoom * usesize.x / 2,
campos.x + camzoom * usesize.x / 2,
campos.y - camzoom * usesize.y / 2,
campos.y + camzoom * usesize.y / 2, -10000.f, 10000.f);
/* if (gif.rec && (apptime() - gif.timer) > gif.spf) {
sg_begin_pass(&(sg_pass){
.action = pass_action,
.swapchain = sg_gif.swap
});
sg_apply_pipeline(sg_gif.pipe);
sg_apply_bindings(&sg_gif.bind);
sg_draw(0,6,1);
sg_end_pass();
gif.timer = apptime();
sg_query_image_pixels(sg_gif.img, crt_post.bind.fs.samplers[0], gif.buffer, gif.w*gif.h*4);
msf_gif_frame(&gif_state, gif.buffer, gif.cpf, gif.depth, gif.w * -4);
}
*/
} }
struct boundingbox cwh2bb(HMM_Vec2 c, HMM_Vec2 wh) { struct boundingbox cwh2bb(HMM_Vec2 c, HMM_Vec2 wh) {

View file

@ -1,19 +1,13 @@
#ifndef OPENGL_RENDER_H #ifndef OPENGL_RENDER_H
#define OPENGL_RENDER_H #define OPENGL_RENDER_H
#if defined __linux__ #include "config.h"
#define SOKOL_GLCORE33
#elif __EMSCRIPTEN__
#define SOKOL_GLES3
#elif __WIN32
#define SOKOL_D3D11
#elif __APPLE__
#define SOKOL_METAL
#endif
#include "sokol/sokol_gfx.h" #include "sokol/sokol_gfx.h"
#include "HandmadeMath.h" #include "HandmadeMath.h"
#include "gameobject.h" #include "gameobject.h"
#include "transform.h"
#include "model.h"
#define RGBA_MAX 255 #define RGBA_MAX 255
@ -22,8 +16,7 @@
extern struct rgba color_white; extern struct rgba color_white;
extern struct rgba color_black; extern struct rgba color_black;
extern struct rgba color_clear; extern struct rgba color_clear;
extern int TOPLEFT;
extern int renderMode;
extern HMM_Vec3 dirl_pos; extern HMM_Vec3 dirl_pos;
@ -32,35 +25,23 @@ extern HMM_Mat4 hudproj;
extern HMM_Mat4 useproj; extern HMM_Mat4 useproj;
extern sg_pass_action pass_action; extern sg_pass_action pass_action;
struct draw_p { extern sg_sampler std_sampler;
float x; extern sg_sampler tex_sampler;
float y; extern sg_image screencolor;
}; extern sg_image screendepth;
struct draw_p3 { typedef struct viewstate {
float x; HMM_Mat4 v;
float y; HMM_Mat4 p;
float z; HMM_Mat4 vp;
}; } viewstate;
#include <chipmunk/chipmunk.h> extern viewstate globalview;
enum RenderMode {
LIT,
UNLIT,
WIREFRAME,
DIRSHADOWMAP,
OBJECTPICKER
};
void render_init(); void render_init();
extern HMM_Vec2 campos;
extern float camzoom;
void openglRender(struct window *window, gameobject *cam, float zoom); void openglRender(HMM_Vec2 usesize);
void opengl_rendermode(enum RenderMode r);
void openglInit3d(struct window *window);
void capture_screen(int x, int y, int w, int h, const char *path); void capture_screen(int x, int y, int w, int h, const char *path);
void gif_rec_start(int w, int h, int cpf, int bitdepth); void gif_rec_start(int w, int h, int cpf, int bitdepth);

View file

@ -126,6 +126,17 @@ char *dirname(const char *path)
return dir; return dir;
} }
char *makepath(char *dir, char *file)
{
int d = strlen(dir) + strlen(file) + 2;
char *path = malloc(d);
path[0] = 0;
strncat(path, dir, d);
strncat(path, "/", d);
strncat(path, file, d);
return path;
}
char *seprint(char *fmt, ...) char *seprint(char *fmt, ...)
{ {
va_list args; va_list args;
@ -156,7 +167,20 @@ static int ls_ftw(const char *path, const struct stat *sb, int typeflag)
time_t file_mod_secs(const char *file) { time_t file_mod_secs(const char *file) {
struct stat attr; struct stat attr;
stat(file, &attr); mz_uint index;
mz_zip_archive_file_stat pstat;
if ((index = mz_zip_reader_locate_file(&game_cdb, file, NULL, 0)) != -1) {
mz_zip_reader_file_stat(&game_cdb, index,&pstat);
return pstat.m_time;
}
else if ((index = mz_zip_reader_locate_file(&corecdb, file, NULL, 0)) != -1) {
mz_zip_reader_file_stat(&corecdb, index, &pstat);
return pstat.m_time;
}
else
stat(file, &attr);
return attr.st_mtime; return attr.st_mtime;
} }
@ -250,9 +274,9 @@ void *slurp_file(const char *filename, size_t *size)
void *ret; void *ret;
if (!access(filename, R_OK)) if (!access(filename, R_OK))
return os_slurp(filename, size); return os_slurp(filename, size);
else if (ret = mz_zip_reader_extract_file_to_heap(&game_cdb, filename, size, 0)) else if ((ret = mz_zip_reader_extract_file_to_heap(&game_cdb, filename, size, 0)))
return ret; return ret;
else if (ret = mz_zip_reader_extract_file_to_heap(&corecdb, filename, size, 0)) else if ((ret = mz_zip_reader_extract_file_to_heap(&corecdb, filename, size, 0)))
return ret; return ret;
return NULL; return NULL;
@ -323,7 +347,7 @@ int mkpath(char *path, mode_t mode)
return 0; return 0;
} }
int slurp_write(const char *txt, const char *filename, size_t len) { int slurp_write(void *txt, const char *filename, size_t len) {
FILE *f = fopen(filename, "w"); FILE *f = fopen(filename, "w");
if (!f) return 1; if (!f) return 1;

View file

@ -11,7 +11,8 @@ extern int LOADED_GAME;
void resources_init(); void resources_init();
char *get_filename_from_path(char *path, int extension); char *get_filename_from_path(char *path, int extension);
char *get_directory_from_path(char *path); char *dirname(const char *path);
char *makepath(char *dir, char *file);
char *str_replace_ext(const char *s, const char *newext); char *str_replace_ext(const char *s, const char *newext);
FILE *res_open(char *path, const char *tag); FILE *res_open(char *path, const char *tag);
char **ls(const char *path); char **ls(const char *path);
@ -22,11 +23,9 @@ void pack_start(const char *name);
void pack_add(const char *path); void pack_add(const char *path);
void pack_end(); void pack_end();
char *dirname(const char *path);
void *slurp_file(const char *filename, size_t *size); void *slurp_file(const char *filename, size_t *size);
char *slurp_text(const char *filename, size_t *size); char *slurp_text(const char *filename, size_t *size);
int slurp_write(const char *txt, const char *filename, size_t len); int slurp_write(void *txt, const char *filename, size_t len);
char *seprint(char *fmt, ...); char *seprint(char *fmt, ...);

View file

@ -1,6 +1,10 @@
#ifndef SCRIPT_H #ifndef SCRIPT_H
#define SCRIPT_H #define SCRIPT_H
#ifdef __cplusplus
extern "C" {
#endif
#include "quickjs/quickjs.h" #include "quickjs/quickjs.h"
#include <time.h> #include <time.h>
@ -26,4 +30,8 @@ void script_call_sym(JSValue sym, int argc, JSValue *argv);
void script_gc(); void script_gc();
#ifdef __cplusplus
}
#endif
#endif #endif

View file

@ -21,18 +21,18 @@ typedef union NoiseUnion {
noiseNDptr pn; noiseNDptr pn;
} genericNoise; } genericNoise;
extern double Noise2D(double x, double y); double Noise2D(double x, double y);
extern double Noise3D(double x, double y, double z); double Noise3D(double x, double y, double z);
extern double Noise4D(double x, double y, double z, double w); double Noise4D(double x, double y, double z, double w);
extern double GBlur1D(double stdDev, double x); double GBlur1D(double stdDev, double x);
extern double GBlur2D(double stdDev, double x, double y); double GBlur2D(double stdDev, double x, double y);
extern double Noise(genericNoise func, int len, double args[]); double Noise(genericNoise func, int len, double args[]);
extern double TurbulentNoise(genericNoise func, int direction, int iterations, int len, double args[]); double TurbulentNoise(genericNoise func, int direction, int iterations, int len, double args[]);
extern double FractalSumNoise(genericNoise func, int iterations, int len, double args[]); double FractalSumNoise(genericNoise func, int iterations, int len, double args[]);
extern double FractalSumAbsNoise(genericNoise func, int iterations, int len, double args[]); double FractalSumAbsNoise(genericNoise func, int iterations, int len, double args[]);
double octave_3d(double x, double y, double z, int octaves, double persistence); double octave_3d(double x, double y, double z, int octaves, double persistence);

View file

@ -1,306 +0,0 @@
/*
sokol_gfx_ext.h - extensions for sokol_gfx
https://github.com/edubart/sokol_gp
*/
#if defined(SOKOL_IMPL) && !defined(SOKOL_GFX_EXT_IMPL)
#define SOKOL_GFX_EXT_IMPL
#endif
#ifndef SOKOL_GFX_EXT_INCLUDED
#define SOKOL_GFX_EXT_INCLUDED
#ifndef SOKOL_GFX_INCLUDED
#error "Please include sokol_gfx.h before sokol_gfx_ext.h"
#endif
#include <stdbool.h>
#include <stdint.h>
SOKOL_GFX_API_DECL void sg_query_image_pixels(sg_image img_id, sg_sampler smp_id, void* pixels, int size);
SOKOL_GFX_API_DECL void sg_query_pixels(int x, int y, int w, int h, bool origin_top_left, void *pixels, int size);
#endif // SOKOL_GFX_EXT_INCLUDED
#ifdef SOKOL_GFX_EXT_IMPL
#ifndef SOKOL_GFX_EXT_IMPL_INCLUDED
#define SOKOL_GFX_EXT_IMPL_INCLUDED
#ifndef SOKOL_GFX_IMPL_INCLUDED
#error "Please include sokol_gfx.h implementation before sokol_gp.h implementation"
#endif
#if defined(_SOKOL_ANY_GL)
static void _sg_gl_query_image_pixels(_sg_image_t* img, _sg_sampler_t *smp, void* pixels) {
SOKOL_ASSERT(img->gl.target == GL_TEXTURE_2D);
SOKOL_ASSERT(0 != img->gl.tex[img->cmn.active_slot]);
#if defined(SOKOL_GLCORE33)
_sg_gl_cache_store_texture_sampler_binding(0);
_sg_gl_cache_bind_texture_sampler(0, img->gl.target, img->gl.tex[img->cmn.active_slot], smp->gl.smp);
glGetTexImage(img->gl.target, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
_SG_GL_CHECK_ERROR();
_sg_gl_cache_restore_texture_sampler_binding(0);
#else
static GLuint newFbo = 0;
GLuint oldFbo = 0;
if(newFbo == 0) {
glGenFramebuffers(1, &newFbo);
}
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&oldFbo);
glBindFramebuffer(GL_FRAMEBUFFER, newFbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, img->gl.tex[img->cmn.active_slot], 0);
glReadPixels(0, 0, img->cmn.width, img->cmn.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, oldFbo);
//glDeleteFramebuffers(1, &newFbo);
_SG_GL_CHECK_ERROR();
#endif
}
static void _sg_gl_query_pixels(int x, int y, int w, int h, bool origin_top_left, void *pixels) {
SOKOL_ASSERT(pixels);
GLuint gl_fb;
GLint dims[4];
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&gl_fb);
_SG_GL_CHECK_ERROR();
glGetIntegerv(GL_VIEWPORT, dims);
int cur_height = dims[3];
y = origin_top_left ? (cur_height - (y+h)) : y;
_SG_GL_CHECK_ERROR();
#if defined(SOKOL_GLES2) // use NV extension instead
glReadBufferNV(gl_fb == 0 ? GL_BACK : GL_COLOR_ATTACHMENT0);
#else
glReadBuffer(gl_fb == 0 ? GL_BACK : GL_COLOR_ATTACHMENT0);
#endif
_SG_GL_CHECK_ERROR();
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
_SG_GL_CHECK_ERROR();
}
#elif defined(SOKOL_D3D11)
static inline void _sgext_d3d11_Texture2D_GetDesc(ID3D11Texture2D* self, D3D11_TEXTURE2D_DESC* pDesc) {
self->lpVtbl->GetDesc(self, pDesc);
}
static inline void _sgext_d3d11_SamplerState_GetDesc(ID3D11SamplerState* self, D3D11_SAMPLER_DESC* pDesc) {
self->lpVtbl->GetDesc(self, pDesc);
}
static inline void _sgext_d3d11_CopySubresourceRegion(ID3D11DeviceContext* self, ID3D11Resource *pDstResource, UINT DstSubresource, UINT DstX, UINT DstY, UINT DstZ, ID3D11Resource *pSrcResource, UINT SrcSubresource, const D3D11_BOX *pSrcBox) {
self->lpVtbl->CopySubresourceRegion(self, pDstResource, DstSubresource, DstX, DstY, DstZ, pSrcResource, SrcSubresource, pSrcBox);
}
static inline void _sgext_d3d11_OMGetRenderTargets(ID3D11DeviceContext* self, UINT NumViews, ID3D11RenderTargetView **ppRenderTargetViews, ID3D11DepthStencilView **ppDepthStencilView) {
self->lpVtbl->OMGetRenderTargets(self, NumViews, ppRenderTargetViews, ppDepthStencilView);
}
static inline void _sgext_d3d11_RenderTargetView_GetResource(ID3D11RenderTargetView* self, ID3D11Resource** ppResource) {
self->lpVtbl->GetResource(self, ppResource);
}
static void _sg_d3d11_query_image_pixels(_sg_image_t* img, void* pixels) {
SOKOL_ASSERT(_sg.d3d11.ctx);
SOKOL_ASSERT(img->d3d11.tex2d);
HRESULT hr;
_SOKOL_UNUSED(hr);
// create staging texture
ID3D11Texture2D* staging_tex = NULL;
D3D11_TEXTURE2D_DESC staging_desc = {
.Width = (UINT)img->cmn.width,
.Height = (UINT)img->cmn.height,
.MipLevels = 1,
.ArraySize = 1,
.Format = img->d3d11.format,
.SampleDesc = {
.Count = 1,
.Quality = 0,
},
.Usage = D3D11_USAGE_STAGING,
.BindFlags = 0,
.CPUAccessFlags = D3D11_CPU_ACCESS_READ,
.MiscFlags = 0
};
hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &staging_desc, NULL, &staging_tex);
SOKOL_ASSERT(SUCCEEDED(hr));
// copy pixels to staging texture
_sgext_d3d11_CopySubresourceRegion(_sg.d3d11.ctx,
(ID3D11Resource*)staging_tex,
0, 0, 0, 0,
(ID3D11Resource*)img->d3d11.tex2d,
0, NULL);
// map the staging texture's data to CPU-accessible memory
D3D11_MAPPED_SUBRESOURCE msr = {.pData = NULL};
hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)staging_tex, 0, D3D11_MAP_READ, 0, &msr);
SOKOL_ASSERT(SUCCEEDED(hr));
memcpy(pixels, msr.pData, img->cmn.width * img->cmn.height * 4);
// unmap the texture
_sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)staging_tex, 0);
if(staging_tex) _sg_d3d11_Release(staging_tex);
}
static void _sg_d3d11_query_pixels(int x, int y, int w, int h, bool origin_top_left, void *pixels) {
// get current render target
ID3D11RenderTargetView* render_target_view = NULL;
_sgext_d3d11_OMGetRenderTargets(_sg.d3d11.ctx, 1, &render_target_view, NULL);
// fallback to window render target
if(!render_target_view)
render_target_view = (ID3D11RenderTargetView*)_sg.d3d11.cur_pass.render_view;
SOKOL_ASSERT(render_target_view);
// get the back buffer texture
ID3D11Texture2D *back_buffer = NULL;
_sgext_d3d11_RenderTargetView_GetResource(render_target_view, (ID3D11Resource**)&back_buffer);
SOKOL_ASSERT(back_buffer);
// create a staging texture to copy the screen's data to
D3D11_TEXTURE2D_DESC staging_desc;
_sgext_d3d11_Texture2D_GetDesc(back_buffer, &staging_desc);
staging_desc.Width = w;
staging_desc.Height = h;
staging_desc.BindFlags = 0;
staging_desc.MiscFlags = 0;
staging_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
staging_desc.Usage = D3D11_USAGE_STAGING;
ID3D11Texture2D *staging_tex = NULL;
HRESULT hr = _sg_d3d11_CreateTexture2D(_sg.d3d11.dev, &staging_desc, NULL, &staging_tex);
SOKOL_ASSERT(SUCCEEDED(hr));
_SOKOL_UNUSED(hr);
// copy the desired portion of the back buffer to the staging texture
// y = (origin_top_left ? y : (_sg.d3d11.cur_height - (y + h)));
D3D11_BOX src_box = {
.left = (UINT)x,
.top = (UINT)y,
.front = 0,
.right = (UINT)(x + w),
.bottom = (UINT)(y + w),
.back = 1,
};
_sgext_d3d11_CopySubresourceRegion(_sg.d3d11.ctx,
(ID3D11Resource*)staging_tex,
0, 0, 0, 0,
(ID3D11Resource*)back_buffer,
0, &src_box);
// map the staging texture's data to CPU-accessible memory
D3D11_MAPPED_SUBRESOURCE msr = {.pData = NULL};
hr = _sg_d3d11_Map(_sg.d3d11.ctx, (ID3D11Resource*)staging_tex, 0, D3D11_MAP_READ, 0, &msr);
SOKOL_ASSERT(SUCCEEDED(hr));
memcpy(pixels, msr.pData, w * h * 4);
// unmap the texture
_sg_d3d11_Unmap(_sg.d3d11.ctx, (ID3D11Resource*)staging_tex, 0);
if(back_buffer) _sg_d3d11_Release(back_buffer);
if(staging_tex) _sg_d3d11_Release(staging_tex);
}
#elif defined(SOKOL_METAL)
#ifdef TARGET_OS_IPHONE
static void _sg_metal_commit_command_buffer(){};
static void _sg_metal_encode_texture_pixels(int x, int y, int w, int h, bool origin_top_left, id<MTLTexture> mtl_src_texture, void* pixels) {};
static void _sg_metal_query_image_pixels(_sg_image_t* img, void* pixels) {};
static void _sg_metal_query_pixels(int x, int y, int w, int h, bool origin_top_left, void *pixels) {};
#else
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
static void _sg_metal_commit_command_buffer() {
SOKOL_ASSERT(!_sg.mtl.in_pass);
if(_sg.mtl.cmd_buffer) {
#if defined(_SG_TARGET_MACOS)
[_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] didModifyRange:NSMakeRange(0, _sg.mtl.cur_ub_offset)];
#endif
[_sg.mtl.cmd_buffer commit];
[_sg.mtl.cmd_buffer waitUntilCompleted];
_sg.mtl.cmd_buffer = [_sg.mtl.cmd_queue commandBufferWithUnretainedReferences];
}
}
static void _sg_metal_encode_texture_pixels(int x, int y, int w, int h, bool origin_top_left, id<MTLTexture> mtl_src_texture, void* pixels) {
SOKOL_ASSERT(!_sg.mtl.in_pass);
_sg_metal_commit_command_buffer();
MTLTextureDescriptor* mtl_dst_texture_desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mtl_src_texture.pixelFormat width:w height:h mipmapped:NO];
mtl_dst_texture_desc.storageMode = MTLStorageModeManaged;
mtl_dst_texture_desc.resourceOptions = MTLResourceStorageModeManaged;
mtl_dst_texture_desc.usage = MTLTextureUsageShaderRead + MTLTextureUsageShaderWrite;
id<MTLTexture> mtl_dst_texture = [mtl_src_texture.device newTextureWithDescriptor:mtl_dst_texture_desc];
id<MTLCommandBuffer> cmd_buffer = [_sg.mtl.cmd_queue commandBuffer];
id<MTLBlitCommandEncoder> blit_encoder = [cmd_buffer blitCommandEncoder];
[blit_encoder copyFromTexture:mtl_src_texture
sourceSlice:0
sourceLevel:0
sourceOrigin:MTLOriginMake(x,(origin_top_left ? y : (mtl_src_texture.height - (y + h))),0)
sourceSize:MTLSizeMake(w,h,1)
toTexture:mtl_dst_texture
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(0,0,0)
];
[blit_encoder synchronizeTexture:mtl_dst_texture slice:0 level:0];
[blit_encoder endEncoding];
[cmd_buffer commit];
[cmd_buffer waitUntilCompleted];
MTLRegion mtl_region = MTLRegionMake2D(0, 0, w, h);
[mtl_dst_texture getBytes:pixels bytesPerRow:w * 4 fromRegion:mtl_region mipmapLevel:0];
}
static void _sg_metal_query_image_pixels(_sg_image_t* img, void* pixels) {
id<MTLTexture> mtl_src_texture = _sg.mtl.idpool.pool[img->mtl.tex[0]];
_sg_metal_encode_texture_pixels(0, 0, mtl_src_texture.width, mtl_src_texture.height, true, mtl_src_texture, pixels);
}
static void _sg_metal_query_pixels(int x, int y, int w, int h, bool origin_top_left, void *pixels) {
id<CAMetalDrawable> mtl_drawable = (__bridge id<CAMetalDrawable>)_sg.mtl.drawable_cb();
_sg_metal_encode_texture_pixels(x, y, w, h, origin_top_left, mtl_drawable.texture, pixels);
}
#endif
#endif
void sg_query_image_pixels(sg_image img_id, sg_sampler smp_id, void* pixels, int size) {
SOKOL_ASSERT(pixels);
SOKOL_ASSERT(img_id.id != SG_INVALID_ID);
_sg_image_t* img = _sg_lookup_image(&_sg.pools, img_id.id);
_sg_sampler_t *smp = _sg_lookup_sampler(&_sg.pools, smp_id.id);
SOKOL_ASSERT(img);
SOKOL_ASSERT(size >= (img->cmn.width * img->cmn.height * 4));
_SOKOL_UNUSED(size);
#if defined(_SOKOL_ANY_GL)
_sg_gl_query_image_pixels(img, smp, pixels);
#elif defined(SOKOL_D3D11)
_sg_d3d11_query_image_pixels(img, pixels);
#elif defined(SOKOL_METAL)
_sg_metal_query_image_pixels(img, pixels);
#endif
}
void sg_query_pixels(int x, int y, int w, int h, bool origin_top_left, void *pixels, int size) {
SOKOL_ASSERT(pixels);
SOKOL_ASSERT(size >= w*h);
_SOKOL_UNUSED(size);
#if defined(_SOKOL_ANY_GL)
_sg_gl_query_pixels(x, y, w, h, origin_top_left, pixels);
#elif defined(SOKOL_D3D11)
_sg_d3d11_query_pixels(x, y, w, h, origin_top_left, pixels);
#elif defined(SOKOL_METAL)
_sg_metal_query_pixels(x, y, w, h, origin_top_left, pixels);
#endif
}
#endif // SOKOL_GFX_EXT_IMPL_INCLUDED
#endif // SOKOL_GFX_EXT_IMPL

View file

@ -195,13 +195,6 @@ void unplug_node(dsp_node *node)
} }
} }
typedef struct {
float amp;
float freq;
float phase; /* from 0 to 1, marking where we are */
float (*filter)(float phase);
} phasor;
float sin_phasor(float p) float sin_phasor(float p)
{ {
return sin(2*PI*p); return sin(2*PI*p);
@ -395,7 +388,6 @@ void filter_iir(struct dsp_iir *iir, soundbyte *buffer, int frames)
} }
} }
dsp_node *dsp_lpf(float freq) dsp_node *dsp_lpf(float freq)
{ {
struct dsp_iir *iir = malloc(sizeof(*iir)); struct dsp_iir *iir = malloc(sizeof(*iir));
@ -624,11 +616,6 @@ void dsp_mono(void *p, soundbyte *restrict out, int n)
} }
} }
struct bitcrush {
float sr;
float depth;
};
#define ROUND(f) ((float)((f>0.0)?floor(f+0.5):ceil(f-0.5))) #define ROUND(f) ((float)((f>0.0)?floor(f+0.5):ceil(f-0.5)))
void filter_bitcrush(struct bitcrush *restrict b, soundbyte *restrict out, int frames) void filter_bitcrush(struct bitcrush *restrict b, soundbyte *restrict out, int frames)
{ {

View file

@ -51,7 +51,7 @@ dsp_node *dsp_hpf(float freq);
dsp_node *dsp_lpf(float freq); dsp_node *dsp_lpf(float freq);
/* atk, dec, sus, rls specify the time, in miliseconds, the phase begins */ /* atk, dec, sus, rls specify the time, in miliseconds, the phase begins */
struct dsp_adsr { typedef struct dsp_adsr {
unsigned int atk; unsigned int atk;
double atk_t; double atk_t;
unsigned int dec; unsigned int dec;
@ -63,7 +63,7 @@ struct dsp_adsr {
double time; /* Current time of the filter */ double time; /* Current time of the filter */
float out; float out;
}; } adsr;
dsp_node *dsp_adsr(unsigned int atk, unsigned int dec, unsigned int sus, unsigned int rls); dsp_node *dsp_adsr(unsigned int atk, unsigned int dec, unsigned int sus, unsigned int rls);
@ -77,7 +77,7 @@ dsp_node *dsp_delay(double sec, double decay);
dsp_node *dsp_fwd_delay(double sec, double decay); dsp_node *dsp_fwd_delay(double sec, double decay);
dsp_node *dsp_pitchshift(float octaves); dsp_node *dsp_pitchshift(float octaves);
struct dsp_compressor { typedef struct dsp_compressor {
double ratio; double ratio;
double threshold; double threshold;
float target; float target;
@ -85,7 +85,7 @@ struct dsp_compressor {
double atk_tau; double atk_tau;
unsigned int rls; /* MIlliseconds */ unsigned int rls; /* MIlliseconds */
double rls_tau; double rls_tau;
}; } compressor;
dsp_node *dsp_compressor(); dsp_node *dsp_compressor();
@ -106,6 +106,19 @@ float tri_phasor(float p);
dsp_node *dsp_reverb(); dsp_node *dsp_reverb();
dsp_node *dsp_sinewave(float amp, float freq); dsp_node *dsp_sinewave(float amp, float freq);
dsp_node *dsp_square(float amp, float freq, int sr, int ch); dsp_node *dsp_square(float amp, float freq, int sr, int ch);
typedef struct {
float amp;
float freq;
float phase; /* from 0 to 1, marking where we are */
float (*filter)(float phase);
} phasor;
typedef struct bitcrush {
float sr;
float depth;
} bitcrush;
dsp_node *dsp_bitcrush(float sr, float res); dsp_node *dsp_bitcrush(float sr, float res);
void dsp_mono(void *p, soundbyte *out, int n); void dsp_mono(void *p, soundbyte *out, int n);
void pan_frames(soundbyte *out, float deg, int frames); void pan_frames(soundbyte *out, float deg, int frames);

View file

@ -5,12 +5,12 @@
#include "tml.h" #include "tml.h"
#include "dsp.h" #include "dsp.h"
struct dsp_midi_song { typedef struct dsp_midi_song {
float bpm; float bpm;
double time; double time;
tml_message *midi; tml_message *midi;
tsf *sf; tsf *sf;
}; } midi;
dsp_node *dsp_midi(const char *midi, tsf *sf); dsp_node *dsp_midi(const char *midi, tsf *sf);
tsf *make_soundfont(const char *sf); tsf *make_soundfont(const char *sf);

View file

@ -1,227 +0,0 @@
#include "sprite.h"
#include "gameobject.h"
#include "log.h"
#include "render.h"
#include "stb_ds.h"
#include "texture.h"
#include "HandmadeMath.h"
#include "sprite.sglsl.h"
#include "9slice.sglsl.h"
static sprite **sprites = NULL;
static sg_shader shader_sprite;
static sg_pipeline pip_sprite;
static sg_bindings bind_sprite;
struct sprite_vert {
HMM_Vec2 pos;
HMM_Vec2 uv;
struct rgba color;
struct rgba emissive;
};
static int num_spriteverts = 5000;
static sg_shader slice9_shader;
static sg_pipeline slice9_pipe;
static sg_bindings slice9_bind;
static float slice9_points[8] = {
0.0, 0.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0
};
struct slice9_vert {
HMM_Vec2 pos;
struct uv_n uv;
unsigned short border[4];
HMM_Vec2 scale;
struct rgba color;
};
sprite *sprite_make()
{
sprite *sp = calloc(sizeof(*sp), 1);
sp->pos = (HMM_Vec2){0,0};
sp->scale = (HMM_Vec2){1,1};
sp->angle = 0;
sp->color = color_white;
sp->emissive = color_clear;
sp->go = NULL;
sp->tex = NULL;
sp->frame = ST_UNIT;
sp->drawmode = DRAW_SIMPLE;
sp->enabled = 1;
sp->parallax = 1;
arrpush(sprites,sp);
return sp;
}
void sprite_free(sprite *sprite)
{
free(sprite);
for (int i = arrlen(sprites)-1; i >= 0; i--)
if (sprites[i] == sprite) {
arrdelswap(sprites,i);
return;
}
}
static int sprite_count = 0;
void sprite_flush() { sprite_count = 0; }
int sprite_sort(sprite **sa, sprite **sb)
{
sprite *a = *sa;
sprite *b = *sb;
struct gameobject *goa = a->go;
struct gameobject *gob= b->go;
if (!goa && !gob) return 0;
if (!goa) return -1;
if (!gob) return 1;
if (goa->drawlayer > gob->drawlayer) return 1;
if (gob->drawlayer > goa->drawlayer) return -1;
if (*sa > *sb) return 1;
return -1;
}
void sprite_draw_all() {
if (arrlen(sprites) == 0) return;
sg_apply_pipeline(pip_sprite);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(useproj));
qsort(sprites, arrlen(sprites), sizeof(*sprites), sprite_sort);
for (int i = 0; i < arrlen(sprites); i++) {
if (!sprites[i]->enabled) continue;
sprite_draw(sprites[i]);
}
}
void sprite_initialize() {
shader_sprite = sg_make_shader(sprite_shader_desc(sg_query_backend()));
pip_sprite = sg_make_pipeline(&(sg_pipeline_desc){
.shader = shader_sprite,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2,
[2].format = SG_VERTEXFORMAT_UBYTE4N,
[3].format = SG_VERTEXFORMAT_UBYTE4N}},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
.label = "sprite pipeline",
.colors[0].blend = blend_trans,
});
bind_sprite.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct sprite_vert) * num_spriteverts,
.type = SG_BUFFERTYPE_VERTEXBUFFER,
.usage = SG_USAGE_STREAM,
.label = "sprite vertex buffer",
});
bind_sprite.fs.samplers[0] = sg_make_sampler(&(sg_sampler_desc){});
slice9_shader = sg_make_shader(slice9_shader_desc(sg_query_backend()));
slice9_pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = slice9_shader,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2,
[2].format = SG_VERTEXFORMAT_USHORT4N,
[3].format = SG_VERTEXFORMAT_FLOAT2,
[4].format = SG_VERTEXFORMAT_UBYTE4N
}},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
});
slice9_bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct slice9_vert) * 100,
.type = SG_BUFFERTYPE_VERTEXBUFFER,
.usage = SG_USAGE_STREAM,
.label = "slice9 buffer"
});
}
void tex_draw(struct texture *tex, HMM_Mat3 m, struct rect r, struct rgba color, int wrap, HMM_Vec2 wrapoffset, HMM_Vec2 wrapscale, struct rgba emissive, float parallax) {
struct sprite_vert verts[4];
float w = tex->width*r.w;
float h = tex->height*r.h;
HMM_Vec2 sposes[4] = {
{0,0},
{w,0},
{0,h},
{w,h}
};
for (int i = 0; i < 4; i++) {
verts[i].pos = mat_t_pos(m, sposes[i]);
verts[i].color = color;
verts[i].emissive = emissive;
}
if (wrap) {
r.w *= wrapscale.x;
r.h *= wrapscale.y;
}
verts[0].uv.X = r.x;
verts[0].uv.Y = r.y+r.h;
verts[1].uv.X = r.x+r.w;
verts[1].uv.Y = r.y+r.h;
verts[2].uv.X = r.x;
verts[2].uv.Y = r.y;
verts[3].uv.X = r.x+r.w;
verts[3].uv.Y = r.y;
bind_sprite.fs.images[0] = tex->id;
sg_append_buffer(bind_sprite.vertex_buffers[0], SG_RANGE_REF(verts));
sg_apply_bindings(&bind_sprite);
sg_draw(sprite_count * 4, 4, 1);
sprite_count++;
}
transform2d sprite2t(sprite *s)
{
return (transform2d){
.pos = s->pos,
.scale = s->scale,
.angle = HMM_TurnToRad*s->angle
};
}
void sprite_draw(struct sprite *sprite) {
if (!sprite->tex) return;
transform2d t;
if (!sprite->go) t = t2d_unit;
else t = go2t(sprite->go);
t.pos.x += (campos.x - (campos.x/sprite->parallax));
t.pos.y += (campos.y - (campos.y/sprite->parallax));
HMM_Mat3 m = transform2d2mat(t);
HMM_Mat3 sm = transform2d2mat(sprite2t(sprite));
tex_draw(sprite->tex, HMM_MulM3(m,sm), sprite->frame, sprite->color, sprite->drawmode, (HMM_Vec2){0,0}, sprite->scale, sprite->emissive, sprite->parallax);
}
void gui_draw_img(texture *tex, transform2d t, int wrap, HMM_Vec2 wrapoffset, float wrapscale, struct rgba color) {
sg_apply_pipeline(pip_sprite);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(useproj));
tex_draw(tex, transform2d2mat(t), ST_UNIT, color, wrap, wrapoffset, (HMM_Vec2){wrapscale,wrapscale}, (struct rgba){0,0,0,0}, 0);
}
void slice9_draw(texture *tex, transform2d *t, HMM_Vec4 border, struct rgba color)
{
}

View file

@ -1,41 +0,0 @@
#ifndef SPRITE_H
#define SPRITE_H
#include "texture.h"
#include "HandmadeMath.h"
#include "render.h"
#include "transform.h"
#include "gameobject.h"
#define DRAW_SIMPLE 0
#define DRAW_TILE 1
struct sprite {
HMM_Vec2 pos;
HMM_Vec2 scale;
float angle;
struct rgba color;
struct rgba emissive;
gameobject *go;
texture *tex;
struct rect frame;
int enabled;
int drawmode;
float parallax;
unsigned int next;
};
typedef struct sprite sprite;
sprite *sprite_make();
int make_sprite(gameobject *go);
void sprite_free(sprite *sprite);
void sprite_delete(int id);
void sprite_initialize();
void sprite_draw(struct sprite *sprite);
void sprite_draw_all();
void sprite_flush();
void gui_draw_img(texture *tex, transform2d t, int wrap, HMM_Vec2 wrapoffset, float wrapscale, struct rgba color);
#endif

View file

@ -1,22 +1,103 @@
#include "steam.h" #include "steam.h"
/* #ifndef NSTEAM
#include <steam/steam_api.h> #include <steam/steam_api.h>
#include <steam/steam_api_flat.h> #include <steam/steam_api_flat.h>
#include "jsffi.h"
ISteamUserStats *stats = NULL;
ISteamApps *app = NULL;
ISteamRemoteStorage *remote = NULL;
ISteamUGC *ugc = NULL;
static JSValue js_steam_init(JSContext *js, JSValue this_v, int argc, JSValue *argv) static JSValue js_steam_init(JSContext *js, JSValue this_v, int argc, JSValue *argv)
{ {
SteamAPI_Init(); SteamErrMsg err;
return JS_UNDEFINED; SteamAPI_InitEx(&err);
JSValue str = str2js(err);
stats = SteamAPI_SteamUserStats();
app = SteamAPI_SteamApps();
remote = SteamAPI_SteamRemoteStorage();
ugc = SteamAPI_SteamUGC();
return str;
} }
static const JSCFunctionListEntry js_steam_funcs[] = { static const JSCFunctionListEntry js_steam_funcs[] = {
MIST_FUNC_DEF(steam, init, 1), MIST_FUNC_DEF(steam, init, 1),
}; };
JSC_SCALL(achievement_get32,
int32 data;
SteamAPI_ISteamUserStats_GetStatInt32(stats, str, &data);
return number2js(data);
)
JSC_SCALL(achievement_get,
bool data;
SteamAPI_ISteamUserStats_GetAchievement(stats, str, &data);
return boolean2js(data);
)
JSC_SCALL(achievement_set,
return boolean2js(SteamAPI_ISteamUserStats_SetAchievement(stats, str));
)
JSC_CCALL(achievement_num,
return number2js(SteamAPI_ISteamUserStats_GetNumAchievements(stats));
)
JSC_SCALL(achievement_clear, SteamAPI_ISteamUserStats_ClearAchievement(stats, str))
JSC_SCALL(achievement_user_get,
bool a;
boolean2js(SteamAPI_ISteamUserStats_GetUserAchievement(stats, js2uint64(argv[1]), str, &a));
ret = boolean2js(a);
)
static const JSCFunctionListEntry js_achievement_funcs[] = {
MIST_FUNC_DEF(achievement, clear, 1),
MIST_FUNC_DEF(achievement, get32, 2),
MIST_FUNC_DEF(achievement, get, 2),
MIST_FUNC_DEF(achievement, set, 1),
MIST_FUNC_DEF(achievement, num, 0),
MIST_FUNC_DEF(achievement, user_get, 2),
};
JSC_CCALL(cloud_app_enabled,
return boolean2js(SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp(remote))
)
JSC_CCALL(cloud_enable, SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp(remote, js2boolean(self)))
JSC_CCALL(cloud_account_enabled, return boolean2js(SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount(remote)))
static const JSCFunctionListEntry js_cloud_funcs[] = {
MIST_FUNC_DEF(cloud, app_enabled, 0),
MIST_FUNC_DEF(cloud, account_enabled, 0),
MIST_FUNC_DEF(cloud, enable, 1)
};
JSC_CCALL(app_owner,
uint64_t own = SteamAPI_ISteamApps_GetAppOwner(app);
return JS_NewBigUint64(js, own);
)
static const JSCFunctionListEntry js_app_funcs[] = {
MIST_FUNC_DEF(app, owner, 0),
};
JSValue js_init_steam(JSContext *js) JSValue js_init_steam(JSContext *js)
{ {
JSValue steam = JS_NewObject(js); JSValue steam = JS_NewObject(js);
JS_SetPropertyFunctionList(js, steam, js_steam_funcs, countof(js_steam_funcs)); JS_SetPropertyFunctionList(js, steam, js_steam_funcs, countof(js_steam_funcs));
JSValue achievements = JS_NewObject(js);
JS_SetPropertyFunctionList(js, achievements, js_achievement_funcs, countof(js_achievement_funcs));
js_setpropstr(steam, "achievements", achievements);
JSValue app = JS_NewObject(js);
JS_SetPropertyFunctionList(js, app, js_app_funcs, countof(js_app_funcs));
js_setpropstr(steam, "app", app);
JSValue cloud = JS_NewObject(js);
JS_SetPropertyFunctionList(js, cloud, js_cloud_funcs, countof(js_cloud_funcs));
js_setpropstr(steam, "cloud", cloud);
return steam; return steam;
} }
*/ #endif

View file

@ -5,15 +5,14 @@
#include "sokol/sokol_gfx.h" #include "sokol/sokol_gfx.h"
#include <math.h> #include <math.h>
#include <stb_image.h> #include <stb_image.h>
#include <stb_image_write.h>
#include "resources.h" #include "resources.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h" #include "stb_image_resize2.h"
#include <stdio.h> #include <stdio.h>
#define QOI_IMPLEMENTATION
#include "qoi.h" #include "qoi.h"
#ifndef NSVG #ifndef NSVG
@ -122,14 +121,10 @@ struct texture *texture_from_file(const char *path) {
unsigned int nw = next_pow2(tex->width); unsigned int nw = next_pow2(tex->width);
unsigned int nh = next_pow2(tex->height); unsigned int nh = next_pow2(tex->height);
int filter = SG_FILTER_NEAREST;
sg_image_data sg_img_data; sg_image_data sg_img_data;
sg_img_data.subimage[0][0] = (sg_range){.ptr = data, .size=tex->width*tex->height*4}; sg_img_data.subimage[0][0] = (sg_range){.ptr = data, .size=tex->width*tex->height*4};
/*
int mips = mip_levels(tex->width, tex->height)+1;
YughInfo("Has %d mip levels, from wxh %dx%d, pow2 is %ux%u.", mips, tex->width, tex->height,nw,nh); int mips = mip_levels(tex->width, tex->height)+1;
int mipw, miph; int mipw, miph;
mipw = tex->width; mipw = tex->width;
@ -151,18 +146,18 @@ struct texture *texture_from_file(const char *path) {
mipw = w; mipw = w;
miph = h; miph = h;
} }
*/
tex->id = sg_make_image(&(sg_image_desc){ tex->id = sg_make_image(&(sg_image_desc){
.type = SG_IMAGETYPE_2D, .type = SG_IMAGETYPE_2D,
.width = tex->width, .width = tex->width,
.height = tex->height, .height = tex->height,
.usage = SG_USAGE_IMMUTABLE, .usage = SG_USAGE_IMMUTABLE,
//.num_mipmaps = mips, .num_mipmaps = mips,
.data = sg_img_data .data = sg_img_data
}); });
/*for (int i = 1; i < mips; i++) for (int i = 1; i < mips; i++)
free(mipdata[i]);*/ free(mipdata[i]);
return tex; return tex;
} }
@ -170,12 +165,33 @@ struct texture *texture_from_file(const char *path) {
void texture_free(texture *tex) void texture_free(texture *tex)
{ {
if (!tex) return; if (!tex) return;
if (tex->data)
free(tex->data); free(tex->data);
if (tex->delays) arrfree(tex->delays); if (tex->delays) arrfree(tex->delays);
sg_destroy_image(tex->id); sg_destroy_image(tex->id);
free(tex); free(tex);
} }
struct texture *texture_empty(int w, int h, int n)
{
texture *tex = calloc(1,sizeof(*tex));
tex->data = calloc(w*h*n, sizeof(unsigned char));
tex->width = w;
tex->height = h;
sg_image_data sgdata;
sgdata.subimage[0][0] = (sg_range){.ptr = tex->data, .size = w*h*4};
tex->id = sg_make_image(&(sg_image_desc){
.type = SG_IMAGETYPE_2D,
.width = tex->width,
.height = tex->height,
.usage = SG_USAGE_IMMUTABLE,
.num_mipmaps = 1,
.data = sgdata,
});
return tex;
}
struct texture *texture_fromdata(void *raw, long size) struct texture *texture_fromdata(void *raw, long size)
{ {
struct texture *tex = calloc(1, sizeof(*tex)); struct texture *tex = calloc(1, sizeof(*tex));
@ -191,8 +207,6 @@ struct texture *texture_fromdata(void *raw, long size)
tex->data = data; tex->data = data;
int filter = SG_FILTER_NEAREST;
sg_image_data sg_img_data; sg_image_data sg_img_data;
int mips = mip_levels(tex->width, tex->height)+1; int mips = mip_levels(tex->width, tex->height)+1;
@ -265,6 +279,65 @@ double grad (int hash, double x, double y, double z)
}*/ }*/
} }
void texture_save(texture *tex, const char *file)
{
char *ext = strrchr(file, '.');
if (!strcmp(ext, ".png"))
stbi_write_png(file, tex->width, tex->height, 4, tex->data, 4*tex->width);
else if (!strcmp(ext, ".bmp"))
stbi_write_bmp(file, tex->width, tex->height, 4, tex->data);
else if (!strcmp(ext, ".tga"))
stbi_write_tga(file, tex->width, tex->height, 4, tex->data);
else if (!strcmp(ext, ".jpg") || !strcmp(ext, ".jpeg"))
stbi_write_jpg(file, tex->width, tex->height, 4, tex->data, 5);
}
void blit_image(uint8_t* src, uint8_t* dest, int src_width, int src_height, int dest_width, int dest_height, int sx, int sy, int sw, int sh) {
if (sx + sw > dest_width) return;
if (sy + sh > dest_height) return;
int src_stride = src_width * 4;
int dest_stride = dest_width * 4;
for (int y = 0; y < sw; y++) {
for (int x = 0; x < sh; x++) {
int src_index = (y * src_stride) + (x * 4);
int dest_index = ((y + sy) * dest_stride) + ((x + sx) * 4);
// Calculate the alpha value for the source pixel
uint8_t src_alpha = src[src_index + 3];
// Calculate the alpha value for the destination pixel
uint8_t dest_alpha = dest[dest_index + 3];
// Calculate the resulting alpha value
uint8_t result_alpha = src_alpha + (255 - src_alpha) * dest_alpha / 255;
// Calculate the resulting RGB values
uint8_t result_red = (src[src_index + 0] * src_alpha + dest[dest_index + 0] * (255 - src_alpha) * dest_alpha / 255) / result_alpha;
uint8_t result_green = (src[src_index + 1] * src_alpha + dest[dest_index + 1] * (255 - src_alpha) * dest_alpha / 255) / result_alpha;
uint8_t result_blue = (src[src_index + 2] * src_alpha + dest[dest_index + 2] * (255 - src_alpha) * dest_alpha / 255) / result_alpha;
// Set the resulting pixel values
dest[dest_index + 0] = result_red;
dest[dest_index + 1] = result_green;
dest[dest_index + 2] = result_blue;
dest[dest_index + 3] = result_alpha;
}
}
}
// Function to draw source image pixels on top of a destination image
void texture_blit(texture *dest, texture *src, int x, int y, int w, int h) {
blit_image(src->data, dest->data, src->width, src->height, dest->height, dest->width, x, y, w, h);
}
void texture_flip(texture *tex, int y)
{
}
static int p[512] = {151,160,137,91,90,15, static int p[512] = {151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,

View file

@ -18,7 +18,8 @@ extern struct rect ST_UNIT;
/* Represents an actual texture on the GPU */ /* Represents an actual texture on the GPU */
struct texture { struct texture {
sg_image id; /* ID reference for the GPU memory location of the texture */ sg_image id; /* ID reference for the GPU memory location of the
texture */
int width; int width;
int height; int height;
unsigned char *data; unsigned char *data;
@ -39,8 +40,11 @@ typedef struct img_sampler{
texture *texture_from_file(const char *path); texture *texture_from_file(const char *path);
void texture_free(texture *tex); void texture_free(texture *tex);
struct texture *texture_fromdata(void *raw, long size); struct texture *texture_fromdata(void *raw, long size);
texture *texture_empty(int width, int height, int n);
void texture_blit(texture *dest, texture *src, int x, int y, int w, int h);
void texture_flip(texture *tex, int y);
void texture_save(texture *tex, const char *file);
double perlin(double x, double y, double z); double perlin(double x, double y, double z);

View file

@ -0,0 +1,131 @@
//-----------------------------------------------------------------------------
// DEAR IMGUI COMPILE-TIME OPTIONS
// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
//-----------------------------------------------------------------------------
// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it)
// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template.
//-----------------------------------------------------------------------------
// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp
// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using.
//-----------------------------------------------------------------------------
#pragma once
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
//#define IMGUI_API __declspec( dllexport )
//#define IMGUI_API __declspec( dllimport )
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty.
//---- Don't implement some functions to reduce linkage requirements.
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Include imgui_user.h at the end of imgui.h as a convenience
// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.
//#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
//#define IMGUI_USE_BGRA_PACKED_COLOR
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h"
//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h"
//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined.
//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined.
//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.
//#define IMGUI_USE_STB_SPRINTF
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType+lunasvg library to render OpenType SVG fonts (SVGinOT)
// Requires lunasvg headers to be available in the include path + program to be linked with the lunasvg library (not provided).
// Only works in combination with IMGUI_ENABLE_FREETYPE.
// (implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_LUNASVG
//---- Use stb_truetype to build and rasterize the font atlas (default)
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
//#define IMGUI_ENABLE_STB_TRUETYPE
//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
// This will be inlined as part of ImVec2 and ImVec4 class declarations.
/*
#define IM_VEC2_CLASS_EXTRA \
constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \
operator MyVec2() const { return MyVec2(x,y); }
#define IM_VEC4_CLASS_EXTRA \
constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \
operator MyVec4() const { return MyVec4(x,y,z,w); }
*/
//---- ...Or use Dear ImGui's own very basic math operators.
//#define IMGUI_DEFINE_MATH_OPERATORS
//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.
// Read about ImGuiBackendFlags_RendererHasVtxOffset for details.
//#define ImDrawIdx unsigned int
//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly)
//struct ImDrawList;
//struct ImDrawCmd;
//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);
//#define ImDrawCallback MyImDrawCallback
//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase)
// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.)
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files)
/*
namespace ImGui
{
void MyFunction(const char* name, MyMatrix44* mtx);
}
*/

16056
source/engine/thirdparty/imgui/imgui.cpp vendored Normal file

File diff suppressed because it is too large Load diff

3378
source/engine/thirdparty/imgui/imgui.h vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,627 @@
// [DEAR IMGUI]
// This is a slightly modified version of stb_rect_pack.h 1.01.
// Grep for [DEAR IMGUI] to find the changes.
//
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
// Sean Barrett 2014
//
// Useful for e.g. packing rectangular textures into an atlas.
// Does not do rotation.
//
// Before #including,
//
// #define STB_RECT_PACK_IMPLEMENTATION
//
// in the file that you want to have the implementation.
//
// Not necessarily the awesomest packing method, but better than
// the totally naive one in stb_truetype (which is primarily what
// this is meant to replace).
//
// Has only had a few tests run, may have issues.
//
// More docs to come.
//
// No memory allocations; uses qsort() and assert() from stdlib.
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
//
// This library currently uses the Skyline Bottom-Left algorithm.
//
// Please note: better rectangle packers are welcome! Please
// implement them to the same API, but with a different init
// function.
//
// Credits
//
// Library
// Sean Barrett
// Minor features
// Martins Mozeiko
// github:IntellectualKitty
//
// Bugfixes / warning fixes
// Jeremy Jaussaud
// Fabian Giesen
//
// Version history:
//
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
// 0.99 (2019-02-07) warning fixes
// 0.11 (2017-03-03) return packing success/fail result
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
// 0.09 (2016-08-27) fix compiler warnings
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
// 0.05: added STBRP_ASSERT to allow replacing assert
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
// 0.01: initial release
//
// LICENSE
//
// See end of file for license information.
//////////////////////////////////////////////////////////////////////////////
//
// INCLUDE SECTION
//
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#define STB_INCLUDE_STB_RECT_PACK_H
#define STB_RECT_PACK_VERSION 1
#ifdef STBRP_STATIC
#define STBRP_DEF static
#else
#define STBRP_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct stbrp_context stbrp_context;
typedef struct stbrp_node stbrp_node;
typedef struct stbrp_rect stbrp_rect;
typedef int stbrp_coord;
#define STBRP__MAXVAL 0x7fffffff
// Mostly for internal use, but this is the maximum supported coordinate value.
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
// Assign packed locations to rectangles. The rectangles are of type
// 'stbrp_rect' defined below, stored in the array 'rects', and there
// are 'num_rects' many of them.
//
// Rectangles which are successfully packed have the 'was_packed' flag
// set to a non-zero value and 'x' and 'y' store the minimum location
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
// if you imagine y increasing downwards). Rectangles which do not fit
// have the 'was_packed' flag set to 0.
//
// You should not try to access the 'rects' array from another thread
// while this function is running, as the function temporarily reorders
// the array while it executes.
//
// To pack into another rectangle, you need to call stbrp_init_target
// again. To continue packing into the same rectangle, you can call
// this function again. Calling this multiple times with multiple rect
// arrays will probably produce worse packing results than calling it
// a single time with the full rectangle array, but the option is
// available.
//
// The function returns 1 if all of the rectangles were successfully
// packed and 0 otherwise.
struct stbrp_rect
{
// reserved for your use:
int id;
// input:
stbrp_coord w, h;
// output:
stbrp_coord x, y;
int was_packed; // non-zero if valid packing
}; // 16 bytes, nominally
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
// Initialize a rectangle packer to:
// pack a rectangle that is 'width' by 'height' in dimensions
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
//
// You must call this function every time you start packing into a new target.
//
// There is no "shutdown" function. The 'nodes' memory must stay valid for
// the following stbrp_pack_rects() call (or calls), but can be freed after
// the call (or calls) finish.
//
// Note: to guarantee best results, either:
// 1. make sure 'num_nodes' >= 'width'
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
//
// If you don't do either of the above things, widths will be quantized to multiples
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
//
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
// may run out of temporary storage and be unable to pack some rectangles.
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
// Optionally call this function after init but before doing any packing to
// change the handling of the out-of-temp-memory scenario, described above.
// If you call init again, this will be reset to the default (false).
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
// Optionally select which packing heuristic the library should use. Different
// heuristics will produce better/worse results for different data sets.
// If you call init again, this will be reset to the default.
enum
{
STBRP_HEURISTIC_Skyline_default=0,
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
STBRP_HEURISTIC_Skyline_BF_sortHeight
};
//////////////////////////////////////////////////////////////////////////////
//
// the details of the following structures don't matter to you, but they must
// be visible so you can handle the memory allocations for them
struct stbrp_node
{
stbrp_coord x,y;
stbrp_node *next;
};
struct stbrp_context
{
int width;
int height;
int align;
int init_mode;
int heuristic;
int num_nodes;
stbrp_node *active_head;
stbrp_node *free_head;
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
};
#ifdef __cplusplus
}
#endif
#endif
//////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION SECTION
//
#ifdef STB_RECT_PACK_IMPLEMENTATION
#ifndef STBRP_SORT
#include <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#define STBRP_ASSERT assert
#endif
#ifdef _MSC_VER
#define STBRP__NOTUSED(v) (void)(v)
#define STBRP__CDECL __cdecl
#else
#define STBRP__NOTUSED(v) (void)sizeof(v)
#define STBRP__CDECL
#endif
enum
{
STBRP__INIT_skyline = 1
};
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
{
switch (context->init_mode) {
case STBRP__INIT_skyline:
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
context->heuristic = heuristic;
break;
default:
STBRP_ASSERT(0);
}
}
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
{
if (allow_out_of_mem)
// if it's ok to run out of memory, then don't bother aligning them;
// this gives better packing, but may fail due to OOM (even though
// the rectangles easily fit). @TODO a smarter approach would be to only
// quantize once we've hit OOM, then we could get rid of this parameter.
context->align = 1;
else {
// if it's not ok to run out of memory, then quantize the widths
// so that num_nodes is always enough nodes.
//
// I.e. num_nodes * align >= width
// align >= width / num_nodes
// align = ceil(width/num_nodes)
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
}
}
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
{
int i;
for (i=0; i < num_nodes-1; ++i)
nodes[i].next = &nodes[i+1];
nodes[i].next = NULL;
context->init_mode = STBRP__INIT_skyline;
context->heuristic = STBRP_HEURISTIC_Skyline_default;
context->free_head = &nodes[0];
context->active_head = &context->extra[0];
context->width = width;
context->height = height;
context->num_nodes = num_nodes;
stbrp_setup_allow_out_of_mem(context, 0);
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
context->extra[0].x = 0;
context->extra[0].y = 0;
context->extra[0].next = &context->extra[1];
context->extra[1].x = (stbrp_coord) width;
context->extra[1].y = (1<<30);
context->extra[1].next = NULL;
}
// find minimum y position if it starts at x1
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
{
stbrp_node *node = first;
int x1 = x0 + width;
int min_y, visited_width, waste_area;
STBRP__NOTUSED(c);
STBRP_ASSERT(first->x <= x0);
#if 0
// skip in case we're past the node
while (node->next->x <= x0)
++node;
#else
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
#endif
STBRP_ASSERT(node->x <= x0);
min_y = 0;
waste_area = 0;
visited_width = 0;
while (node->x < x1) {
if (node->y > min_y) {
// raise min_y higher.
// we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
// the first time through, visited_width might be reduced
if (node->x < x0)
visited_width += node->next->x - x0;
else
visited_width += node->next->x - node->x;
} else {
// add waste area
int under_width = node->next->x - node->x;
if (under_width + visited_width > width)
under_width = width - visited_width;
waste_area += under_width * (min_y - node->y);
visited_width += under_width;
}
node = node->next;
}
*pwaste = waste_area;
return min_y;
}
typedef struct
{
int x,y;
stbrp_node **prev_link;
} stbrp__findresult;
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
{
int best_waste = (1<<30), best_x, best_y = (1 << 30);
stbrp__findresult fr;
stbrp_node **prev, *node, *tail, **best = NULL;
// align to multiple of c->align
width = (width + c->align - 1);
width -= width % c->align;
STBRP_ASSERT(width % c->align == 0);
// if it can't possibly fit, bail immediately
if (width > c->width || height > c->height) {
fr.prev_link = NULL;
fr.x = fr.y = 0;
return fr;
}
node = c->active_head;
prev = &c->active_head;
while (node->x + width <= c->width) {
int y,waste;
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
// bottom left
if (y < best_y) {
best_y = y;
best = prev;
}
} else {
// best-fit
if (y + height <= c->height) {
// can only use it if it first vertically
if (y < best_y || (y == best_y && waste < best_waste)) {
best_y = y;
best_waste = waste;
best = prev;
}
}
}
prev = &node->next;
node = node->next;
}
best_x = (best == NULL) ? 0 : (*best)->x;
// if doing best-fit (BF), we also have to try aligning right edge to each node position
//
// e.g, if fitting
//
// ____________________
// |____________________|
//
// into
//
// | |
// | ____________|
// |____________|
//
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
//
// This makes BF take about 2x the time
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
tail = c->active_head;
node = c->active_head;
prev = &c->active_head;
// find first node that's admissible
while (tail->x < width)
tail = tail->next;
while (tail) {
int xpos = tail->x - width;
int y,waste;
STBRP_ASSERT(xpos >= 0);
// find the left position that matches this
while (node->next->x <= xpos) {
prev = &node->next;
node = node->next;
}
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
if (y + height <= c->height) {
if (y <= best_y) {
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
best_x = xpos;
//STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
best_y = y;
best_waste = waste;
best = prev;
}
}
}
tail = tail->next;
}
}
fr.prev_link = best;
fr.x = best_x;
fr.y = best_y;
return fr;
}
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
{
// find best position according to heuristic
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
stbrp_node *node, *cur;
// bail if:
// 1. it failed
// 2. the best node doesn't fit (we don't always check this)
// 3. we're out of memory
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
res.prev_link = NULL;
return res;
}
// on success, create new node
node = context->free_head;
node->x = (stbrp_coord) res.x;
node->y = (stbrp_coord) (res.y + height);
context->free_head = node->next;
// insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be
// stiched back in
cur = *res.prev_link;
if (cur->x < res.x) {
// preserve the existing one, so start testing with the next one
stbrp_node *next = cur->next;
cur->next = node;
cur = next;
} else {
*res.prev_link = node;
}
// from here, traverse cur and free the nodes, until we get to one
// that shouldn't be freed
while (cur->next && cur->next->x <= res.x + width) {
stbrp_node *next = cur->next;
// move the current node to the free list
cur->next = context->free_head;
context->free_head = cur;
cur = next;
}
// stitch the list back in
node->next = cur;
if (cur->x < res.x + width)
cur->x = (stbrp_coord) (res.x + width);
#ifdef _DEBUG
cur = context->active_head;
while (cur->x < context->width) {
STBRP_ASSERT(cur->x < cur->next->x);
cur = cur->next;
}
STBRP_ASSERT(cur->next == NULL);
{
int count=0;
cur = context->active_head;
while (cur) {
cur = cur->next;
++count;
}
cur = context->free_head;
while (cur) {
cur = cur->next;
++count;
}
STBRP_ASSERT(count == context->num_nodes+2);
}
#endif
return res;
}
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
if (p->h > q->h)
return -1;
if (p->h < q->h)
return 1;
return (p->w > q->w) ? -1 : (p->w < q->w);
}
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
}
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
{
int i, all_rects_packed = 1;
// we use the 'was_packed' field internally to allow sorting/unsorting
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = i;
}
// sort according to heuristic
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
if (rects[i].w == 0 || rects[i].h == 0) {
rects[i].x = rects[i].y = 0; // empty rect needs no space
} else {
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
if (fr.prev_link) {
rects[i].x = (stbrp_coord) fr.x;
rects[i].y = (stbrp_coord) fr.y;
} else {
rects[i].x = rects[i].y = STBRP__MAXVAL;
}
}
}
// unsort
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
// set was_packed flags and all_rects_packed status
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
if (!rects[i].was_packed)
all_rects_packed = 0;
}
// return the all_rects_packed status
return all_rects_packed;
}
#endif
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
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.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
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 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.
------------------------------------------------------------------------------
*/

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -18,11 +18,12 @@
the backend selected for sokol_gfx.h if both are used in the same the backend selected for sokol_gfx.h if both are used in the same
project): project):
#define SOKOL_GLCORE33 #define SOKOL_GLCORE
#define SOKOL_GLES3 #define SOKOL_GLES3
#define SOKOL_D3D11 #define SOKOL_D3D11
#define SOKOL_METAL #define SOKOL_METAL
#define SOKOL_WGPU #define SOKOL_WGPU
#define SOKOL_NOAPI
Optionally provide the following defines with your own implementations: Optionally provide the following defines with your own implementations:
@ -47,7 +48,7 @@
On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport) On Windows, SOKOL_DLL will define SOKOL_APP_API_DECL as __declspec(dllexport)
or __declspec(dllimport) as needed. or __declspec(dllimport) as needed.
On Linux, SOKOL_GLCORE33 can use either GLX or EGL. On Linux, SOKOL_GLCORE can use either GLX or EGL.
GLX is default, set SOKOL_FORCE_EGL to override. GLX is default, set SOKOL_FORCE_EGL to override.
For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp
@ -87,7 +88,7 @@
- makes the rendered frame visible - makes the rendered frame visible
- provides keyboard-, mouse- and low-level touch-events - provides keyboard-, mouse- and low-level touch-events
- platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android - platforms: MacOS, iOS, HTML5, Win32, Linux/RaspberryPi, Android
- 3D-APIs: Metal, D3D11, GL3.2, GLES3, WebGL, WebGL2 - 3D-APIs: Metal, D3D11, GL3.2, GLES3, WebGL, WebGL2, NOAPI
FEATURE/PLATFORM MATRIX FEATURE/PLATFORM MATRIX
======================= =======================
@ -97,6 +98,7 @@
gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES
metal | --- | YES | --- | YES | --- | --- metal | --- | YES | --- | YES | --- | ---
d3d11 | YES | --- | --- | --- | --- | --- d3d11 | YES | --- | --- | --- | --- | ---
noapi | YES | TODO | TODO | --- | TODO | ---
KEY_DOWN | YES | YES | YES | SOME | TODO | YES KEY_DOWN | YES | YES | YES | SOME | TODO | YES
KEY_UP | YES | YES | YES | SOME | TODO | YES KEY_UP | YES | YES | YES | SOME | TODO | YES
CHAR | YES | YES | YES | YES | TODO | YES CHAR | YES | YES | YES | YES | TODO | YES
@ -313,10 +315,15 @@
objects and values required for rendering. If sokol_app.h objects and values required for rendering. If sokol_app.h
is not compiled with SOKOL_WGPU, these functions return null. is not compiled with SOKOL_WGPU, these functions return null.
const uint32_t sapp_gl_get_framebuffer(void) uint32_t sapp_gl_get_framebuffer(void)
This returns the 'default framebuffer' of the GL context. This returns the 'default framebuffer' of the GL context.
Typically this will be zero. Typically this will be zero.
int sapp_gl_get_major_version(void)
int sapp_gl_get_minor_version(void)
Returns the major and minor version of the GL context
(only for SOKOL_GLCORE, all other backends return zero here, including SOKOL_GLES3)
const void* sapp_android_get_native_activity(void); const void* sapp_android_get_native_activity(void);
On Android, get the native activity ANativeActivity pointer, otherwise On Android, get the native activity ANativeActivity pointer, otherwise
a null pointer. a null pointer.
@ -348,7 +355,7 @@
sapp_consume_event() from inside the event handler (NOTE that sapp_consume_event() from inside the event handler (NOTE that
this behaviour is currently only implemented for some HTML5 this behaviour is currently only implemented for some HTML5
events, support for other platforms and event types will events, support for other platforms and event types will
be added as needed, please open a github ticket and/or provide be added as needed, please open a GitHub ticket and/or provide
a PR if needed). a PR if needed).
NOTE: Do *not* call any 3D API rendering functions in the event NOTE: Do *not* call any 3D API rendering functions in the event
@ -1892,6 +1899,10 @@ SOKOL_APP_API_DECL const void* sapp_wgpu_get_depth_stencil_view(void);
/* GL: get framebuffer object */ /* GL: get framebuffer object */
SOKOL_APP_API_DECL uint32_t sapp_gl_get_framebuffer(void); SOKOL_APP_API_DECL uint32_t sapp_gl_get_framebuffer(void);
/* GL: get major version (only valid for desktop GL) */
SOKOL_APP_API_DECL int sapp_gl_get_major_version(void);
/* GL: get minor version (only valid for desktop GL) */
SOKOL_APP_API_DECL int sapp_gl_get_minor_version(void);
/* Android: get native activity handle */ /* Android: get native activity handle */
SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void); SOKOL_APP_API_DECL const void* sapp_android_get_native_activity(void);
@ -1959,8 +1970,8 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
#if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE #if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE
/* MacOS */ /* MacOS */
#define _SAPP_MACOS (1) #define _SAPP_MACOS (1)
#if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33) #if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE)
#error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE")
#endif #endif
#else #else
/* iOS or iOS Simulator */ /* iOS or iOS Simulator */
@ -1978,8 +1989,8 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
#elif defined(_WIN32) #elif defined(_WIN32)
/* Windows (D3D11 or GL) */ /* Windows (D3D11 or GL) */
#define _SAPP_WIN32 (1) #define _SAPP_WIN32 (1)
#if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33) #if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE) && !defined(SOKOL_NOAPI)
#error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11, SOKOL_GLCORE or SOKOL_NOAPI")
#endif #endif
#elif defined(__ANDROID__) #elif defined(__ANDROID__)
/* Android */ /* Android */
@ -1993,7 +2004,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
#elif defined(__linux__) || defined(__unix__) #elif defined(__linux__) || defined(__unix__)
/* Linux */ /* Linux */
#define _SAPP_LINUX (1) #define _SAPP_LINUX (1)
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
#if !defined(SOKOL_FORCE_EGL) #if !defined(SOKOL_FORCE_EGL)
#define _SAPP_GLX (1) #define _SAPP_GLX (1)
#endif #endif
@ -2003,13 +2014,13 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
#include <GLES3/gl3.h> #include <GLES3/gl3.h>
#include <GLES3/gl3ext.h> #include <GLES3/gl3ext.h>
#else #else
#error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE33, SOKOL_GLES3") #error("sokol_app.h: unknown 3D API selected for Linux, must be SOKOL_GLCORE, SOKOL_GLES3")
#endif #endif
#else #else
#error "sokol_app.h: Unknown platform" #error "sokol_app.h: Unknown platform"
#endif #endif
#if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES3) #if defined(SOKOL_GLCORE) || defined(SOKOL_GLES3)
#define _SAPP_ANY_GL (1) #define _SAPP_ANY_GL (1)
#endif #endif
@ -2399,11 +2410,11 @@ _SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) {
#if defined(SOKOL_METAL) #if defined(SOKOL_METAL)
@interface _sapp_macos_view : MTKView @interface _sapp_macos_view : MTKView
@end @end
#elif defined(SOKOL_GLCORE33) #elif defined(SOKOL_GLCORE)
@interface _sapp_macos_view : NSOpenGLView @interface _sapp_macos_view : NSOpenGLView
- (void)timerFired:(id)sender; - (void)timerFired:(id)sender;
@end @end
#endif // SOKOL_GLCORE33 #endif // SOKOL_GLCORE
typedef struct { typedef struct {
uint32_t flags_changed_store; uint32_t flags_changed_store;
@ -2545,7 +2556,7 @@ typedef struct {
uint8_t raw_input_data[256]; uint8_t raw_input_data[256];
} _sapp_win32_t; } _sapp_win32_t;
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_DRAW_TO_WINDOW_ARB 0x2001
@ -2605,7 +2616,7 @@ typedef struct {
HWND msg_hwnd; HWND msg_hwnd;
HDC msg_dc; HDC msg_dc;
} _sapp_wgl_t; } _sapp_wgl_t;
#endif // SOKOL_GLCORE33 #endif // SOKOL_GLCORE
#endif // _SAPP_WIN32 #endif // _SAPP_WIN32
@ -2876,7 +2887,7 @@ typedef struct {
_sapp_win32_t win32; _sapp_win32_t win32;
#if defined(SOKOL_D3D11) #if defined(SOKOL_D3D11)
_sapp_d3d11_t d3d11; _sapp_d3d11_t d3d11;
#elif defined(SOKOL_GLCORE33) #elif defined(SOKOL_GLCORE)
_sapp_wgl_t wgl; _sapp_wgl_t wgl;
#endif #endif
#elif defined(_SAPP_ANDROID) #elif defined(_SAPP_ANDROID)
@ -3085,8 +3096,13 @@ _SOKOL_PRIVATE sapp_desc _sapp_desc_defaults(const sapp_desc* desc) {
// (or expressed differently: zero is a valid value for gl_minor_version // (or expressed differently: zero is a valid value for gl_minor_version
// and can't be used to indicate 'default') // and can't be used to indicate 'default')
if (0 == res.gl_major_version) { if (0 == res.gl_major_version) {
res.gl_major_version = 3; #if defined(_SAPP_APPLE)
res.gl_minor_version = 2; res.gl_major_version = 4;
res.gl_minor_version = 1;
#else
res.gl_major_version = 4;
res.gl_minor_version = 3;
#endif
} }
res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas"); res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "canvas");
res.clipboard_size = _sapp_def(res.clipboard_size, 8192); res.clipboard_size = _sapp_def(res.clipboard_size, 8192);
@ -3650,7 +3666,7 @@ _SOKOL_PRIVATE void _sapp_macos_update_dimensions(void) {
const int cur_fb_height = (int)roundf(fb_size.height); const int cur_fb_height = (int)roundf(fb_size.height);
const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) ||
(_sapp.framebuffer_height != cur_fb_height); (_sapp.framebuffer_height != cur_fb_height);
#elif defined(SOKOL_GLCORE33) #elif defined(SOKOL_GLCORE)
const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale); const int cur_fb_width = (int)roundf(bounds.size.width * _sapp.dpi_scale);
const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale); const int cur_fb_height = (int)roundf(bounds.size.height * _sapp.dpi_scale);
const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) || const bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) ||
@ -3892,7 +3908,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
_sapp.macos.window.contentView = _sapp.macos.view; _sapp.macos.window.contentView = _sapp.macos.view;
[_sapp.macos.window makeFirstResponder:_sapp.macos.view]; [_sapp.macos.window makeFirstResponder:_sapp.macos.view];
_sapp.macos.view.layer.magnificationFilter = kCAFilterNearest; _sapp.macos.view.layer.magnificationFilter = kCAFilterNearest;
#elif defined(SOKOL_GLCORE33) #elif defined(SOKOL_GLCORE)
NSOpenGLPixelFormatAttribute attrs[32]; NSOpenGLPixelFormatAttribute attrs[32];
int i = 0; int i = 0;
attrs[i++] = NSOpenGLPFAAccelerated; attrs[i++] = NSOpenGLPFAAccelerated;
@ -4124,7 +4140,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
@end @end
@implementation _sapp_macos_view @implementation _sapp_macos_view
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
- (void)timerFired:(id)sender { - (void)timerFired:(id)sender {
_SOKOL_UNUSED(sender); _SOKOL_UNUSED(sender);
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
@ -4224,7 +4240,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events(void) {
// helper function to make GL context active // helper function to make GL context active
static void _sapp_gl_make_current(void) { static void _sapp_gl_make_current(void) {
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
[[_sapp.macos.view openGLContext] makeCurrentContext]; [[_sapp.macos.view openGLContext] makeCurrentContext];
#endif #endif
} }
@ -5749,16 +5765,28 @@ _SOKOL_PRIVATE void _sapp_emsc_wgpu_request_adapter_cb(WGPURequestAdapterStatus
SOKOL_ASSERT(adapter); SOKOL_ASSERT(adapter);
_sapp.wgpu.adapter = adapter; _sapp.wgpu.adapter = adapter;
size_t cur_feature_index = 1; size_t cur_feature_index = 1;
WGPUFeatureName requiredFeatures[8] = { #define _SAPP_WGPU_MAX_REQUESTED_FEATURES (8)
WGPUFeatureName requiredFeatures[_SAPP_WGPU_MAX_REQUESTED_FEATURES] = {
WGPUFeatureName_Depth32FloatStencil8, WGPUFeatureName_Depth32FloatStencil8,
}; };
// check for optional features we're interested in // check for optional features we're interested in
// FIXME: ASTC texture compression
if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionBC)) { if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionBC)) {
SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES);
requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionBC; requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionBC;
} else if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionETC2)) { }
if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionETC2)) {
SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES);
requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionETC2; requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionETC2;
} }
if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionASTC)) {
SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES);
requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionASTC;
}
if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_Float32Filterable)) {
SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES);
requiredFeatures[cur_feature_index++] = WGPUFeatureName_Float32Filterable;
}
#undef _SAPP_WGPU_MAX_REQUESTED_FEATURES
WGPUDeviceDescriptor dev_desc; WGPUDeviceDescriptor dev_desc;
_sapp_clear(&dev_desc, sizeof(dev_desc)); _sapp_clear(&dev_desc, sizeof(dev_desc));
@ -5945,7 +5973,7 @@ int main(int argc, char* argv[]) {
// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████ // ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████
// //
// >>gl helpers // >>gl helpers
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
typedef struct { typedef struct {
int red_bits; int red_bits;
int green_bits; int green_bits;
@ -6596,7 +6624,7 @@ _SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) {
#endif /* SOKOL_D3D11 */ #endif /* SOKOL_D3D11 */
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
_SOKOL_PRIVATE void _sapp_wgl_init(void) { _SOKOL_PRIVATE void _sapp_wgl_init(void) {
_sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll"); _sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll");
if (!_sapp.wgl.opengl32) { if (!_sapp.wgl.opengl32) {
@ -6894,7 +6922,7 @@ _SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) {
/* FIXME: DwmIsCompositionEnabled? (see GLFW) */ /* FIXME: DwmIsCompositionEnabled? (see GLFW) */
SwapBuffers(_sapp.win32.dc); SwapBuffers(_sapp.win32.dc);
} }
#endif /* SOKOL_GLCORE33 */ #endif /* SOKOL_GLCORE */
_SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) { _SOKOL_PRIVATE bool _sapp_win32_wide_to_utf8(const wchar_t* src, char* dst, int dst_num_bytes) {
SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); SOKOL_ASSERT(src && dst && (dst_num_bytes > 1));
@ -7311,7 +7339,10 @@ _SOKOL_PRIVATE void _sapp_win32_timing_measure(void) {
// fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason // fallback if swap model isn't "flip-discard" or GetFrameStatistics failed for another reason
_sapp_timing_measure(&_sapp.timing); _sapp_timing_measure(&_sapp.timing);
#endif #endif
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
_sapp_timing_measure(&_sapp.timing);
#endif
#if defined(SOKOL_NOAPI)
_sapp_timing_measure(&_sapp.timing); _sapp_timing_measure(&_sapp.timing);
#endif #endif
} }
@ -7513,7 +7544,7 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM
// present with DXGI_PRESENT_DO_NOT_WAIT // present with DXGI_PRESENT_DO_NOT_WAIT
_sapp_d3d11_present(true); _sapp_d3d11_present(true);
#endif #endif
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
_sapp_wgl_swap_buffers(); _sapp_wgl_swap_buffers();
#endif #endif
/* NOTE: resizing the swap-chain during resize leads to a substantial /* NOTE: resizing the swap-chain during resize leads to a substantial
@ -7926,7 +7957,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) {
_sapp_d3d11_create_device_and_swapchain(); _sapp_d3d11_create_device_and_swapchain();
_sapp_d3d11_create_default_render_target(); _sapp_d3d11_create_default_render_target();
#endif #endif
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
_sapp_wgl_init(); _sapp_wgl_init();
_sapp_wgl_load_extensions(); _sapp_wgl_load_extensions();
_sapp_wgl_create_context(); _sapp_wgl_create_context();
@ -7954,7 +7985,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) {
Sleep((DWORD)(16 * _sapp.swap_interval)); Sleep((DWORD)(16 * _sapp.swap_interval));
} }
#endif #endif
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
_sapp_wgl_swap_buffers(); _sapp_wgl_swap_buffers();
#endif #endif
/* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */ /* check for window resized, this cannot happen in WM_SIZE as it explodes memory usage */
@ -10947,7 +10978,7 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) {
#if !defined(_SAPP_GLX) #if !defined(_SAPP_GLX)
_SOKOL_PRIVATE void _sapp_egl_init(void) { _SOKOL_PRIVATE void _sapp_egl_init(void) {
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
if (!eglBindAPI(EGL_OPENGL_API)) { if (!eglBindAPI(EGL_OPENGL_API)) {
_SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED); _SAPP_PANIC(LINUX_EGL_BIND_OPENGL_API_FAILED);
} }
@ -10971,7 +11002,7 @@ _SOKOL_PRIVATE void _sapp_egl_init(void) {
EGLint alpha_size = _sapp.desc.alpha ? 8 : 0; EGLint alpha_size = _sapp.desc.alpha ? 8 : 0;
const EGLint config_attrs[] = { const EGLint config_attrs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
#elif defined(SOKOL_GLES3) #elif defined(SOKOL_GLES3)
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
@ -11034,7 +11065,7 @@ _SOKOL_PRIVATE void _sapp_egl_init(void) {
} }
EGLint ctx_attrs[] = { EGLint ctx_attrs[] = {
#if defined(SOKOL_GLCORE33) #if defined(SOKOL_GLCORE)
EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version,
EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version,
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
@ -11753,6 +11784,24 @@ SOKOL_API_IMPL uint32_t sapp_gl_get_framebuffer(void) {
#endif #endif
} }
SOKOL_API_IMPL int sapp_gl_get_major_version(void) {
SOKOL_ASSERT(_sapp.valid);
#if defined(SOKOL_GLCORE)
return _sapp.desc.gl_major_version;
#else
return 0;
#endif
}
SOKOL_API_IMPL int sapp_gl_get_minor_version(void) {
SOKOL_ASSERT(_sapp.valid);
#if defined(SOKOL_GLCORE)
return _sapp.desc.gl_minor_version;
#else
return 0;
#endif
}
SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) { SOKOL_API_IMPL const void* sapp_android_get_native_activity(void) {
// NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity() // NOTE: _sapp.valid is not asserted here because sapp_android_get_native_activity()
// needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708) // needs to be callable from within sokol_main() (see: https://github.com/floooh/sokol/issues/708)

View file

@ -39,6 +39,7 @@
- on macOS: AudioToolbox - on macOS: AudioToolbox
- on iOS: AudioToolbox, AVFoundation - on iOS: AudioToolbox, AVFoundation
- on FreeBSD: asound
- on Linux: asound - on Linux: asound
- on Android: link with OpenSLES or aaudio - on Android: link with OpenSLES or aaudio
- on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib
@ -51,6 +52,7 @@
- Windows: WASAPI - Windows: WASAPI
- Linux: ALSA - Linux: ALSA
- FreeBSD: ALSA
- macOS: CoreAudio - macOS: CoreAudio
- iOS: CoreAudio+AVAudioSession - iOS: CoreAudio+AVAudioSession
- emscripten: WebAudio with ScriptProcessorNode - emscripten: WebAudio with ScriptProcessorNode
@ -780,7 +782,9 @@ inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc);
#include "aaudio/AAudio.h" #include "aaudio/AAudio.h"
#endif #endif
#elif defined(_SAUDIO_LINUX) #elif defined(_SAUDIO_LINUX)
#include <alloca.h> #if !defined(__FreeBSD__)
#include <alloca.h>
#endif
#define _SAUDIO_PTHREADS (1) #define _SAUDIO_PTHREADS (1)
#include <pthread.h> #include <pthread.h>
#define ALSA_PCM_NEW_HW_PARAMS_API #define ALSA_PCM_NEW_HW_PARAMS_API

View file

@ -1071,17 +1071,14 @@ typedef struct sfetch_response_t {
sfetch_range_t buffer; // the user-provided buffer which holds the fetched data sfetch_range_t buffer; // the user-provided buffer which holds the fetched data
} sfetch_response_t; } sfetch_response_t;
/* response callback function signature */
typedef void(*sfetch_callback_t)(const sfetch_response_t*);
/* request parameters passed to sfetch_send() */ /* request parameters passed to sfetch_send() */
typedef struct sfetch_request_t { typedef struct sfetch_request_t {
uint32_t channel; // index of channel this request is assigned to (default: 0) uint32_t channel; // index of channel this request is assigned to (default: 0)
const char* path; // filesystem path or HTTP URL (required) const char* path; // filesystem path or HTTP URL (required)
sfetch_callback_t callback; // response callback function pointer (required) void (*callback) (const sfetch_response_t*); // response callback function pointer (required)
uint32_t chunk_size; // number of bytes to load per stream-block (optional) uint32_t chunk_size; // number of bytes to load per stream-block (optional)
sfetch_range_t buffer; // a memory buffer where the data will be loaded into (optional) sfetch_range_t buffer; // a memory buffer where the data will be loaded into (optional)
sfetch_range_t user_data; // ptr/size of a POD user data block which will be memcpy'd (optional) sfetch_range_t user_data; // ptr/size of a POD user data block which will be memcpy'd (optional)
} sfetch_request_t; } sfetch_request_t;
/* setup sokol-fetch (can be called on multiple threads) */ /* setup sokol-fetch (can be called on multiple threads) */
@ -1302,7 +1299,7 @@ typedef struct {
uint32_t channel; uint32_t channel;
uint32_t lane; uint32_t lane;
uint32_t chunk_size; uint32_t chunk_size;
sfetch_callback_t callback; void (*callback) (const sfetch_response_t*);
sfetch_range_t buffer; sfetch_range_t buffer;
/* updated by IO-thread, off-limits to user thread */ /* updated by IO-thread, off-limits to user thread */

File diff suppressed because it is too large Load diff

View file

@ -43,7 +43,7 @@
functions. Use this in the sg_setup() call like this: functions. Use this in the sg_setup() call like this:
sg_setup(&(sg_desc){ sg_setup(&(sg_desc){
.environment = sglue_enviornment(), .environment = sglue_environment(),
... ...
}); });

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,167 @@
#if defined(SOKOL_IMPL) && !defined(SOKOL_MEMTRACK_IMPL)
#define SOKOL_MEMTRACK_IMPL
#endif
#ifndef SOKOL_MEMTRACK_INCLUDED
/*
sokol_memtrack.h -- memory allocation wrapper to track memory usage
of sokol libraries
Project URL: https://github.com/floooh/sokol
Optionally provide the following defines with your own implementations:
SOKOL_MEMTRACK_API_DECL - public function declaration prefix (default: extern)
SOKOL_API_DECL - same as SOKOL_MEMTRACK_API_DECL
SOKOL_API_IMPL - public function implementation prefix (default: -)
If sokol_memtrack.h is compiled as a DLL, define the following before
including the declaration or implementation:
SOKOL_DLL
USAGE
=====
Just plug the malloc/free wrapper functions into the desc.allocator
struct provided by most sokol header setup functions:
sg_setup(&(sg_desc){
//...
.allocator = {
.alloc_fn = smemtrack_alloc,
.free_fn = smemtrack_free,
}
});
Then call smemtrack_info() to get information about current number
of allocations and overall allocation size:
const smemtrack_info_t info = smemtrack_info();
const int num_allocs = info.num_allocs;
const int num_bytes = info.num_bytes;
Note the sokol_memtrack.h can only track allocations issued by
the sokol headers, not allocations that happen under the hood
in system libraries.
LICENSE
=======
zlib/libpng license
Copyright (c) 2018 Andre Weissflog
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#define SOKOL_MEMTRACK_INCLUDED (1)
#include <stdint.h>
#include <stddef.h> // size_t
#if defined(SOKOL_API_DECL) && !defined(SOKOL_MEMTRACK_API_DECL)
#define SOKOL_MEMTRACK_API_DECL SOKOL_API_DECL
#endif
#ifndef SOKOL_MEMTRACK_API_DECL
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_MEMTRACK_IMPL)
#define SOKOL_MEMTRACK_API_DECL __declspec(dllexport)
#elif defined(_WIN32) && defined(SOKOL_DLL)
#define SOKOL_MEMTRACK_API_DECL __declspec(dllimport)
#else
#define SOKOL_MEMTRACK_API_DECL extern
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct smemtrack_info_t {
int num_allocs;
int num_bytes;
} smemtrack_info_t;
SOKOL_MEMTRACK_API_DECL smemtrack_info_t smemtrack_info(void);
SOKOL_MEMTRACK_API_DECL void* smemtrack_alloc(size_t size, void* user_data);
SOKOL_MEMTRACK_API_DECL void smemtrack_free(void* ptr, void* user_data);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SOKOL_MEMTRACK_INCLUDED */
/*=== IMPLEMENTATION =========================================================*/
#ifdef SOKOL_MEMTRACK_IMPL
#define SOKOL_MEMTRACK_IMPL_INCLUDED (1)
#include <stdlib.h> // malloc, free
#include <string.h> // memset
#ifndef SOKOL_API_IMPL
#define SOKOL_API_IMPL
#endif
#ifndef SOKOL_DEBUG
#ifndef NDEBUG
#define SOKOL_DEBUG
#endif
#endif
#ifndef _SOKOL_PRIVATE
#if defined(__GNUC__) || defined(__clang__)
#define _SOKOL_PRIVATE __attribute__((unused)) static
#else
#define _SOKOL_PRIVATE static
#endif
#endif
// per-allocation header used to keep track of the allocation size
#define _SMEMTRACK_HEADER_SIZE (16)
static struct {
smemtrack_info_t state;
} _smemtrack;
SOKOL_API_IMPL void* smemtrack_alloc(size_t size, void* user_data) {
(void)user_data;
uint8_t* ptr = (uint8_t*) malloc(size + _SMEMTRACK_HEADER_SIZE);
if (ptr) {
// store allocation size (for allocation size tracking)
*(size_t*)ptr = size;
_smemtrack.state.num_allocs++;
_smemtrack.state.num_bytes += (int) size;
return ptr + _SMEMTRACK_HEADER_SIZE;
}
else {
// allocation failed, return null pointer
return ptr;
}
}
SOKOL_API_IMPL void smemtrack_free(void* ptr, void* user_data) {
(void)user_data;
if (ptr) {
uint8_t* alloc_ptr = ((uint8_t*)ptr) - _SMEMTRACK_HEADER_SIZE;
size_t size = *(size_t*)alloc_ptr;
_smemtrack.state.num_allocs--;
_smemtrack.state.num_bytes -= (int) size;
free(alloc_ptr);
}
}
SOKOL_API_IMPL smemtrack_info_t smemtrack_info(void) {
return _smemtrack.state;
}
#endif /* SOKOL_MEMTRACK_IMPL */

File diff suppressed because it is too large Load diff

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