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

View file

@ -1,4 +1,5 @@
MAKEFLAGS = --jobs=8
PROCS != nproc --all
MAKEFLAGS = -j $(PROCS)
UNAME != uname
MAKEDIR != pwd
# Options
@ -16,12 +17,13 @@ LD = $(CC)
STEAM = steam/sdk
STEAMAPI =
LDFLAGS += -lstdc++
ifeq ($(CROSS)$(CC), emcc)
LDFLAGS += --shell-file shell.html --closure 1
CPPFLAGS += -Wbad-function-cast -Wcast-function-type -sSTACK_SIZE=1MB -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=128MB
NDEBUG = 1
ARCH:= wasm
OPT=small
endif
ifdef NEDITOR
@ -51,6 +53,10 @@ else
INFO :=$(INFO)_dbg
endif
ifdef NSTEAM
CPPFLAGS += -DNSTEAM
endif
ifdef LEAK
CPPFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -DLEAK
INFO := $(INFO)_leak
@ -69,16 +75,12 @@ else
CPPFLAGS += -O2
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
# ENABLE_SINC_[BEST|FAST|MEDIUM]_CONVERTER
# 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)
ifeq ($(OS), Windows_NT) # then WINDOWS
@ -88,9 +90,6 @@ ifeq ($(OS), Windows_NT) # then WINDOWS
LDFLAGS += -mwin32 -static
CPPFLAGS += -mwin32
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/\*
ZIP = .zip
UNZIP = unzip -o -q $(DISTDIR)/$(DIST) -d $(DESTDIR)
INFO :=$(INFO)_win
EXT = .exe
else ifeq ($(OS), IOS)
@ -101,11 +100,11 @@ else ifeq ($(OS), IOS)
LDFLAGS += -isysroot $(SDK_PATH) -miphoneos-version-min=13.0
LDFLAGS += -framework Foundation -framework UIKit -framework AudioToolbox -framework Metal -framework MetalKit -framework AVFoundation
CXXFLAGS += -std=c++11
CFLAGS += -x objective-c
CFLAGS += -x objective-c -DIOS
INFO :=$(INFO)_ios
else ifeq ($(OS), wasm) # Then WEB
OS := Web
LDFLAGS += -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2
LDFLAGS += -sUSE_WEBGPU
CPPFLAGS += -DNSTEAM
LDLIBS += GL openal c m dl
STEAMAPI :=
@ -116,8 +115,9 @@ else
OS := Linux
PLATFORM := linux64
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
STEAMAPI := steam_api
endif
ifeq ($(UNAME), Darwin)
@ -128,9 +128,14 @@ else
CXXFLAGS += -std=c++11
LDFLAGS += -framework Cocoa -framework QuartzCore -framework AudioToolbox -framework Metal -framework MetalKit
INFO :=$(INFO)_macos
STEAMAPI := steam_api
endif
endif
ifdef NSTEAM
STEAMAPI =
endif
# All other sources
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'
@ -145,16 +150,18 @@ OBJS := $(patsubst %.m, %$(INFO).o, $(OBJS))
engineincs != find source/engine -maxdepth 1 -type d
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 += source
includeflag := $(addprefix -I, $(includeflag))
WARNING_FLAGS = -Wno-incompatible-function-pointer-types -Wno-incompatible-pointer-types
# For vanilla compilation, remove _
ifeq ($(INFO),_)
INFO :=
endif
APP = prosperon
NAME = $(APP)$(INFO)$(EXT)
SEM != git describe --tags --abbrev=0
@ -166,22 +173,33 @@ LDPATHS := $(STEAM)/redistributable_bin/$(PLATFORM)
LDPATHS := $(addprefix -L, $(LDPATHS))
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
all: $(NAME)
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)
@echo Linking $(NAME)
$(CROSS)$(LD) $^ $(CPPFLAGS) $(LDFLAGS) -L. $(LDPATHS) $(LDLIBS) -o $@
@ -199,16 +217,7 @@ $(NAME): $(OBJS) $(DEPS)
@echo Making Objective-C object $@
$(CROSS)$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
shaders: $(SHADERS)
@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)
CORE != (ls icons/* fonts/* shaders/*.cg scripts/*.js*)
packer: tools/packer.c source/engine/miniz.c
@echo Making packer
@ -218,7 +227,7 @@ core.cdb: packer $(CORE)
@echo Packing core.cdb
./packer $@ $(CORE)
source/engine/core.cdb.h: core.cdb
core.cdb.h: core.cdb
@echo Making $@
xxd -i $< > $@
@ -261,12 +270,12 @@ crosswin:
make CROSS=x86_64-w64-mingw32- OS=Windows_NT CC=gcc
crossweb:
make CROSS=em OS=wasm
make CC=emcc OS=wasm
mv $(APP).html index.html
clean:
@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
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
#+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
# Engine Tour
Prosperon is built in a code-first fashion.
** 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]].
## Scripting
The scripting language used in Prosperon is Javascript, with QuickJS. It is ES2023 compliant. It is fast.
#+begin_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.
#+end_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.
*** 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 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.
#+begin_scholium
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
!!! scholium
It is a common requirement to add some amount of functionality to an object. It can be easily done with a script file.
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.
| script | When called |
|-----------------+---------------------------------------------|
|-----------------|---------------------------------------------|
| config.js | First script on Prosperon load |
| game.js | Entry point for running the game |
| 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 |
| dbgret.js | After stopping debug mode, used for cleanup |
#+begin_scholium
Try it out. Add a script called ~config.js~ to your folder, with this.
!!! scholium
Try it out. Add a script called `config.js` to your folder, with this.
#+begin_src
console.log("Hello world!");
Game.quit();
#+end_src
Run ~prosperon~. You will see "Hello world!" in the console, and it shuts down.
#+end_scholium
```
console.log("Hello world!");
Game.quit();
```
Run `prosperon`. You will see "Hello world!" in the console, and it shuts down.
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 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 |
|---------------------+----------------------------------------------------------|
|---------------------|----------------------------------------------------------|
| spawn(text, config) | Creates an actor as the padawan of this one, using text |
| kill() | Kills an 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.
*** Turns
### 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.
| function | call time |
|----------+----------------------------------------------------------|
|----------|----------------------------------------------------------|
| start | The first function called when the actor is in the world |
| update | Called once per turn |
| gui | Called on GUI draw |
| stop | Called when the actor is killed |
| gizmo | Called by the editor when the entity is selected |
#+begin_scholium
Create a new actor, then kill it.
#+begin_src
var act_die_call = function() {
!!! scholium
Create a new actor, then kill it.
```
var act_die_call = function() {
console.log(`Actor ${this.id} has died.`);
}
var act1 = Empyrean.spawn();
var act2 = actor1.spawn();
act1.stop = act_die_call;
act2.stop = act_die_call;
Empyrean.kill(); /* Error: The Empyrean cannot be killed */
act1.kill();
act2.kill(); /* Error: act2 has been killed because act1 was */
#+end_src
#+end_scholium
}
var act1 = Empyrean.spawn();
var act2 = actor1.spawn();
act1.stop = act_die_call;
act2.stop = act_die_call;
Empyrean.kill(); /* Error: The Empyrean cannot be killed */
act1.kill();
act2.kill(); /* Error: act2 has been killed because act1 was */
```
#+begin_scholium
Now simplify by putting the code into a file named *hello.jso*.
#+begin_src
this.stop = function() {
!!! scholium
Now simplify by putting the code into a file named *hello.jso*.
```
this.stop = function() {
console.log(`Actor ${this.id} has died.`);
}
#+end_src
Now spawn two actors using it.
#+begin_src
var act1 = Empyrean.spawn("hello.jso");
var act2 = act1.spawn("hello.jso");
#+end_src
#+end_scholium
}
```
Now spawn two actors using it.
```
var act1 = Empyrean.spawn("hello.jso");
var act2 = act1.spawn("hello.jso");
```
*** 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.
#+begin_scholium
Add a name for the actor to take on using a configuration file named *hello.json*.
#+begin_src
{
!!! scholium
Add a name for the actor to take on using a configuration file named *hello.json*.
```
{
"name": "Actor 1"
}
#+end_src
Now create *hello.jso* to use it.
#+begin_src
this.start = function() { console.log(`I, ${this.name}, have been created.`); }
#+end_src
#+end_scholium
}
```
Now create *hello.jso* to use it.
```
this.start = function() { console.log(`I, ${this.name}, have been created.`); }
```
** 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.
#+begin_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.
#+end_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.
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.
*** 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.
#+begin_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.
#+begin_src
var ent = Empyrean.spawn();
var spr = ent.add_component(component.sprite);
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.
!!! 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.
```
var ent = Empyrean.spawn();
var spr = ent.add_component(component.sprite);
spr.path = "image.png";
```
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.
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.
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").
#+begin_scholium
Create an ur from the *hello* files above, and then spawn it.
#+begin_src
ur.create("hello", "hello.jso", "hello.json");
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.
#+end_scholium
!!! scholium
Create an ur from the *hello* files above, and then spawn it.
```
ur.create("hello", "hello.jso", "hello.json");
Primum.spawn(ur.hello);
```
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.
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.
*** Urs in game
### Urs in game
Each ur has the following fields.
| field | description |
|-----------+-------------------------------------------------------------|
|-----------|-------------------------------------------------------------|
| instances | An array of instances of this ur |
| name | Name of the ur |
| text | Path to the script file |
| data | Object to write to a newly generated actor |
| 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.
Entities all have the following functions to assist with this:
| function | use |
|---------------+---------------------------------------------|
|---------------|---------------------------------------------|
| clone(parent) | Create an entity prototyped from the parent |
| dup(parent) | Create an exact duplicate of the parent |
| revert() | Removes all local changes on the entity |
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.
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
Let's take a simple RPG game.
#+begin_src
Primum
!!! scholium
Let's take a simple RPG game.
```
Primum
level1
orc
goblin
human
sword
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.
#+end_scholium
```
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.
*** 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.
### 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.
| sigil | meaning |
|--------+------------------------|
|--------|------------------------|
| \slash | root of project |
| @ | root of save directory |
| # | 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.
#+begin_src
```
/
score.wav
/bumper
@ -310,22 +264,21 @@ Resources can be referenced in a relative manner by actor scripts. When it comes
/ball
hit.wav
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*.
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! 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
!!! 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/.
**** 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
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.
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.
#+begin_src
```
@/
flipper.js
flipper/
@ -345,50 +298,50 @@ Only one ur of any name can be created.
flipper.js
left/
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.
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/
car <== When a key is pressed, this is the first pawn to handle input
player
ui <== /block/ is set to true here, so editor recieves no input!
editor
/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.
*** 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.
| symbol | key |
|--------+-------|
|--------|-------|
| C | ctrl |
| M | alt |
| S | super |
#+begin_src
```
var orc = Primum.spawn(ur.orc);
orc.inputs = {};
orc.inputs.a = function() { ... };
orc.inputs.A = function() { ... }; /* This is only called with a capital A! */
orc.inputs['C-a'] = function() { ... }; /* Control-a */
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.
| property | type | effect |
|----------------+----------+--------------------------------------|
|----------------|----------|--------------------------------------|
| post | function | called after any input is processed |
| =release_post= | function | called after any input is released |
| 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.
| property | type | effect |
|----------+----------+--------------------------------------------------------|
|----------|----------|--------------------------------------------------------|
| released | function | Called when the input is released |
| rep | bool | true if holding the input should repeatedly trigger it |
| down | function | called while the input is down |
** GUI
## GUI
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.
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.
* 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.
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 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.
** 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 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
Easily run commands on multiple entities using Javascript functions like for each.
#+begin_src
$.forEach(e => console.log(e.pos));
#+end_src
#+end_scholium
!!! scholium
Easily run commands on multiple entities using Javascript functions like for each.
```
$.forEach(e => console.log(e.pos));
```
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.
| key | action |
|-------+-----------------------------------------------------|
|-------|-----------------------------------------------------|
| f5 | Play the game, starting with entry point /debug.js/ |
| f6 | Play the game from the beginning |
While playing the game, a limited editor is available that allows for simple debugging tasks.
| key | action |
|-----+-----------------------------|
|-----|-----------------------------|
| C-p | Pause |
| M-p | One time step |
| 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.
*** 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.
** 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.
| key | description |
|-----+----------------------------|
|-----|----------------------------|
| F1 | Draw physics info |
| F3 | Draw bounding boxes |
| F12 | Draw gui info |
@ -478,7 +430,7 @@ Prosperon is a multiplatform engine. Bundling your game for these platforms esse
- Conversion of assets
- 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 |
|----------|
@ -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!
** Building static content
## Building static content
Static content creation involves any number of optimizations.
- 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.
** 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.
** 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.
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
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.
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
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/.
## 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/.
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.

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){
if (typeof script !== 'string') return undefined;
console.info(`spawning actor with script ${script}`);
var padawan = Object.create(actor);
use(script, padawan);

View file

@ -358,13 +358,6 @@ Object.readonly = function(o, name, msg)
Object.defineProperty(o, name, prop);
}
Object.extend = function(from)
{
var n = {};
Object.mixin(n, from);
return n;
}
Object.mixin = function(target, source)
{
if (typeof source !== 'object') return target;
@ -542,10 +535,8 @@ Object.unhide = function(obj, ...props)
{
for (var prop of props) {
var p = Object.getOwnPropertyDescriptor(obj,prop);
if (!p) {
console.warn(`No property of name ${prop}.`);
return;
}
if (!p)
continue;
p.enumerable = true;
Object.defineProperty(obj, prop, p);
}
@ -810,6 +801,13 @@ Object.defineProperty(String.prototype, 'name', {
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', {
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', {
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 */
/*Object.defineProperty(Array.prototype, 'includes', {
value: function(x) {
return this.some(e => e === x);
}});
*/
Object.defineProperty(Array.prototype, 'empty', {
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', {
value: function(fn) {
@ -1217,6 +1219,15 @@ Object.defineProperty(Array.prototype, 'find', {
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', {
value: function() { return this[this.length-1]; },
});
@ -1336,16 +1347,19 @@ Math.snap = function(val, grid) {
}
Math.angledist = function (a1, a2) {
a1 = Math.turn2deg(a1);
a2 = Math.turn2deg(a2);
a1 = a1%1;
a2 = a2%1;
var dist = a2 - a1;
var wrap = dist >= 0 ? dist+360 : dist-360;
wrap %= 360;
if (dist == 0) return dist;
if (Math.abs(dist) < Math.abs(wrap))
return Math.deg2turn(dist);
if (dist > 0) {
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.TAU = Math.PI*2;
@ -1492,6 +1506,16 @@ Vector.random = function() {
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.rotate = function(v,angle) {
var r = Vector.length(v);
@ -1543,18 +1567,74 @@ Math.sortpointsccw = function(points)
var cm = points2cm(points);
var cmpoints = points.map(function(x) { return x.sub(cm); });
var ccw = cmpoints.sort(function(a,b) {
aatan = Math.atan2(a.y, a.x);
batan = Math.atan2(b.y, b.x);
var aatan = Math.atan2(a.y, a.x);
var batan = Math.atan2(b.y, b.x);
return aatan - batan;
});
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 {
convert,
time,
json,
Vector,
bbox
bbox,
yaml
};

View file

@ -13,6 +13,10 @@ this.view2world = function(pos) {
pos = pos.scale(this.zoom);
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;
};
this.world2view = function(pos) {
@ -25,6 +29,9 @@ this.world2view = function(pos) {
pos = pos.sub(this.pos);
pos = pos.scale(1/this.zoom);
pos = pos.add(window.size.scale(0.5));
}
if (window.mode === window.modetypes.expand) {
}
return pos;
};

View file

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

View file

@ -1,44 +1,4 @@
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 component = {};
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 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), {
var sprite = {
loop: true,
toJSON:json_from_whitelist([
"path",
"pos",
"scale",
"angle",
"color",
"emissive",
"parallax",
"frame"
]),
anim:{},
playing: 0,
play(str = 0) {
@ -110,6 +37,7 @@ Object.mixin(os.sprite(true), {
if (!self.gameobject) return;
//self.path = playing.path;
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;
if (f === 0) {
self.anim_done?.();
@ -117,7 +45,7 @@ Object.mixin(os.sprite(true), {
}
stop = self.gameobject.delay(advance, playing.frames[f].time);
}
this.tex(game.texture(playing.path));
advance();
},
stop() {
@ -133,7 +61,9 @@ Object.mixin(os.sprite(true), {
this._p = p;
this.del_anim?.();
this.texture = game.texture(p);
this.tex(this.texture);
this.diffuse = this.texture;
this.rect = [0,0,1,1];
var anim = SpriteAnim.make(p);
if (!anim) return;
@ -141,6 +71,7 @@ Object.mixin(os.sprite(true), {
this.play();
this.pos = this.dimensions().scale(this.anchor);
},
get path() {
return this._p;
@ -150,8 +81,8 @@ Object.mixin(os.sprite(true), {
this.anim = undefined;
this.gameobject = undefined;
this.anim_done = undefined;
delete allsprites[this.guid];
},
toString() { return "sprite"; },
move(d) { this.pos = this.pos.add(d); },
grow(x) {
this.scale = this.scale.scale(x);
@ -175,19 +106,8 @@ Object.mixin(os.sprite(true), {
},
width() { return this.dimensions().x; },
height() { return this.dimensions().y; },
});
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;
};
globalThis.allsprites = {};
sprite.doc = {
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.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
frames: array of frames
@ -251,13 +182,11 @@ SpriteAnim.make = function(path) {
return animcache[path];
};
SpriteAnim.gif = function(path) {
console.info(`making an anim from ${path}`);
var anim = {};
anim.frames = [];
anim.path = path;
var tex = game.texture(path);
var frames = tex.frames;
console.info(`frames are ${frames}`);
if (frames === 1) return undefined;
var yslice = 1/frames;
for (var f = 0; f < frames; f++) {
@ -276,7 +205,6 @@ SpriteAnim.gif = function(path) {
anim.frames[i].time = times[i]/1000;
anim.loop = true;
var dim = [tex.width,tex.height];
console.info(`dimensions are ${dim}`);
dim.y /= frames;
anim.dim = dim;
return {0:anim};
@ -329,7 +257,7 @@ SpriteAnim.aseprite = function(path) {
var anims = {};
var frames = Array.isArray(data.frames) ? data.frames : Object.values(data.frames);
var f = 0;
if (data.meta.frameTags.length === 0) {
if (!data.meta.frameTags || data.meta.frameTags.length === 0) {
anims[0] = aseframeset2anim(frames, data.meta);
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.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 = 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');
var collider2d = {};
collider2d.inputs = {};
collider2d.inputs['M-s'] = function() { this.sensor = !this.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'].doc = "Toggle if this collider is enabled.";
component.polygon2d = Object.copy(collider2d, {
toJSON:json_from_whitelist([
'points',
'sensor'
]),
toString() { return "polygon2d"; },
flipx: false,
flipy: false,
Object.mix(os.make_poly2d(), {
boundingbox() {
return bbox.frompoints(this.spoints());
},
hides: ['id', 'shape', 'gameobject'],
_enghook: os.make_poly2d,
points:[],
setpoints(points) {
this.points = points;
this.sync();
},
/* EDITOR */
spoints() {
var spoints = this.points.slice();
@ -445,63 +346,71 @@ function pointscaler(x) {
this.points = this.points.map(p => p.mult(x));
}
component.polygon2d.impl = Object.mix(collider2d.impl, {
sync() { poly2d.setverts(this.id,this.spoints()); },
query() { return physics.shape_query(this.shape); },
Object.mixin(os.make_poly2d(), {
sync() {
this.setverts(this.points);
},
grow: pointscaler,
});
var polygon2d = component.polygon2d;
var polyinputs = Object.create(collider2d.inputs);
os.make_poly2d().inputs = polyinputs;
polygon2d.inputs = {};
//polygon2d.inputs.post = function() { this.sync(); };
polygon2d.inputs.f10 = function() {
polyinputs = {};
polyinputs.f10 = function() {
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()));
};
polygon2d.inputs['C-lm'].doc = "Add a point to location of mouse.";
polygon2d.inputs.lm = function(){};
polygon2d.inputs.lm.released = function(){};
polyinputs['C-lm'].doc = "Add a point to location of mouse.";
polyinputs.lm = 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);
if (idx === -1) return;
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.flipx = 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, {
toJSON:json_from_whitelist([
'sensor',
'thickness',
'points',
'hollow',
'hollowt',
'angle',
]),
var edge2d = {
dimensions:2,
thickness:0,
thickness:1,
/* if type === -1, point to point */
type: Spline.type.catmull,
C: 1, /* when in bezier, continuity required. 0, 1 or 2. */
looped: false,
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,
flipy: false,
points:[],
toString() { return "edge2d"; },
hollow: false,
hollowt: 0,
@ -540,12 +449,10 @@ component.edge2d = Object.copy(collider2d, {
return arr1.concat(arr2.reverse());
}
return spoints;
},
if (this.looped)
spoints = spoints.wrapped(1);
setpoints(points) {
this.points = points;
// this.sync();
return spoints;
},
post() {
@ -578,9 +485,6 @@ component.edge2d = Object.copy(collider2d, {
boundingbox() { return bbox.frompoints(this.points.map(x => x.scale(this.gameobject.scale))); },
hides: ['gameobject', 'id', 'shape'],
_enghook: os.make_edge2d,
/* EDITOR */
gizmo() {
if (this.type === Spline.type.catmull || this.type === -1) {
@ -688,71 +592,64 @@ component.edge2d = Object.copy(collider2d, {
this.points.forEach(x =>picks.push(make_point_obj(this,x)));
return picks;
},
});
};
component.edge2d.impl = Object.mix(collider2d.impl, {
set thickness(x) { edge2d.set_thickness(this.id,x); },
get thickness() { return edge2d.get_thickness(this.id); },
grow: pointscaler,
sync() {
var sensor = this.sensor;
var points = this.sample();
if (!points) return;
edge2d.setverts(this.id,points);
this.sensor = sensor;
},
});
component.edge2d = function(obj) {
if (!obj.body) obj.rigidify();
var edge = Object.create(edge2d);
edge.body = obj.body;
return edge;
}
var bucket = component.edge2d;
bucket.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises.";
bucket.inputs = {};
bucket.inputs.h = function() { this.hollow = !this.hollow; };
bucket.inputs.h.doc = "Toggle hollow.";
edge2d.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises.";
edge2d.inputs = {};
edge2d.inputs.h = function() { this.hollow = !this.hollow; };
edge2d.inputs.h.doc = "Toggle hollow.";
bucket.inputs['C-g'] = function() { if (this.hollowt > 0) this.hollowt--; };
bucket.inputs['C-g'].doc = "Thin the hollow thickness.";
bucket.inputs['C-g'].rep = true;
edge2d.inputs['C-g'] = function() { if (this.hollowt > 0) this.hollowt--; };
edge2d.inputs['C-g'].doc = "Thin the hollow thickness.";
edge2d.inputs['C-g'].rep = true;
bucket.inputs['C-f'] = function() { this.hollowt++; };
bucket.inputs['C-f'].doc = "Increase the hollow thickness.";
bucket.inputs['C-f'].rep = true;
edge2d.inputs['C-f'] = function() { this.hollowt++; };
edge2d.inputs['C-f'].doc = "Increase the hollow thickness.";
edge2d.inputs['C-f'].rep = true;
bucket.inputs['M-v'] = function() { if (this.thickness > 0) this.thickness--; };
bucket.inputs['M-v'].doc = "Decrease spline thickness.";
bucket.inputs['M-v'].rep = true;
edge2d.inputs['M-v'] = function() { if (this.thickness > 0) this.thickness--; };
edge2d.inputs['M-v'].doc = "Decrease spline thickness.";
edge2d.inputs['M-v'].rep = true;
bucket.inputs['C-y'] = function() {
edge2d.inputs['C-y'] = function() {
this.points = this.spoints();
this.flipx = false;
this.flipy = false;
this.hollow = false;
};
bucket.inputs['C-y'].doc = "Freeze mirroring,";
bucket.inputs['M-b'] = function() { this.thickness++; };
bucket.inputs['M-b'].doc = "Increase spline thickness.";
bucket.inputs['M-b'].rep = true;
edge2d.inputs['C-y'].doc = "Freeze mirroring,";
edge2d.inputs['M-b'] = function() { this.thickness++; };
edge2d.inputs['M-b'].doc = "Increase spline thickness.";
edge2d.inputs['M-b'].rep = true;
bucket.inputs.plus = function() {
edge2d.inputs.plus = function() {
if (this.angle <= 1) {
this.angle = 1;
return;
}
this.angle *= 0.9;
};
bucket.inputs.plus.doc = "Increase the number of samples of this spline.";
bucket.inputs.plus.rep = true;
edge2d.inputs.plus.doc = "Increase the number of samples of this spline.";
edge2d.inputs.plus.rep = true;
bucket.inputs.minus = function() { this.angle *= 1.1; };
bucket.inputs.minus.doc = "Decrease the number of samples on this spline.";
bucket.inputs.minus.rep = true;
edge2d.inputs.minus = function() { this.angle *= 1.1; };
edge2d.inputs.minus.doc = "Decrease the number of samples on this spline.";
edge2d.inputs.minus.rep = true;
bucket.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'] = function() { this.points = this.points.reverse(); };
edge2d.inputs['C-r'].doc = "Reverse the order of the spline's points.";
bucket.inputs['C-l'] = function() { this.looped = !this.looped};
bucket.inputs['C-l'].doc = "Toggle spline being looped.";
edge2d.inputs['C-l'] = function() { this.looped = !this.looped};
edge2d.inputs['C-l'].doc = "Toggle spline being looped.";
bucket.inputs['C-c'] = function() {
edge2d.inputs['C-c'] = function() {
switch(this.type) {
case Spline.type.bezier:
this.points = Spline.bezier2catmull(this.points);
@ -761,9 +658,9 @@ bucket.inputs['C-c'] = function() {
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) {
case Spline.type.catmull:
this.points = Spline.catmull2bezier(Spline.catmull_caps(this.points));
@ -772,10 +669,10 @@ bucket.inputs['C-b'] = function() {
this.type = Spline.type.bezier;
};
bucket.inputs['C-o'] = function() { this.type = -1; };
bucket.inputs['C-o'].doc = "Set spline to linear.";
edge2d.inputs['C-o'] = function() { this.type = -1; };
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)) {
var idx = Math.grab_from_points(input.mouse.worldpos(), this.points.map(p => this.gameobject.this2world(p)), 25);
if (idx === -1) return;
@ -785,12 +682,12 @@ bucket.inputs['C-M-lm'] = function() {
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()); }
bucket.inputs['C-lm'].doc = "Add a point to the spline at the mouse position.";
edge2d.inputs['C-lm'] = function() { this.add_node(input.mouse.worldpos()); }
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;
if (Spline.is_catmull(this.type))
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);
};
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(){};
bucket.inputs.lm.released = function(){};
edge2d.inputs.lm = function(){};
edge2d.inputs.lm.released = function(){};
bucket.inputs.lb = function() {
edge2d.inputs.lb = function() {
var np = [];
this.points.forEach(function(c) {
@ -816,10 +713,10 @@ bucket.inputs.lb = function() {
this.points = np;
};
bucket.inputs.lb.doc = "Rotate the points CCW.";
bucket.inputs.lb.rep = true;
edge2d.inputs.lb.doc = "Rotate the points CCW.";
edge2d.inputs.lb.rep = true;
bucket.inputs.rb = function() {
edge2d.inputs.rb = function() {
var np = [];
this.points.forEach(function(c) {
@ -828,37 +725,27 @@ bucket.inputs.rb = function() {
this.points = np;
};
bucket.inputs.rb.doc = "Rotate the points CW.";
bucket.inputs.rb.rep = true;
edge2d.inputs.rb.doc = "Rotate the points CW.";
edge2d.inputs.rb.rep = true;
component.circle2d = Object.copy(collider2d, {
radius:10,
offset:[0,0],
toString() { return "circle2d"; },
/* CIRCLE */
boundingbox() {
return bbox.fromcwh([0,0], [this.radius,this.radius]);
},
function shape_maker(maker) {
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'],
_enghook: os.make_circle2d,
});
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); },
Object.mix(os.make_circle2d(), {
boundingbox() { return bbox.fromcwh(this.offset, [this.radius,this.radius]); },
set scale(x) { this.radius = x; },
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; },
set pos(x) { this.offset = x; },
@ -866,7 +753,6 @@ component.circle2d.impl = Object.mix({
if (typeof x === 'number') this.scale *= x;
else if (typeof x === 'object') this.scale *= x[0];
},
}, collider2d.impl);
});
return {component, SpriteAnim};

View file

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

View file

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

View file

@ -1,107 +1,97 @@
"use math";
Object.defineProperty(String.prototype, 'tolast', {
value: function(val) {
Object.defineProperty(String.prototype, "tolast", {
value: function (val) {
var idx = this.lastIndexOf(val);
if (idx === -1) return this.slice();
return this.slice(0,idx);
}
return this.slice(0, idx);
},
});
Object.defineProperty(String.prototype, 'dir', {
value: function() {
if (!this.includes('/')) return "";
return this.tolast('/');
}
Object.defineProperty(String.prototype, "dir", {
value: function () {
if (!this.includes("/")) return "";
return this.tolast("/");
},
});
Object.defineProperty(String.prototype, 'folder', {
value: function() {
Object.defineProperty(String.prototype, "folder", {
value: function () {
var dir = this.dir();
if (!dir) return "";
else return dir + "/";
}
},
});
globalThis.Resources = {};
Resources.replpath = function(str, path)
{
Resources.replpath = function (str, path) {
if (!str) return str;
if (str[0] === "/")
return str.rm(0);
if (str[0] === "/") return str.rm(0);
if (str[0] === "@")
return os.prefpath() + "/" + str.rm(0);
if (str[0] === "@") return os.prefpath() + "/" + str.rm(0);
if (!path) return str;
var stem = path.dir();
while (stem) {
var tr = stem + "/" +str;
var tr = stem + "/" + str;
if (io.exists(tr)) return tr;
stem = stem.updir();
}
return str;
}
};
Resources.replstrs = function(path)
{
Resources.replstrs = function (path) {
if (!path) return;
var script = io.slurp(path);
var regexp = /"[^"\s]*?\.[^"\s]+?"/g;
var stem = path.dir();
script = script.replace(regexp,function(str) {
script = script.replace(regexp, function (str) {
var newstr = Resources.replpath(str.trimchr('"'), path);
return `"${newstr}"`;
});
return script;
}
};
globalThis.json = {};
json.encode = function(value, replacer, space = 1)
{
json.encode = function (value, replacer, space = 1) {
return JSON.stringify(value, replacer, space);
}
};
json.decode = function(text, reviver)
{
json.decode = function (text, reviver) {
if (!text) return undefined;
return JSON.parse(text,reviver);
}
return JSON.parse(text, reviver);
};
json.readout = function(obj)
{
json.readout = function (obj) {
var j = {};
for (var k in obj)
if (typeof obj[k] === 'function')
j[k] = 'function ' + obj[k].toString();
else
j[k] = obj[k];
if (typeof obj[k] === "function") j[k] = "function " + obj[k].toString();
else j[k] = obj[k];
return json.encode(j);
}
};
json.doc = {
doc: "json implementation.",
encode: "Encode a value to json.",
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.images = ["png", "gif", "jpg", "jpeg"];
Resources.sounds = ["wav", 'flac', 'mp3', "qoa"];
Resources.is_image = function(path) {
Resources.sounds = ["wav", "flac", "mp3", "qoa"];
Resources.is_image = function (path) {
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;
for (var e of ext) {
var nf = `${file}.${e}`;
@ -110,46 +100,48 @@ function find_ext(file, ext)
return;
}
Resources.find_image = function(file) { return find_ext(file,Resources.images); }
Resources.find_sound = function(file) { return find_ext(file,Resources.sounds); }
Resources.find_script = function(file) { return find_ext(file,Resources.scripts); }
Resources.find_image = function (file) {
return find_ext(file, Resources.images);
};
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) {
var qq = 'ns';
profile.best_t = function (t) {
var qq = "ns";
if (t > 1000) {
t /= 1000;
qq = 'us';
qq = "us";
if (t > 1000) {
t /= 1000;
qq = 'ms';
qq = "ms";
}
}
return `${t.toPrecision(4)} ${qq}`;
}
};
profile.report = function(start, msg = "[undefined report]")
{
console.info(`${msg} in ${profile.best_t(profile.now()-start)}`);
}
profile.report = function (start, msg = "[undefined report]") {
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].push(profile.now()-start);
}
cache[line].push(profile.now() - start);
};
profile.printreport = function(cache, name)
{
profile.printreport = function (cache, name) {
var report = name + "\n";
for (var i in cache) {
report += `${i} ${profile.best_t(profcache[i].reduce((a,b) => a+b)/profcache[i].length)}\n`;
}
for (var i in cache)
report += `${i} ${profile.best_t(cache[i].reduce((a, b) => a + b) / cache[i].length)}\n`;
return report;
}
};
console.transcript = "";
console.say = function(msg) {
console.say = function (msg) {
msg += "\n";
console.print(msg);
console.transcript += msg;
@ -158,16 +150,14 @@ console.log = console.say;
globalThis.say = console.say;
globalThis.print = console.print;
console.pprint = function(msg,lvl = 0) {
if (typeof msg === 'object')
msg = JSON.stringify(msg, null, 2);
console.pprint = function (msg, lvl = 0) {
if (typeof msg === "object") msg = JSON.stringify(msg, null, 2);
var file = "nofile";
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) {
var md = caller.match(/\((.*)\:/);
var m = md ? md[1] : "SCRIPT";
@ -180,19 +170,33 @@ console.pprint = function(msg,lvl = 0) {
console.rec(lvl, msg, file, line);
};
console.spam = function(msg) { console.pprint (msg,0); };
console.debug = function(msg) { console.pprint(msg,1); };
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) {
console.spam = function (msg) {
console.pprint(msg, 0);
};
console.debug = function (msg) {
console.pprint(msg, 1);
};
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 stack = err.stack.split('\n');
return stack.slice(skip,stack.length).join('\n');
var stack = err.stack.split("\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.trace = console.stack;
@ -207,15 +211,14 @@ console.doc = {
say: "Write raw text to console, plus a newline.",
stack: "Output a stacktrace to console.",
console: "Output directly to in game console.",
clear: "Clear console."
clear: "Clear console.",
};
globalThis.global = globalThis;
var profcache = {};
function use(file, env = {}, script)
{
function use(file, env = {}, script) {
file = Resources.find_script(file);
var st = profile.now();
@ -228,7 +231,7 @@ function use(file, env = {}, script)
}
script ??= Resources.replstrs(file);
script = `(function() { var self = this; ${script}; })`;
var fn = os.eval(file,script);
var fn = os.eval(file, script);
use.cache[file] = fn;
var ret = fn.call(env);
profile.addreport(profcache, file, st);
@ -237,158 +240,181 @@ function use(file, env = {}, script)
use.cache = {};
global.check_registers = function(obj)
{
if (typeof obj.update === 'function')
global.check_registers = function (obj) {
if (typeof obj.update === "function")
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)));
if (typeof obj.draw === 'function')
if (typeof obj.draw === "function")
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)));
if (typeof obj.gui === 'function')
if (typeof obj.gui === "function")
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)));
for (var k in obj) {
if (!k.startswith("on_")) continue;
var signal = k.fromfirst("on_");
Event.observe(signal, obj, obj[k]);
};
}
}
};
Object.assign(global, use("scripts/base"));
global.obscure('global');
global.obscure("global");
global.mixin("scripts/render");
global.mixin("scripts/debug");
var frame_t = profile.secs(profile.now());
var phys_step = 1/240;
var sim = {};
sim.mode = "play";
sim.play = function() { this.mode = "play"; os.reindex_static(); };
sim.playing = function() { 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'; }
sim.play = function () {
this.mode = "play";
os.reindex_static();
};
sim.playing = function () {
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 gggstart = game.engine_start;
game.engine_start = function(s) {
game.engine_start = function (s) {
game.startengine = 1;
gggstart(function() {
gggstart(
function () {
global.mixin("scripts/sound.js");
world_start();
go_init();
window.set_icon(os.make_texture("icons/moon.gif"))
Object.readonly(window.__proto__, 'vsync');
Object.readonly(window.__proto__, 'enable_dragndrop');
Object.readonly(window.__proto__, 'enable_clipboard');
Object.readonly(window.__proto__, 'high_dpi');
Object.readonly(window.__proto__, 'sample_count');
window.set_icon(os.make_texture("icons/moon.gif"));
Object.readonly(window.__proto__, "vsync");
Object.readonly(window.__proto__, "enable_dragndrop");
Object.readonly(window.__proto__, "enable_clipboard");
Object.readonly(window.__proto__, "high_dpi");
Object.readonly(window.__proto__, "sample_count");
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;
var frames = [];
function process()
{
function process() {
var startframe = profile.now();
var dt = profile.secs(profile.now()) - frame_t;
frame_t = profile.secs(profile.now());
prosperon.appupdate(dt);
prosperon.emitters_step(dt);
input.procdown();
if (sim.mode === "play" || sim.mode === "step") {
prosperon.update(dt*game.timescale);
if (sim.mode === "step")
sim.pause();
prosperon.update(dt * game.timescale);
if (sim.mode === "step") sim.pause();
physlag += dt;
while (physlag > phys_step) {
physlag -= phys_step;
prosperon.phys2d_step(phys_step*game.timescale);
prosperon.physupdate(phys_step*game.timescale);
while (physlag > physics.delta) {
physlag -= physics.delta;
var st = profile.now();
prosperon.phys2d_step(physics.delta * game.timescale);
prosperon.physupdate(physics.delta * game.timescale);
profile.addreport(profcache, "physics step", st);
}
}
if (!game.camera)
prosperon.window_render(world, 1);
else
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);
var st = profile.now();
prosperon.window_render(window.size);
prosperon.draw();
prosperon.debug();
prosperon.gui();
render.flush();
render.hud_res(window.size);
prosperon.screengui();
render.flush();
render.end_pass();
prosperon.hookend?.();
profile.addreport(profcache, "render frame", st);
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;
var eachobj = function(obj,fn)
{
var eachobj = function (obj, fn) {
var val = fn(obj);
if (val) return val;
for (var o in obj.objects) {
if (obj.objects[o] === obj)
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;
}
}
};
game.all_objects = function(fn, startobj = world) { return eachobj(startobj,fn); };
game.find_object = function(fn, startobj = world) {
}
game.all_objects = function (fn, startobj = world) {
return eachobj(startobj, fn);
};
game.find_object = function (fn, startobj = world) {};
game.tags = {};
game.tag_add = function(tag, obj) {
game.tag_add = function (tag, obj) {
game.tags[tag] ??= {};
game.tags[tag][obj.guid] = obj;
}
};
game.tag_rm = function(tag, obj) {
game.tag_rm = function (tag, obj) {
delete game.tags[tag][obj.guid];
}
};
game.tag_clear_guid = function(guid)
{
for (var tag in game.tags)
delete game.tags[tag][guid];
}
game.tag_clear_guid = function (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 [];
return Object.values(game.tags[tag]);
}
};
game.doc = {};
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.camera = "Current camera.";
game.texture = function(path)
{
if (game.texture.cache[path]) return game.texture.cache[path];
game.texture = function (path, force = false) {
if (force && game.texture.cache[path]) return game.texture.cache[path];
if (!io.exists(path)) {
console.warn(`Missing texture: ${path}`);
game.texture.cache[path] = game.texture("icons/no_tex.gif");
} else
game.texture.cache[path] ??= os.make_texture(path);
} else game.texture.cache[path] ??= os.make_texture(path);
return game.texture.cache[path];
}
};
game.texture.cache = {};
prosperon.semver = {};
prosperon.semver.valid = function(v, range)
{
v = v.split('.');
range = range.split('.');
prosperon.semver.valid = function (v, range) {
v = v.split(".");
range = range.split(".");
if (v.length !== 3) return undefined;
if (range.length !== 3) return undefined;
if (range[0][0] === '^') {
if (range[0][0] === "^") {
range[0] = range[0].slice(1);
if (parseInt(v[0]) >= parseInt(range[0])) return true;
return false;
}
if (range[0] === '~') {
if (range[0] === "~") {
range[0] = range[0].slice(1);
for (var i = 0; i < 2; i++)
if (parseInt(v[i]) < parseInt(range[i])) return false;
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)
{
var ver1 = v1.split('.');
var ver2 = v2.split('.');
prosperon.semver.cmp = function (v1, v2) {
var ver1 = v1.split(".");
var ver2 = v2.split(".");
for (var i = 0; i < 3; i++) {
var n1 = parseInt(ver1[i]);
var n2 = parseInt(ver2[i]);
if (n1 > n2)
return 1;
else if (n1 < n2)
return -1;
if (n1 > n2) return 1;
else if (n1 < n2) return -1;
}
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.cmp.doc = "Compare two semantic version numbers, given like X.X.X.";
prosperon.semver.doc =
"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.
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 must match exactly, but any MINOR and PATCH greater or equal is valid.`;
prosperon.iconified = function(icon) {};
prosperon.focus = function(focus) {};
prosperon.resize = function(dimensions) {};
prosperon.suspended = function(sus) {};
prosperon.mouseenter = function(){};
prosperon.mouseleave = function(){};
prosperon.touchpress = function(touches){};
prosperon.touchrelease = function(touches){};
prosperon.touchmove = function(touches){};
prosperon.clipboardpaste = function(str){};
prosperon.quit = function(){
prosperon.iconified = function (icon) {};
prosperon.focus = function (focus) {};
prosperon.resize = function (dimensions) {
window.size.x = dimensions.x;
window.size.y = dimensions.y;
};
prosperon.suspended = function (sus) {};
prosperon.mouseenter = function () {};
prosperon.mouseleave = function () {};
prosperon.touchpress = function (touches) {};
prosperon.touchrelease = function (touches) {};
prosperon.touchmove = function (touches) {};
prosperon.clipboardpaste = function (str) {};
prosperon.quit = function () {
say(profile.printreport(profcache, "USE REPORT"));
say(profile.printreport(entityreport, "ENTITY REPORT"));
console.info("QUITTING");
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");
@ -526,18 +551,21 @@ var Register = {
var n = {};
var fns = [];
n.register = function(fn, obj) {
if (typeof fn !== 'function') return;
if (typeof obj === 'object')
fn = fn.bind(obj);
n.register = function (fn, obj) {
if (typeof fn !== "function") return;
if (typeof obj === "object") fn = fn.bind(obj);
fns.push(fn);
return function() {
return function () {
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;
n.clear = function() { fns = []; }
n.clear = function () {
fns = [];
};
Register[name] = n;
Register.registries.push(n);
@ -563,33 +591,32 @@ var Event = {
},
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) {
Object.keys(this.events).forEach(name => Event.unobserve(name,obj));
Object.keys(this.events).forEach((name) => Event.unobserve(name, obj));
},
notify(name, ...args) {
if (!this.events[name]) return;
this.events[name].forEach(function(x) {
this.events[name].forEach(function (x) {
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.modetypes = {
stretch: 0, // stretch render to fill window
keep: 1, // keep render exact dimensions, with no stretching
width: 2, // keep render at width
height: 3, // keep render at height
expand: 4, // expand width or height
full: 5 // expand out beyond window
};
// set to one of the following
// stretch render to fill window
// keep render exact dimensions, with no stretching
// width keep render at width
// height keep render at height
// expand width or height
// full expand out beyond window
window.size = [640, 480];
window.mode = "keep";
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");
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.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/entity");
function world_start() {
globalThis.world = os.make_gameobject();
globalThis.world = Object.create(entity);
world.transform = os.make_transform();
world.objects = {};
world.toString = function() { return "world"; };
world.toString = function () {
return "world";
};
world.ur = "world";
world.kill = function() { this.clear(); };
world.kill = function () {
this.clear();
};
world.phys = 2;
world.zoom = 1;
world._ed = { selectable: false };
@ -624,9 +658,4 @@ global.mixin("scripts/widget");
globalThis.mum = app.spawn("scripts/mum");
window.title = `Prosperon v${prosperon.version}`;
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);
}
window.size = [500, 500];

View file

@ -12,7 +12,18 @@ function obj_unique_name(name, obj) {
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) {
var comps = [];
for (var c of Object.values(this.components))
@ -22,78 +33,10 @@ var gameobject = {
return undefined;
},
check_dirty() {
this._ed.urdiff = this.json_obj();
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;
rigidify() {
this.body = os.make_body(this.transform);
},
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) {
var p = this.toString();
@ -106,6 +49,10 @@ var gameobject = {
return p;
},
drawlayer: 0,
full_path() { return this.path_from(world); },
clear() {
for (var k in this.objects) {
this.objects[k].kill();
@ -113,6 +60,11 @@ var gameobject = {
this.objects = {};
},
sync() {
this.components.forEach(function(x) { x.sync?.(); });
this.objects.forEach(function(x) { x.sync(); });
},
delay(fn, seconds) {
var timers = this.timers;
var stop = function() {
@ -145,110 +97,60 @@ var gameobject = {
cry(file) { return audio.cry(file); },
set pos(x) { this.set_pos(x); },
get pos() { return this.rpos; },
set angle(x) { this.set_angle(x); },
get angle() { return this.rangle; },
set scale(x) { this.set_scale(x); },
get scale() { return this.rscale; },
get pos() { return this.transform.pos; },
set pos(x) { this.transform.pos = x; },
get angle() { return this.transform.angle; },
set angle(x) { this.transform.angle = x; },
get scale() { return this.transform.scale; },
set scale(x) { this.transform.scale = x; },
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]);
},
/* Moving, rotating, scaling functions, world relative */
move(vec) { this.set_pos(this.pos.add(vec)); },
rotate(x) { this.set_angle(this.angle + x); },
move(vec) { this.pos = this.pos.add(vec); },
rotate(x) { this.transform.rotate(x, [0,0,-1]); },
grow(vec) {
if (typeof vec === 'number') vec = [vec,vec,vec];
this.set_scale(this.scale.map((x, i) => x * vec[i]));
if (typeof vec === 'number') vec = [vec,vec];
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) {
var st = profile.now();
var ent = os.make_gameobject();
var ent = Object.create(entity);
ent.transform = os.make_transform();
ent.guid = prosperon.guid();
ent.components = {};
ent.objects = {};
ent.timers = [];
Object.mixin(ent, {
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;
}
});
if (typeof text === 'object' && text) {// assume it's an ur
if (!text)
ent.ur = emptyur;
else if (typeof text === 'object' && text) {// assume it's an ur
ent.ur = text;
text = ent.ur.text;
config = [ent.ur.data, config].filter(x => x).flat();
@ -281,9 +183,8 @@ var gameobject = {
for (var [prop, p] of Object.entries(ent)) {
if (!p) continue;
if (typeof p !== 'object') continue;
if (component.isComponent(p)) continue;
if (!p.comp) continue;
ent[prop] = component[p.comp].make(ent);
ent[prop] = component[p.comp](ent);
Object.merge(ent[prop], p);
ent.components[prop] = ent[prop];
};
@ -294,7 +195,7 @@ var gameobject = {
if (sim.playing())
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 = {
selectable: true,
@ -330,63 +231,21 @@ var gameobject = {
ent.ur.fresh.objects[i] = ent.objects[i].instance_obj();
profile.addreport(entityreport, ent.ur.name, st);
return ent;
},
/* 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;
}
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,
disable() { this.components.forEach(function(x) { x.disable(); }); },
enable() { this.components.forEach(function(x) { x.enable(); }); },
this2screen(pos) { return game.camera.world2view(this.this2world(pos)); },
screen2this(pos) { return this.world2this(game.camera.view2world(pos)); },
in_air() { return this.in_air(); },
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?.(); }); },
/* Make a unique object the same as its prototype */
revert() { Object.merge(this, this.ur.fresh); },
name: "new_object",
toString() { return this.name; },
width() {
var bb = this.boundingbox();
return bb.r - bb.l;
@ -397,19 +256,11 @@ var gameobject = {
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; },
flipy() { return this.scale.y < 0; },
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 */
boundingbox() {
var boxes = [];
@ -476,22 +327,7 @@ var gameobject = {
return t;
},
/* Velocity and angular velocity of the object */
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) {
dup(diff) {
var n = this.master.spawn(this.ur);
Object.totalmerge(n, this.transform());
return n;
@ -515,9 +351,8 @@ var gameobject = {
for (var key in this.components) {
this.components[key].kill?.();
this.components[key].gameobject = undefined;
this[key].enabled = false;
this.components[key].enabled = false;
delete this.components[key];
delete this[key];
}
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) {
for (var prop in objs) {
@ -565,29 +396,168 @@ var gameobject = {
return this.objects[newname];
},
add_component(comp, data, name = comp.toString()) {
if (typeof comp.make !== 'function') return;
name = obj_unique_name(name, this);
this[name] = comp.make(this);
this[name].comp = comp.toString();
this.components[name] = this[name];
if (data)
Object.assign(this[name], data);
return this[name];
add_component(comp, data) {
var name = prosperon.guid();
this.components[name] = comp(this);
if (data) {
Object.assign(this.components[name], data);
this.components[name].sync?.();
}
return this.components[name];
},
}
};
function go_init() {
var gop = os.make_gameobject().__proto__;
Object.mixin(gop, gameobject);
gop.sync = function() {
this.selfsync();
this.components.forEach(function(x) { x.sync?.(); });
this.objects.forEach(function(x) { x.sync?.(); });
var gameobject = {
check_dirty() {
this._ed.urdiff = this.json_obj();
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;
},
/* 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 = {
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)
{
if (!text && !data) {
console.info('empty ur');
return {
name: "empty"
};
}
var urstr = text + "+" + data;
if (!ur[urstr]) {
ur[urstr] = {
@ -747,6 +727,7 @@ game.loadurs = function() {
}
}
return;
for (var file of io.glob("**.json").filter(f => !ur[f.name()])) {
if (file[0] === '.' || file[0] === '_') continue;
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 = {};
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.circle = {};
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."); },
});
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({
start() {
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);
render.window(p,this.wh, this.color);
this.bb = bbox.blwh(p, this.wh);
gui.flush();
this.max_width = this.width;
if (this.selectable) gui.controls.check_bb(this);
var pos = [this.bb.l, this.bb.t].add(this.padding);
@ -181,7 +190,12 @@ Mum.window = Mum.extend({
if (item.hide) return;
item.draw(pos.slice(),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();
},
});

View file

@ -119,6 +119,7 @@ prosperon.textinput = function(c){
};
prosperon.mousemove = function(pos, dx){
mousepos = pos;
mousepos.y = window.size.y - mousepos.y;
player[0].mouse_input("move", pos, dx);
};
prosperon.mousescroll = function(dx){
@ -299,15 +300,20 @@ var Player = {
raw_input(cmd, state, ...args) {
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);
if (!pawn.inputs.fallthru)
return;
}
if (!pawn.inputs?.[cmd]) {
if (pawn.inputs?.block) return;
if (!pawn.inputs[cmd]) {
if (pawn.inputs.block) return;
continue;
}
@ -329,6 +335,7 @@ var Player = {
if (typeof fn === 'function') {
fn.call(pawn, ... args);
if (!pawn.inputs) continue; // TODO: OK? Checking if the call uncontrolled the pawn
pawn.inputs.post?.call(pawn);
}
@ -369,6 +376,10 @@ var Player = {
pawns: [],
control(pawn) {
if (!pawn.inputs) {
console.warn(`attempted to control a pawn without any input object.`);
return;
}
this.pawns.push_unique(pawn);
},

View file

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

View file

@ -4,6 +4,324 @@ render.doc = {
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 = {
pc: [1920,1080],
macbook_m2: [2560,1664, 13.6],
@ -40,16 +358,91 @@ render.device = {
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 */
render.point = function(pos,size,color = Color.blue) {
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) {
var a = [
pos.add([0,size]),
@ -59,10 +452,9 @@ render.cross = function(pos, size, color = Color.red) {
pos.add([size,0]),
pos.add([-size,0])
];
render.line(a,color);
render.line(b,color);
};
};
render.arrow = function(start, end, color = Color.red, wingspan = 4, wingangle = 10) {
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;
};
render.image = function(tex, pos, rotation = 0, color = Color.white, dimensions = [tex.width, tex.height]) {
var scale = [dimensions.x/tex.width, dimensions.y/tex.height];
gui.img(tex,pos, scale, 0.0, false, [0.0,0.0], color);
return bbox.fromcwh([0,0], [tex.width,tex.height]);
render.image = function(tex, pos, scale = 1, rotation = 0, color = Color.white, dimensions = [tex.width, tex.height]) {
var t = os.make_transform();
t.pos = pos;
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 = {};
@ -139,12 +540,10 @@ render.set_font = function(path, size) {
}
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.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.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};

View file

@ -192,7 +192,7 @@ Cmdline.register_order("edit", function() {
}
window.size = [1280, 720];
window.mode = window.modetypes.full;
window.mode = "full";
sim.pause();
game.engine_start(function() {
@ -243,13 +243,9 @@ Cmdline.register_order("play", function(argv) {
var project = json.decode(io.slurp(projectfile));
game.title = project.title;
window.mode = window.modetypes.expand;
global.mixin("config.js");
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() {
render.set_font("fonts/c64.ttf", 8);
global.app = actor.spawn("game.js");
@ -455,8 +451,8 @@ function cmd_args(cmdargs)
if (cmds.length === 0)
cmds[0] = "play";
else if (!Cmdline.orders[cmds[0]]) {
cmds[0] = "play";
console.warn(`Command ${cmds[0]} not found. Playing instead.`);
cmds[0] = "play";
}
Cmdline.orders[cmds[0]](cmds.slice(1));
@ -476,9 +472,54 @@ Cmdline.register_cmd("l", function(n) {
console.level = n;
}, "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 {
Resources,
Cmdline,
cmd_args
cmd_args,
convertYAMLtoJSON
};

View file

@ -1,4 +1,3 @@
var inputpanel = {
title: "untitled",
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 "gameobject.h"
#include <string.h>
#include "debugdraw.h"
#include "stb_ds.h"
#include <assert.h>
#include <chipmunk/chipmunk_unsafe.h>
#include <math.h>
#include "2dphysics.h"
#include "jsffi.h"
#include "script.h"
#include "log.h"
cpSpace *space = NULL;
struct rgba color_white = {255,255,255,255};
struct rgba color_black = {0,0,0,255};
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;
static JSValue *fns = NULL;
static JSValue *hits = NULL;
void phys2d_init()
{
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) {
cpSpaceStep(space, deltaT);
cpSpaceEachConstraint(space, constraint_test, &deltaT);
cb_idx = 0;
arrsetlen(fns,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)
{
cpBody *body1;
@ -546,18 +30,21 @@ JSValue arb2js(cpArbiter *arb)
cpShape *shape2;
cpArbiterGetShapes(arb, &shape1, &shape2);
struct phys2d_shape *pshape = cpShapeGetUserData(shape2);
gameobject *go2 = cpBodyGetUserData(body2);
JSValue j = *(JSValue*)cpShapeGetUserData(shape2);
JSValue obj = JS_NewObject(js);
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)));
JSValue jg = body2go(body2)->ref;
HMM_Vec2 srfv;
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, "impulse", vec22js((HMM_Vec2)cpArbiterTotalImpulse(arb)));
JS_SetPropertyStr(js, obj, "ke", number2js(cpArbiterTotalKE(arb)));
return obj;
}
@ -575,31 +62,21 @@ void register_hit(cpArbiter *arb, gameobject *go, const char *name)
JSValue cb = JS_GetPropertyStr(js, go->ref, name);
if (!JS_IsUndefined(cb)) {
JSValue jarb = arb2js(arb);
fns[cb_idx] = JS_DupValue(js, cb);
hits[cb_idx] = jarb;
cpSpaceAddPostStepCallback(space, phys_run_post, &fns[cb_idx], &hits[cb_idx]);
cb_idx++;
arrput(fns, JS_DupValue(js,cb));
arrput(hits, jarb);
cpSpaceAddPostStepCallback(space, phys_run_post, fns+arrlen(fns)-1, hits+arrlen(hits)-1);
}
cpShape *s1, *s2;
cpArbiterGetShapes(arb, &s1, &s2);
gameobject *g1, *g2;
g1 = shape2go(s1);
g2 = shape2go(g2);
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);
JSValue j1 = *(JSValue*)cpShapeGetUserData(s1);
JSValue j2 = *(JSValue*)cpShapeGetUserData(s2);
cb = JS_GetPropertyStr(js, j1, name);
if (!JS_IsUndefined(cb)) {
JSValue jarb = arb2js(arb);
fns[cb_idx] = JS_DupValue(js,cb);
hits[cb_idx] = jarb;
cpSpaceAddPostStepCallback(space, phys_run_post, &fns[cb_idx], &hits[cb_idx]);
cb_idx++;
arrput(fns, JS_DupValue(js,cb));
arrput(hits, jarb);
cpSpaceAddPostStepCallback(space, phys_run_post, fns+arrlen(fns)-1, hits+arrlen(hits)-1);
}
}

View file

@ -1,130 +1,15 @@
#ifndef TWODPHYSICS_H
#define TWODPHYSICS_H
#include "script.h"
#include <chipmunk/chipmunk.h>
#include "gameobject.h"
#include "render.h"
#include "transform.h"
extern float phys2d_gravity;
extern int physOn;
#include "script.h"
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_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);
int query_point(HMM_Vec2 pos);
void phys2d_reindex_body(cpBody *body);
JSValue arb2js(cpArbiter *arb);
#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 "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);
arrput(s->data,val);
float elapsed = now - anim->time;
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)
{
float t2 = t*t;
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;
return (HMM_Vec4)HMM_SLerp(HMM_QV4(sampler->data[prev]), t, HMM_QV4(sampler->data[next]));
}
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];
int previous_time=0;
int next_time=0;
for (int i = 1; i < arrlen(sampler->times); i++) {
if (time < sampler->times[i]) {
previous_time = sampler->times[i-1];
next_time = sampler->times[i];
previous_time = i-1;
next_time = i;
break;
}
}

View file

@ -13,12 +13,6 @@ struct keyframe {
#define CUBICSPLINE 2
#define SLERP 3
typedef struct samplerf {
float *times;
float *data;
int type;
} samplerf;
typedef struct sampler {
float *times;
HMM_Vec4 *data;
@ -26,6 +20,8 @@ typedef struct sampler {
} sampler;
struct anim_channel {
HMM_Vec4 *target;
int comps;
sampler *sampler;
};
@ -33,10 +29,10 @@ struct animation {
char *name;
double time;
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);
#endif

View file

@ -1,4 +1,4 @@
#include "render.h"
#include "config.h"
#define SOKOL_TRACE_HOOKS
#define SOKOL_IMPL
@ -7,7 +7,6 @@
#include "sokol/sokol_args.h"
#include "sokol/sokol_gfx.h"
#include "sokol/sokol_app.h"
#include "sokol_gfx_ext.h"
#define MSF_GIF_IMPL
#include "msf_gif.h"
@ -25,11 +24,11 @@
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#define STBI_NO_STDIO
#ifdef __TINYC__
#define STBI_NO_SIMD
#endif
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_BOX
#include "stb_image_write.h"
@ -41,3 +40,15 @@
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg.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 MAXNAME 50
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 720
#define PI 3.14159265358979323846264338327950288f
#define DEG2RADS 0.0174532925199432957692369076848861271344287188854172545609719144f
#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

View file

@ -13,15 +13,16 @@
#include "font.h"
#include "render.h"
#include "mpeg2.sglsl.h"
#include "cbuf.h"
#include "sokol/sokol_gfx.h"
sg_shader vid_shader;
sg_pipeline vid_pipeline;
sg_bindings vid_bind;
void datastream_free(datastream *ds)
{
sg_destroy_image(ds->img);
plm_destroy(ds->plm);
free(ds);
}
void soundstream_fillbuf(struct datastream *ds, soundbyte *buf, int frames) {
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) {
return;
if (ds->dirty) return;
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);
sg_image_data imgd;
sg_range ir = {
.ptr = rgb,
.size = frame->height*frame->width*4*sizeof(uint8_t)
};
imgd.subimage[0][0] = ir;
sg_image_data imgd = {0};
imgd.subimage[0][0] = SG_RANGE(rgb);
sg_update_image(ds->img, &imgd);
ds->dirty = true;
}
static void render_audio(plm_t *mpeg, plm_samples_t *samples, struct datastream *ds) {
@ -67,16 +65,21 @@ struct datastream *ds_openvideo(const char *path)
plm_get_num_audio_streams(ds->plm),
plm_get_duration(ds->plm));
ds->img = sg_make_image(&(sg_image_desc){
.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);
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_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
plm_set_audio_lead_time(ds->plm, BUF_FRAMES / SAMPLERATE);
ds->playing = true;
return ds;
}
void MakeDatastream() {
vid_shader = sg_make_shader(mpeg2_shader_desc(sg_query_backend()));
}
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) {
@ -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) {
return plm_get_duration(ds->plm);
}

View file

@ -11,17 +11,22 @@ struct soundstream;
struct datastream {
plm_t *plm;
double last_time;
int playing;
sg_image img;
sg_image y;
sg_image cr;
sg_image cb;
int width;
int height;
int dirty;
soundbyte *ring;
};
typedef struct datastream datastream;
struct texture;
void MakeDatastream();
void datastream_free(datastream *ds);
struct datastream *ds_openvideo(const char *path);
struct texture *ds_maketexture(struct datastream *);
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 <window.h>
#include "resources.h"
#include "debugdraw.h"
#include "text.sglsl.h"
#include "render.h"
#include "stb_image_write.h"
@ -22,64 +20,25 @@
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 draw_p pos;
struct draw_p wh;
struct uv_n uv;
struct uv_n st;
struct rgba color;
HMM_Vec2 pos;
HMM_Vec2 wh;
HMM_Vec2 uv;
HMM_Vec2 st;
HMM_Vec4 color;
};
static struct text_vert *text_buffer;
void font_init() {
text_buffer = malloc(sizeof(*text_buffer)*max_chars);
fontshader = sg_make_shader(text_shader_desc(sg_query_backend()));
pipe_text = sg_make_pipeline(&(sg_pipeline_desc){
.shader = fontshader,
.layout = {
.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,
text_ssbo = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct text_vert),
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_STREAM,
.label = "text buffer"
});
bind_text.fs.samplers[0] = sg_make_sampler(&(sg_sampler_desc){});
}
void font_free(font *f)
@ -91,7 +50,6 @@ void font_free(font *f)
void font_set(font *f)
{
use_font = f;
bind_text.fs.images[0] = f->texID;
}
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->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,
.width = 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++) {
stbtt_packedchar glyph = glyphs[c - 32];
@ -192,8 +154,6 @@ struct sFont *MakeFont(const char *fontfile, int height) {
return newfont;
}
static int curchar = 0;
void draw_underline_cursor(HMM_Vec2 pos, float scale, struct rgba color)
{
pos.Y -= 2;
@ -213,29 +173,31 @@ void draw_char_box(struct Character c, HMM_Vec2 cursor, float scale, struct rgba
HMM_Vec2 b;
b.x = cursor.X + wh.x/2;
b.y = cursor.Y + wh.y/2;
draw_box(b, wh, color);
}
void text_flush(HMM_Mat4 *proj) {
if (curchar == 0) return;
int text_flush() {
if (arrlen(text_buffer) == 0) return 0;
sg_range verts;
verts.ptr = text_buffer;
verts.size = sizeof(struct text_vert) * curchar;
int offset = sg_append_buffer(bind_text.vertex_buffers[0], &verts);
verts.size = sizeof(struct text_vert) * arrlen(text_buffer);
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_apply_pipeline(pipe_text);
sg_apply_bindings(&bind_text);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(*proj));
sg_draw(0, 4, curchar);
curchar = 0;
sg_append_buffer(text_ssbo, &verts);
int n = arrlen(text_buffer);
arrsetlen(text_buffer, 0);
return n;
}
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 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;
vert.uv.u = c.rect.x*USHRT_MAX;
vert.uv.v = c.rect.y*USHRT_MAX;
vert.st.u = c.rect.w*USHRT_MAX;
vert.st.v = c.rect.h*USHRT_MAX;
vert.color = color;
vert.uv.x = c.rect.x;
vert.uv.y = c.rect.y;
vert.st.x = c.rect.w;
vert.st.y = c.rect.h;
rgba2floats(vert.color.e, color);
memcpy(text_buffer + curchar, &vert, sizeof(struct text_vert));
curchar++;
arrput(text_buffer, vert);
}
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')
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);
cursor.X += use_font->Characters[*wordstart].Advance * tracking * scale;

View file

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

View file

@ -2,105 +2,37 @@
#include "2dphysics.h"
#include <string.h>
#include "debugdraw.h"
#include "log.h"
#include "math.h"
#include "stb_ds.h"
gameobject *body2go(cpBody *body) { return cpBodyGetUserData(body); }
gameobject *shape2go(cpShape *shape) {
struct phys2d_shape *pshape = cpShapeGetUserData(shape);
if (!pshape) return NULL;
return pshape->go;
}
HMM_Vec2 go_pos(gameobject *go)
transform go2t(gameobject *go)
{
cpVect p = cpBodyGetPosition(go->body);
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;
transform t = {0};
t.pos.cp = cpBodyGetPosition(go->body);
t.angle = cpBodyGetAngle(go->body);
t.scale = go->scale.XY;
if (!isfinite(t.scale.X)) t.scale.X = 1;
if (!isfinite(t.scale.Y)) t.scale.Y = 1;
t.rotation = angle2rotation(cpBodyGetAngle(go->body));
t.scale = go->t->scale;
return t;
}
void go_shape_apply(cpBody *body, cpShape *shape, gameobject *go) {
cpShapeSetFriction(shape, go->friction);
cpShapeSetElasticity(shape, go->elasticity);
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);
gameobject *body2go(cpBody *b)
{
return cpBodyGetUserData(b);
}
void go_shape_moi(cpBody *body, cpShape *shape, gameobject *go) {
float moment = cpBodyGetMoment(body);
struct phys2d_shape *s = cpShapeGetUserData(shape);
if (!s) {
cpBodySetMoment(body, moment + 1);
return;
}
moment += s->moi(s->data);
if (moment < 0) moment = 0;
cpBodySetMoment(body, moment);
gameobject *shape2go(cpShape *s)
{
cpBody *b = cpShapeGetBody(s);
return cpBodyGetUserData(b);
}
void gameobject_apply(gameobject *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);
}
}
void gameobject_apply(gameobject *go) { *go->t = go2t(go); }
static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt)
{
gameobject *go = body2go(body);
gameobject_apply(go);
cpVect pos = cpBodyGetPosition(body);
HMM_Vec2 g = warp_force((HMM_Vec3){pos.x, pos.y, 0}, go->warp_mask).xy;
if (!go) {
@ -126,23 +58,15 @@ static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt
gameobject *MakeGameobject() {
gameobject *ngo = malloc(sizeof(*ngo));
gameobject go = {
.scale = (HMM_Vec3){1.f,1.f,1.f},
.phys = CP_BODY_TYPE_STATIC,
.maxvelocity = INFINITY,
.maxangularvelocity = INFINITY,
.mass = 1.f,
.next = -1,
.drawlayer = 0,
.damping = INFINITY,
.timescale = 1.0,
.ref = JS_UNDEFINED,
.mask = ~0,
.categories = 1,
.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);
*ngo = go;
@ -152,26 +76,12 @@ gameobject *MakeGameobject() {
}
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);
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) {
@ -182,31 +92,3 @@ void gameobject_free(gameobject *go) {
cpBodyFree(go->body);
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)
struct gameobject {
cpBodyType phys;
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 timescale;
float maxvelocity;
float maxangularvelocity;
unsigned int layer;
cpBitmask categories;
cpBitmask mask;
unsigned int warp_mask;
JSValue ref;
HMM_Mat4 world;
float drawlayer;
transform *t; // the transform this body controls
};
/*
@ -68,31 +58,13 @@ typedef struct gameobject gameobject;
gameobject *MakeGameobject();
void gameobject_apply(gameobject *go);
void gameobject_free(gameobject *go);
transform go2t(gameobject *go);
transform2d go2t(gameobject *go);
transform3d go2t3(gameobject *go);
HMM_Vec3 go_pos(gameobject *go);
HMM_Vec2 go2world(gameobject *go, HMM_Vec2 pos);
HMM_Vec2 world2go(gameobject *go, HMM_Vec2 pos);
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);
gameobject *shape2go(cpShape *s);
gameobject *body2go(cpBody *b);
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

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};
strncat(dest,"[", 512);
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,"]", 512);

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,22 @@
#ifndef FFI_H
#define FFI_H
#ifdef __cplusplus
extern "C" {
#endif
#include "quickjs/quickjs.h"
#include "HandmadeMath.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_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, \
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_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; \
{FN;} \
return ret; \
} \
#define GETSETPAIR(ID, ENTRY, TYPE, FN) \
JSValue js_##ID##_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \
js2##ID (this)->ENTRY = js2##TYPE (val); \
JSValue js_##ID##_set_##ENTRY (JS_SETSIG) { \
js2##ID (self)->ENTRY = js2##TYPE (val); \
{FN;} \
return JS_UNDEFINED; \
} \
\
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue this) { \
return TYPE##2js(js2##ID (this)->ENTRY); \
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \
return TYPE##2js(js2##ID (self)->ENTRY); \
} \
#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_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); \
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); \
} \
#define JSC_GETSET_BODY(ENTRY, CPENTRY, TYPE) \
JSValue js_gameobject_set_##ENTRY (JSContext *js, JSValue this, JSValue val) { \
cpBody *b = js2gameobject(this)->body; \
JSValue js_gameobject_set_##ENTRY (JS_SETSIG) { \
cpBody *b = js2gameobject(self)->body; \
cpBodySet##CPENTRY (b, js2##TYPE (val)); \
return JS_UNDEFINED; \
} \
\
JSValue js_gameobject_get_##ENTRY (JSContext *js, JSValue this) { \
cpBody *b = js2gameobject(this)->body; \
JSValue js_gameobject_get_##ENTRY (JSContext *js, JSValue self) { \
cpBody *b = js2gameobject(self)->body; \
return TYPE##2js (cpBodyGet##CPENTRY (b)); \
} \
#define JSC_GET(ID, ENTRY, TYPE) \
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue this) { \
return TYPE##2js(js2##ID (this)->ENTRY); } \
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \
return TYPE##2js(js2##ID (self)->ENTRY); } \
#define QJSCLASS(TYPE)\
static JSClassID js_##TYPE##_id;\
@ -91,14 +98,17 @@ static JSClassDef js_##TYPE##_class = {\
#TYPE,\
.finalizer = js_##TYPE##_finalizer,\
};\
static TYPE *js2##TYPE (JSValue val) { return JS_GetOpaque(val,js_##TYPE##_id); }\
static JSValue TYPE##2js(TYPE *n) { \
TYPE *js2##TYPE (JSValue val) { \
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);\
YughSpam("Created " #TYPE " at %p", n); \
JS_SetOpaque(j,n);\
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) \
JSValue NAME = JS_NewObject(js); \
@ -124,6 +134,9 @@ void ffi_load();
JSValue vec22js(HMM_Vec2 v);
HMM_Vec2 js2vec2(JSValue v);
const char *js2str(JSValue v);
char *js2strdup(JSValue v);
JSValue bitmask2js(cpBitmask mask);
cpBitmask js2bitmask(JSValue v);
int js_print_exception(JSValue v);
@ -131,8 +144,29 @@ int js_print_exception(JSValue v);
struct rgba js2color(JSValue v);
double js2number(JSValue v);
JSValue number2js(double g);
uint64_t js2uint64(JSValue v);
JSValue str2js(const char *c, ...);
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

View file

@ -15,6 +15,10 @@
#include "script.h"
#ifdef __WIN32
#include "debugapi.h"
#endif
#define ESC "\033["
#define BLACK 30
#define RED 31
@ -25,7 +29,7 @@
#define CYAN 36
#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 *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")) {
logout = tmpfile();
dump = tmpfile();
writeout = stdout;
}
else {
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)
{
#ifndef NDEBUG
fprintf(stdout, str);
fprintf(writeout, str);
fprintf(stdout, "%s", str);
fprintf(writeout, "%s", str);
fflush(stdout);
#else
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 "stb_ds.h"
#include "render.h"
#include "particle.sglsl.h"
#include "2dphysics.h"
#include "log.h"
#include "simplex.h"
#include "pthread.h"
#include "math.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){});
}
#include "log.h"
emitter *make_emitter() {
emitter *e = calloc(sizeof(*e),1);
e->max = 20;
arrsetcap(e->particles, e->max);
arrsetcap(e->particles, 10);
for (int i = 0; i < arrlen(e->particles); i++)
e->particles[i].life = 0;
e->life = 10;
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->speed = 20;
e->texture = NULL;
arrpush(emitters,e);
e->buffer = sg_make_buffer(&(sg_buffer_desc){
.size = sizeof(struct par_vert),
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_STREAM
});
return e;
}
void emitter_free(emitter *e)
{
YughWarn("kill emitter");
arrfree(e->particles);
for (int i = arrlen(emitters)-1; i >= 0; i--)
if (emitters[i] == e) {
arrdelswap(emitters,i);
break;
}
arrfree(e->verts);
free(e);
}
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. */
float variate(float val, float variance)
@ -126,84 +39,69 @@ float variate(float val, float variance)
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.pos = (HMM_Vec4){e->t.pos.x,e->t.pos.y,0,0};
float newan = e->t.rotation.Elements[0]+(2*HMM_PI*(frand(e->divergence)-(e->divergence/2)));
HMM_Vec2 norm = HMM_V2Rotate((HMM_Vec2){0,1}, newan);
p.v = HMM_MulV4F((HMM_Vec4){norm.x,norm.y,0,0}, variate(e->speed, e->variation));
p.angle = 0;
p.scale = variate(e->scale, e->scale_var);
// p.av = 1;
p.pos = (HMM_Vec4){t->pos.x,t->pos.y,t->pos.z,0};
HMM_Vec3 up = transform_direction(t, vFWD);
float newan = (frand(e->divergence)-(e->divergence/2))*HMM_TurnToRad;
HMM_Vec2 v2n = HMM_V2Rotate((HMM_Vec2){0,1}, newan);
HMM_Vec3 norm = (HMM_Vec3){v2n.x, v2n.y,0};
p.v = HMM_MulV4F((HMM_Vec4){norm.x,norm.y,norm.z,0}, variate(e->speed, e->variation));
p.angle = 0.25;
p.scale = variate(e->scale*t->scale.x, e->scale_var);
arrput(e->particles,p);
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++)
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++)
emitter_step(emitters[i], dt);
}
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 (arrlen(e->particles) == 0) return;
arrsetlen(e->verts, arrlen(e->particles));
for (int i = 0; i < arrlen(e->particles); i++) {
if (e->particles[i].time >= e->particles[i].life) continue;
particle *p = &e->particles[i];
pv[i].pos = p->pos.xy;
pv[i].angle = p->angle;
float s = p->scale;
if (p->time < e->grow_for)
s = lerp(p->time/e->grow_for, 0, p->scale);
particle *p = e->particles+i;
e->verts[i].pos = p->pos.xy;
e->verts[i].angle = p->angle;
e->verts[i].scale = p->scale;
/* if (p->time < e->grow_for)
e->verts[i].scale = lerp(p->time/e->grow_for, 0, p->scale);
else if (p->time > (p->life - e->shrink_for))
s = 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);
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);
e->verts[i].scale = lerp((p->time-(p->life-e->shrink_for))/e->shrink_for, p->scale, 0);*/
e->verts[i].color = p->color;
}
sg_apply_pipeline(par_pipe);
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(*proj));
sg_apply_bindings(&par_bind);
sg_draw(0, 4, draw_count);
sg_range verts;
verts.ptr = e->verts;
verts.size = sizeof(*e->verts)*arrlen(e->verts);
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;
static HMM_Vec4 g_accel;
void emitter_step(emitter *e, double dt, transform *t) {
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 = t.end-1; i >=0; i--) {
for (int i = 0; i < arrlen(e->particles); i++) {
if (e->particles[i].time >= e->particles[i].life) continue;
if (e->warp_mask & gravmask)
e->particles[i].v = HMM_AddV4(e->particles[i].v, g_accel);
//if (e->warp_mask & gravmask)
// 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].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)
arrdelswap(e->particles, i);
else if (query_point(e->particles[i].pos.xy))
arrdelswap(e->particles,i);
// else if (query_point(e->particles[i].pos.xy))
// 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;
if (e->tte <= 0) {
emitter_spawn(e);
e->tte = lerp(e->explosiveness, e->life/e->max,0);
float step = lerp(e->explosiveness, e->life/e->max,0);
while (e->tte <= 0) {
e->tte += step;
if (!emitter_spawn(e, t)) break;
}
}

View file

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

View file

@ -2,11 +2,9 @@
#include "config.h"
#include "datastream.h"
#include "debugdraw.h"
#include "font.h"
#include "gameobject.h"
#include "log.h"
#include "sprite.h"
#include "particle.h"
#include "window.h"
#include "model.h"
@ -18,118 +16,33 @@
#include "sokol/sokol_glue.h"
#include "stb_image_write.h"
#include "box.sglsl.h"
#include "shadow.sglsl.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};
float camzoom = 1;
static struct {
sg_swapchain swap;
sg_pipeline pipe;
sg_bindings bind;
sg_shader shader;
sg_image img;
sg_image depth;
} sg_gif;
viewstate globalview = {0};
static struct {
sg_pipeline pipe;
sg_bindings bind;
sg_shader shader;
} sg_crt;
sg_sampler std_sampler;
sg_sampler nofilter_sampler;
sg_sampler tex_sampler;
static struct {
int w;
int h;
int cpf;
int depth;
double timer;
double spf;
int rec;
uint8_t *buffer;
} gif;
int TOPLEFT = 0;
MsfGifState gif_state = {};
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);
}
sg_pass offscreen;
#include "sokol/sokol_app.h"
#include "HandmadeMath.h"
sg_pass_action pass_action = {0};
static struct {
sg_pass_action pass_action;
sg_pass pass;
sg_pipeline pipe;
sg_shader shader;
} sg_shadow;
sg_pass_action off_action = {0};
sg_image screencolor = {0};
sg_image screendepth = {0};
void trace_apply_pipeline(sg_pipeline pip, void *data)
{
@ -217,159 +130,103 @@ static sg_trace_hooks hooks = {
};
void render_init() {
mainwin.size = (HMM_Vec2){sapp_width(), sapp_height()};
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger = { .func = sg_logging },
.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
sg_trace_hooks hh = sg_install_trace_hooks(&hooks);
#endif
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};
pass_action = (sg_pass_action){
.colors[0] = {.load_action = SG_LOADACTION_CLEAR, .clear_value = c},
};
sg_gif.shader = sg_make_shader(box_shader_desc(sg_query_backend()));
sg_gif.pipe = sg_make_pipeline(&(sg_pipeline_desc){
.shader = sg_gif.shader,
.layout = {
.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2
sg_color oc = (sg_color){35.0/255,60.0/255,92.0/255,1};
off_action = (sg_pass_action){
.colors[0] = {.load_action = SG_LOADACTION_CLEAR, .clear_value = oc},
.depth = {
.load_action = SG_LOADACTION_CLEAR,
.clear_value = 1
//.store_action = SG_STOREACTION_STORE
}
},
.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[] = {
-1, 1, 0, 1,
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, -1, 1, 0,
1, 1, 1, 1
screencolor = sg_make_image(&(sg_image_desc){
.render_target = true,
.width = 500,
.height = 500,
.pixel_format = sapp_color_format(),
.sample_count = 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 hudproj = {0.f};
HMM_Mat4 useproj = {0};
#define MODE_STRETCH 0
#define MODE_KEEP 1
#define MODE_WIDTH 2
#define MODE_HEIGHT 3
#define MODE_EXPAND 4
#define MODE_FULL 5
void openglRender(struct window *window, gameobject *cam, float zoom) {
sg_swapchain sch = sglue_swapchain();
sg_begin_pass(&(sg_pass){
.action = pass_action,
.swapchain = sglue_swapchain(),
.label = "window pass"
void openglRender(HMM_Vec2 usesize) {
if (usesize.x != lastuse.x || usesize.y != lastuse.y) {
sg_destroy_image(screencolor);
sg_destroy_image(screendepth);
sg_destroy_attachments(offscreen.attachments);
screencolor = sg_make_image(&(sg_image_desc){
.render_target = true,
.width = usesize.x,
.height = usesize.y,
.pixel_format = sapp_color_format(),
.sample_count = 1,
});
HMM_Vec2 usesize = window->rendersize;
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
screendepth = sg_make_image(&(sg_image_desc){
.render_target = true,
.width =usesize.x,
.height=usesize.y,
.pixel_format = sapp_depth_format(),
.sample_count = 1
});
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);
offscreen = (sg_pass){
.attachments = sg_make_attachments(&(sg_attachments_desc){
.colors[0].image = screencolor,
.depth_stencil.image = screendepth
}),
.action = off_action,
};
}
*/
lastuse = usesize;
sg_begin_pass(&offscreen);
}
struct boundingbox cwh2bb(HMM_Vec2 c, HMM_Vec2 wh) {

View file

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

View file

@ -126,6 +126,17 @@ char *dirname(const char *path)
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, ...)
{
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) {
struct stat 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;
}
@ -250,9 +274,9 @@ void *slurp_file(const char *filename, size_t *size)
void *ret;
if (!access(filename, R_OK))
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;
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 NULL;
@ -323,7 +347,7 @@ int mkpath(char *path, mode_t mode)
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");
if (!f) return 1;

View file

@ -11,7 +11,8 @@ extern int LOADED_GAME;
void resources_init();
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);
FILE *res_open(char *path, const char *tag);
char **ls(const char *path);
@ -22,11 +23,9 @@ void pack_start(const char *name);
void pack_add(const char *path);
void pack_end();
char *dirname(const char *path);
void *slurp_file(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, ...);

View file

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

View file

@ -21,18 +21,18 @@ typedef union NoiseUnion {
noiseNDptr pn;
} genericNoise;
extern double Noise2D(double x, double y);
extern double Noise3D(double x, double y, double z);
extern double Noise4D(double x, double y, double z, double w);
double Noise2D(double x, double y);
double Noise3D(double x, double y, double z);
double Noise4D(double x, double y, double z, double w);
extern double GBlur1D(double stdDev, double x);
extern double GBlur2D(double stdDev, double x, double y);
double GBlur1D(double stdDev, double x);
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[]);
extern double FractalSumNoise(genericNoise func, int iterations, int len, double args[]);
extern double FractalSumAbsNoise(genericNoise func, int iterations, int len, double args[]);
double TurbulentNoise(genericNoise func, int direction, int iterations, int len, double args[]);
double FractalSumNoise(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);

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)
{
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)
{
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)))
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);
/* atk, dec, sus, rls specify the time, in miliseconds, the phase begins */
struct dsp_adsr {
typedef struct dsp_adsr {
unsigned int atk;
double atk_t;
unsigned int dec;
@ -63,7 +63,7 @@ struct dsp_adsr {
double time; /* Current time of the filter */
float out;
};
} adsr;
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_pitchshift(float octaves);
struct dsp_compressor {
typedef struct dsp_compressor {
double ratio;
double threshold;
float target;
@ -85,7 +85,7 @@ struct dsp_compressor {
double atk_tau;
unsigned int rls; /* MIlliseconds */
double rls_tau;
};
} compressor;
dsp_node *dsp_compressor();
@ -106,6 +106,19 @@ float tri_phasor(float p);
dsp_node *dsp_reverb();
dsp_node *dsp_sinewave(float amp, float freq);
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);
void dsp_mono(void *p, soundbyte *out, int n);
void pan_frames(soundbyte *out, float deg, int frames);

View file

@ -5,12 +5,12 @@
#include "tml.h"
#include "dsp.h"
struct dsp_midi_song {
typedef struct dsp_midi_song {
float bpm;
double time;
tml_message *midi;
tsf *sf;
};
} midi;
dsp_node *dsp_midi(const char *midi, tsf *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"
/*
#ifndef NSTEAM
#include <steam/steam_api.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)
{
SteamAPI_Init();
return JS_UNDEFINED;
SteamErrMsg err;
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[] = {
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 steam = JS_NewObject(js);
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;
}
*/
#endif

View file

@ -5,15 +5,14 @@
#include "sokol/sokol_gfx.h"
#include <math.h>
#include <stb_image.h>
#include <stb_image_write.h>
#include "resources.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
#include <stdio.h>
#define QOI_IMPLEMENTATION
#include "qoi.h"
#ifndef NSVG
@ -122,14 +121,10 @@ struct texture *texture_from_file(const char *path) {
unsigned int nw = next_pow2(tex->width);
unsigned int nh = next_pow2(tex->height);
int filter = SG_FILTER_NEAREST;
sg_image_data sg_img_data;
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;
mipw = tex->width;
@ -151,18 +146,18 @@ struct texture *texture_from_file(const char *path) {
mipw = w;
miph = h;
}
*/
tex->id = sg_make_image(&(sg_image_desc){
.type = SG_IMAGETYPE_2D,
.width = tex->width,
.height = tex->height,
.usage = SG_USAGE_IMMUTABLE,
//.num_mipmaps = mips,
.num_mipmaps = mips,
.data = sg_img_data
});
/*for (int i = 1; i < mips; i++)
free(mipdata[i]);*/
for (int i = 1; i < mips; i++)
free(mipdata[i]);
return tex;
}
@ -170,12 +165,33 @@ struct texture *texture_from_file(const char *path) {
void texture_free(texture *tex)
{
if (!tex) return;
if (tex->data)
free(tex->data);
if (tex->delays) arrfree(tex->delays);
sg_destroy_image(tex->id);
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 *tex = calloc(1, sizeof(*tex));
@ -191,8 +207,6 @@ struct texture *texture_fromdata(void *raw, long size)
tex->data = data;
int filter = SG_FILTER_NEAREST;
sg_image_data sg_img_data;
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,
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,

View file

@ -18,7 +18,8 @@ extern struct rect ST_UNIT;
/* Represents an actual texture on the GPU */
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 height;
unsigned char *data;
@ -39,8 +40,11 @@ typedef struct img_sampler{
texture *texture_from_file(const char *path);
void texture_free(texture *tex);
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);

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
project):
#define SOKOL_GLCORE33
#define SOKOL_GLCORE
#define SOKOL_GLES3
#define SOKOL_D3D11
#define SOKOL_METAL
#define SOKOL_WGPU
#define SOKOL_NOAPI
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)
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.
For example code, see https://github.com/floooh/sokol-samples/tree/master/sapp
@ -87,7 +88,7 @@
- makes the rendered frame visible
- provides keyboard-, mouse- and low-level touch-events
- 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
=======================
@ -97,6 +98,7 @@
gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES
metal | --- | YES | --- | YES | --- | ---
d3d11 | YES | --- | --- | --- | --- | ---
noapi | YES | TODO | TODO | --- | TODO | ---
KEY_DOWN | YES | YES | YES | SOME | TODO | YES
KEY_UP | YES | YES | YES | SOME | TODO | YES
CHAR | YES | YES | YES | YES | TODO | YES
@ -313,10 +315,15 @@
objects and values required for rendering. If sokol_app.h
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.
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);
On Android, get the native activity ANativeActivity pointer, otherwise
a null pointer.
@ -348,7 +355,7 @@
sapp_consume_event() from inside the event handler (NOTE that
this behaviour is currently only implemented for some HTML5
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).
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 */
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 */
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
/* MacOS */
#define _SAPP_MACOS (1)
#if !defined(SOKOL_METAL) && !defined(SOKOL_GLCORE33)
#error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or 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_GLCORE")
#endif
#else
/* iOS or iOS Simulator */
@ -1978,8 +1989,8 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
#elif defined(_WIN32)
/* Windows (D3D11 or GL) */
#define _SAPP_WIN32 (1)
#if !defined(SOKOL_D3D11) && !defined(SOKOL_GLCORE33)
#error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or 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, SOKOL_GLCORE or SOKOL_NOAPI")
#endif
#elif defined(__ANDROID__)
/* Android */
@ -1993,7 +2004,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
#elif defined(__linux__) || defined(__unix__)
/* Linux */
#define _SAPP_LINUX (1)
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
#if !defined(SOKOL_FORCE_EGL)
#define _SAPP_GLX (1)
#endif
@ -2003,13 +2014,13 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); }
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#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
#else
#error "sokol_app.h: Unknown platform"
#endif
#if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES3)
#if defined(SOKOL_GLCORE) || defined(SOKOL_GLES3)
#define _SAPP_ANY_GL (1)
#endif
@ -2399,11 +2410,11 @@ _SOKOL_PRIVATE double _sapp_timing_get_avg(_sapp_timing_t* t) {
#if defined(SOKOL_METAL)
@interface _sapp_macos_view : MTKView
@end
#elif defined(SOKOL_GLCORE33)
#elif defined(SOKOL_GLCORE)
@interface _sapp_macos_view : NSOpenGLView
- (void)timerFired:(id)sender;
@end
#endif // SOKOL_GLCORE33
#endif // SOKOL_GLCORE
typedef struct {
uint32_t flags_changed_store;
@ -2545,7 +2556,7 @@ typedef struct {
uint8_t raw_input_data[256];
} _sapp_win32_t;
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
@ -2605,7 +2616,7 @@ typedef struct {
HWND msg_hwnd;
HDC msg_dc;
} _sapp_wgl_t;
#endif // SOKOL_GLCORE33
#endif // SOKOL_GLCORE
#endif // _SAPP_WIN32
@ -2876,7 +2887,7 @@ typedef struct {
_sapp_win32_t win32;
#if defined(SOKOL_D3D11)
_sapp_d3d11_t d3d11;
#elif defined(SOKOL_GLCORE33)
#elif defined(SOKOL_GLCORE)
_sapp_wgl_t wgl;
#endif
#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
// and can't be used to indicate 'default')
if (0 == res.gl_major_version) {
res.gl_major_version = 3;
res.gl_minor_version = 2;
#if defined(_SAPP_APPLE)
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.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 bool dim_changed = (_sapp.framebuffer_width != cur_fb_width) ||
(_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_height = (int)roundf(bounds.size.height * _sapp.dpi_scale);
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 makeFirstResponder:_sapp.macos.view];
_sapp.macos.view.layer.magnificationFilter = kCAFilterNearest;
#elif defined(SOKOL_GLCORE33)
#elif defined(SOKOL_GLCORE)
NSOpenGLPixelFormatAttribute attrs[32];
int i = 0;
attrs[i++] = NSOpenGLPFAAccelerated;
@ -4124,7 +4140,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) {
@end
@implementation _sapp_macos_view
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
- (void)timerFired:(id)sender {
_SOKOL_UNUSED(sender);
[self setNeedsDisplay:YES];
@ -4224,7 +4240,7 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events(void) {
// helper function to make GL context active
static void _sapp_gl_make_current(void) {
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
[[_sapp.macos.view openGLContext] makeCurrentContext];
#endif
}
@ -5749,16 +5765,28 @@ _SOKOL_PRIVATE void _sapp_emsc_wgpu_request_adapter_cb(WGPURequestAdapterStatus
SOKOL_ASSERT(adapter);
_sapp.wgpu.adapter = adapter;
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,
};
// check for optional features we're interested in
// FIXME: ASTC texture compression
if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionBC)) {
SOKOL_ASSERT(cur_feature_index < _SAPP_WGPU_MAX_REQUESTED_FEATURES);
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;
}
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;
_sapp_clear(&dev_desc, sizeof(dev_desc));
@ -5945,7 +5973,7 @@ int main(int argc, char* argv[]) {
// ██████ ███████ ██ ██ ███████ ███████ ██ ███████ ██ ██ ███████
//
// >>gl helpers
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
typedef struct {
int red_bits;
int green_bits;
@ -6596,7 +6624,7 @@ _SOKOL_PRIVATE void _sapp_d3d11_present(bool do_not_wait) {
#endif /* SOKOL_D3D11 */
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
_SOKOL_PRIVATE void _sapp_wgl_init(void) {
_sapp.wgl.opengl32 = LoadLibraryA("opengl32.dll");
if (!_sapp.wgl.opengl32) {
@ -6894,7 +6922,7 @@ _SOKOL_PRIVATE void _sapp_wgl_swap_buffers(void) {
/* FIXME: DwmIsCompositionEnabled? (see GLFW) */
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_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
_sapp_timing_measure(&_sapp.timing);
#endif
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
_sapp_timing_measure(&_sapp.timing);
#endif
#if defined(SOKOL_NOAPI)
_sapp_timing_measure(&_sapp.timing);
#endif
}
@ -7513,7 +7544,7 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM
// present with DXGI_PRESENT_DO_NOT_WAIT
_sapp_d3d11_present(true);
#endif
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
_sapp_wgl_swap_buffers();
#endif
/* 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_default_render_target();
#endif
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
_sapp_wgl_init();
_sapp_wgl_load_extensions();
_sapp_wgl_create_context();
@ -7954,7 +7985,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) {
Sleep((DWORD)(16 * _sapp.swap_interval));
}
#endif
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
_sapp_wgl_swap_buffers();
#endif
/* 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)
_SOKOL_PRIVATE void _sapp_egl_init(void) {
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
if (!eglBindAPI(EGL_OPENGL_API)) {
_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;
const EGLint config_attrs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
#elif defined(SOKOL_GLES3)
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
@ -11034,7 +11065,7 @@ _SOKOL_PRIVATE void _sapp_egl_init(void) {
}
EGLint ctx_attrs[] = {
#if defined(SOKOL_GLCORE33)
#if defined(SOKOL_GLCORE)
EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version,
EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version,
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
}
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) {
// 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)

View file

@ -39,6 +39,7 @@
- on macOS: AudioToolbox
- on iOS: AudioToolbox, AVFoundation
- on FreeBSD: asound
- on Linux: asound
- 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
@ -51,6 +52,7 @@
- Windows: WASAPI
- Linux: ALSA
- FreeBSD: ALSA
- macOS: CoreAudio
- iOS: CoreAudio+AVAudioSession
- emscripten: WebAudio with ScriptProcessorNode
@ -780,7 +782,9 @@ inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc);
#include "aaudio/AAudio.h"
#endif
#elif defined(_SAUDIO_LINUX)
#if !defined(__FreeBSD__)
#include <alloca.h>
#endif
#define _SAUDIO_PTHREADS (1)
#include <pthread.h>
#define ALSA_PCM_NEW_HW_PARAMS_API

View file

@ -1071,14 +1071,11 @@ typedef struct sfetch_response_t {
sfetch_range_t buffer; // the user-provided buffer which holds the fetched data
} sfetch_response_t;
/* response callback function signature */
typedef void(*sfetch_callback_t)(const sfetch_response_t*);
/* request parameters passed to sfetch_send() */
typedef struct sfetch_request_t {
uint32_t channel; // index of channel this request is assigned to (default: 0)
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)
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)
@ -1302,7 +1299,7 @@ typedef struct {
uint32_t channel;
uint32_t lane;
uint32_t chunk_size;
sfetch_callback_t callback;
void (*callback) (const sfetch_response_t*);
sfetch_range_t buffer;
/* 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:
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