Compare commits
1 Commits
main
...
wip-work-t
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bff0f9501 |
4
LICENSE
4
LICENSE
@ -1,7 +1,7 @@
|
|||||||
Copyright 2026 Tuomas Katajisto
|
Copyright 2025 Tuomas Katajisto
|
||||||
|
|
||||||
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:
|
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 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.
|
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.
|
||||||
26
TODO.txt
26
TODO.txt
@ -1,8 +1,20 @@
|
|||||||
Engine todo list:
|
DONE:
|
||||||
- Engine testing + game testing framework is a mess.
|
- Implement idle orbit thing using the rotation in the level editor camera. (Cool transition effect?)
|
||||||
- A lot of the graphics pipeline stuff is really bloated and repeats itself.
|
- Level editor camera movement complete.
|
||||||
- Sokol should be fully under a platform abstraction layer but it's not,
|
|
||||||
currently it's fine but it would be really nice to be able to have a metal, vulkan
|
|
||||||
webgpu backends at some point for the longevity of the engine.
|
|
||||||
- Backend sokol has a global named camera taking up the name which is fucking crazy.
|
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- Level format (just a hash map?). Store a matrix so that we can rotate and maybe scale blocks. Scaling will cause problems with RDM, so figure that out.
|
||||||
|
- Trile color textures.
|
||||||
|
- Sun lighting and shadowmap for levels.
|
||||||
|
- A ground plane with customizable material (ground or water or maybe a reflective floor. Think about what part of this should be done with triles and what with textures.)
|
||||||
|
- Sky editor and passing to shader.
|
||||||
|
- Level saving
|
||||||
|
- Trile atlas saving? JSON?
|
||||||
|
|
||||||
|
PLANNED:
|
||||||
|
- Render distance, sky and fog?
|
||||||
|
- Load RDMs. Load into the Trile_GFX structure?
|
||||||
|
- Render RDMs.
|
||||||
|
- Start working on game port for proof of concept.
|
||||||
|
- Maybe port tacoma in as a part of the engine in plain Vulkan without nvpro. Would also allow for cool ability to render levels as path traced for screenshots and references.
|
||||||
|
- Improve the bz4x format, or figure out a pre-existing format.
|
||||||
|
|||||||
16
first.jai
16
first.jai
@ -15,13 +15,6 @@ Trueno_Build_Options :: struct {
|
|||||||
test_exe_game : bool;
|
test_exe_game : bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine tests build against test_game/ so they work without the game/ directory;
|
|
||||||
// game tests build against the real game and its real resources. Kept in step with
|
|
||||||
// FLAG_USE_TEST_GAME in src/main.jai.
|
|
||||||
uses_test_game :: (opts: Trueno_Build_Options) -> bool {
|
|
||||||
return opts.test_engine || opts.test_exe_engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
build_options_from_args :: (args: []string) -> Trueno_Build_Options {
|
build_options_from_args :: (args: []string) -> Trueno_Build_Options {
|
||||||
opts : Trueno_Build_Options;
|
opts : Trueno_Build_Options;
|
||||||
|
|
||||||
@ -73,9 +66,8 @@ max_asset_file_size :: (dir: string, ext: string, floor: s64 = 0) -> s64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_asset_buffer_sizes_to_compiler_strings :: (trueno_opts: Trueno_Build_Options, w: Workspace) {
|
add_asset_buffer_sizes_to_compiler_strings :: (trueno_opts: Trueno_Build_Options, w: Workspace) {
|
||||||
use_test_game := uses_test_game(trueno_opts);
|
pack_dir := ifx trueno_opts.test_exe_engine || trueno_opts.test_exe_game then "./test_packs" else "./packs";
|
||||||
pack_dir := ifx use_test_game then "./test_packs" else "./packs";
|
game_resources_dir := ifx trueno_opts.test_exe_engine || trueno_opts.test_exe_game then "./test_game/resources" else "./game/resources";
|
||||||
game_resources_dir := ifx use_test_game then "./test_game/resources" else "./game/resources";
|
|
||||||
|
|
||||||
sizes: [6]s64;
|
sizes: [6]s64;
|
||||||
sizes[0] = max_asset_file_size(pack_dir, ".pack", 1 * 1024 * 1024);
|
sizes[0] = max_asset_file_size(pack_dir, ".pack", 1 * 1024 * 1024);
|
||||||
@ -262,8 +254,8 @@ wasm_build :: (opts: Build_Options, trueno_opts: Trueno_Build_Options) {
|
|||||||
opt := get_build_options();
|
opt := get_build_options();
|
||||||
trueno_opts := build_options_from_args(opt.compile_time_command_line);
|
trueno_opts := build_options_from_args(opt.compile_time_command_line);
|
||||||
compile_shaders();
|
compile_shaders();
|
||||||
use_test_game := uses_test_game(trueno_opts);
|
is_test_exe := trueno_opts.test_exe_engine || trueno_opts.test_exe_game;
|
||||||
create_pack(use_test_game, ifx use_test_game then "./test_packs" else "./packs");
|
create_pack(is_test_exe, ifx is_test_exe then "./test_packs" else "./packs");
|
||||||
|
|
||||||
if trueno_opts.wasm_build {
|
if trueno_opts.wasm_build {
|
||||||
wasm_build(opt, trueno_opts);
|
wasm_build(opt, trueno_opts);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
_______________
|
_______________
|
||||||
Vulkan Version:
|
Vulkan Version:
|
||||||
- available: 1.3.296
|
- available: 1.4.309
|
||||||
- requesting: 1.3.0
|
- requesting: 1.3.0
|
||||||
______________________
|
______________________
|
||||||
Used Instance Layers :
|
Used Instance Layers :
|
||||||
@ -8,17 +8,15 @@ VK_LAYER_KHRONOS_validation
|
|||||||
|
|
||||||
Used Instance Extensions :
|
Used Instance Extensions :
|
||||||
____________________
|
____________________
|
||||||
Devices : 2
|
Devices : 1
|
||||||
0: AMD Radeon RX 6700 XT (RADV NAVI22)
|
0: AMD Radeon RX 6950 XT
|
||||||
- Compatible
|
- Compatible
|
||||||
1: llvmpipe (LLVM 20.1.2, 256 bits)
|
Compatible physical devices found : 1
|
||||||
- Compatible
|
|
||||||
Compatible physical devices found : 2
|
|
||||||
Using Device:
|
Using Device:
|
||||||
- Device Name : AMD Radeon RX 6700 XT (RADV NAVI22)
|
- Device Name : AMD Radeon RX 6950 XT
|
||||||
- Vendor : AMD
|
- Vendor : AMD
|
||||||
- Driver Version : 25.2.8
|
- Driver Version : 2.0.341
|
||||||
- API Version : 1.4.318
|
- API Version : 1.4.308
|
||||||
- Device Type : Discrete GPU
|
- Device Type : Discrete GPU
|
||||||
________________________
|
________________________
|
||||||
Used Device Extensions :
|
Used Device Extensions :
|
||||||
@ -26,4 +24,4 @@ VK_KHR_deferred_host_operations
|
|||||||
VK_KHR_acceleration_structure
|
VK_KHR_acceleration_structure
|
||||||
VK_KHR_ray_query
|
VK_KHR_ray_query
|
||||||
|
|
||||||
BLAS Compaction: 0.3MB -> 0.2MB (0.1MB saved, 32.1% smaller)
|
BLAS Compaction: 1.8MB -> 0.6MB (1.2MB saved, 65.9% smaller)
|
||||||
|
|||||||
@ -332,6 +332,7 @@ parse_object :: (str: string) -> result: JSON_Object, remainder: string, success
|
|||||||
return result, remainder, true;
|
return result, remainder, true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(*result, 32);
|
||||||
while true {
|
while true {
|
||||||
if remainder[0] != #char "\"" return result, remainder, false;
|
if remainder[0] != #char "\"" return result, remainder, false;
|
||||||
|
|
||||||
|
|||||||
@ -540,6 +540,7 @@ parse_object :: (str: string, slot: *u8, info: *Type_Info_Struct, ignore_unknown
|
|||||||
// @Speed: Building this table every time is pretty silly.
|
// @Speed: Building this table every time is pretty silly.
|
||||||
// We should probably either not build it at all or cache it somewhere.
|
// We should probably either not build it at all or cache it somewhere.
|
||||||
member_table: Table(string, Member_Offset);
|
member_table: Table(string, Member_Offset);
|
||||||
|
init(*member_table);
|
||||||
defer deinit(*member_table);
|
defer deinit(*member_table);
|
||||||
|
|
||||||
if info fill_member_table(*member_table, info, rename);
|
if info fill_member_table(*member_table, info, rename);
|
||||||
|
|||||||
142
run_tests.sh
142
run_tests.sh
@ -1,142 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Runs every test tier and exits nonzero if any of them fail.
|
|
||||||
#
|
|
||||||
# ./run_tests.sh # everything that can run here
|
|
||||||
# ./run_tests.sh unit # compile-time tests only (no display needed)
|
|
||||||
# ./run_tests.sh engine # engine tiers only
|
|
||||||
# ./run_tests.sh game # game tiers only
|
|
||||||
#
|
|
||||||
# Tiers:
|
|
||||||
# test_engine engine unit tests compile-time #run, builds against test_game/
|
|
||||||
# test_game game unit tests compile-time #run, builds against game/
|
|
||||||
# test_exe_engine engine exe tests builds then runs ./first against test_game/
|
|
||||||
# test_exe_game game exe tests builds then runs ./first against game/
|
|
||||||
#
|
|
||||||
# The unit tiers report through the compiler's exit code. The exe tiers run the
|
|
||||||
# real app, so they need a display and are wrapped in a hard timeout.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
JAI="${JAI:-$HOME/bin/jai/bin/jai-linux}"
|
|
||||||
EXE_TIMEOUT="${EXE_TIMEOUT:-180}" # seconds per exe-test binary, kills a true freeze
|
|
||||||
|
|
||||||
FILTER="${1:-all}"
|
|
||||||
|
|
||||||
if [ ! -x "$JAI" ]; then
|
|
||||||
echo "error: Jai compiler not found at '$JAI' (override with JAI=/path/to/jai)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The game/ directory is not in the repo, so the game tiers are skipped rather
|
|
||||||
# than failed on a checkout that does not have it.
|
|
||||||
HAVE_GAME=0
|
|
||||||
[ -f game/game.jai ] && HAVE_GAME=1
|
|
||||||
|
|
||||||
# Exe tests open a real window. Prefer a live display, fall back to Xvfb.
|
|
||||||
XVFB=()
|
|
||||||
NO_DISPLAY=0
|
|
||||||
if [ -z "${DISPLAY:-}" ]; then
|
|
||||||
if command -v xvfb-run >/dev/null 2>&1; then
|
|
||||||
XVFB=(xvfb-run -a)
|
|
||||||
else
|
|
||||||
NO_DISPLAY=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
PASSED=(); FAILED=(); SKIPPED=()
|
|
||||||
|
|
||||||
want() {
|
|
||||||
case "$FILTER" in
|
|
||||||
all) return 0 ;;
|
|
||||||
unit) [[ "$1" != *exe* ]] ;;
|
|
||||||
engine) [[ "$1" == *engine* ]] ;;
|
|
||||||
game) [[ "$1" == *game* && "$1" != *engine* ]] ;;
|
|
||||||
*) echo "error: unknown filter '$FILTER' (use all|unit|engine|game)" >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
run_unit_tier() {
|
|
||||||
local flag="$1"
|
|
||||||
echo ""
|
|
||||||
echo "=============================================================="
|
|
||||||
echo " $flag (compile-time)"
|
|
||||||
echo "=============================================================="
|
|
||||||
if "$JAI" first.jai - "$flag"; then
|
|
||||||
PASSED+=("$flag")
|
|
||||||
else
|
|
||||||
FAILED+=("$flag")
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
run_exe_tier() {
|
|
||||||
local flag="$1"
|
|
||||||
echo ""
|
|
||||||
echo "=============================================================="
|
|
||||||
echo " $flag (builds, then runs the app)"
|
|
||||||
echo "=============================================================="
|
|
||||||
|
|
||||||
if ! "$JAI" first.jai - "$flag"; then
|
|
||||||
echo "[$flag] build failed"
|
|
||||||
FAILED+=("$flag (build)")
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
timeout --foreground "$EXE_TIMEOUT" "${XVFB[@]}" ./first
|
|
||||||
local status=$?
|
|
||||||
if [ $status -eq 0 ]; then
|
|
||||||
PASSED+=("$flag")
|
|
||||||
elif [ $status -eq 124 ]; then
|
|
||||||
echo "[$flag] hung and was killed after ${EXE_TIMEOUT}s"
|
|
||||||
FAILED+=("$flag (hung)")
|
|
||||||
else
|
|
||||||
echo "[$flag] exited $status"
|
|
||||||
FAILED+=("$flag")
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
for flag in test_engine test_game; do
|
|
||||||
want "$flag" || continue
|
|
||||||
if [ "$flag" = "test_game" ] && [ "$HAVE_GAME" -eq 0 ]; then
|
|
||||||
SKIPPED+=("$flag (no game/ directory)")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
run_unit_tier "$flag"
|
|
||||||
done
|
|
||||||
|
|
||||||
for flag in test_exe_engine test_exe_game; do
|
|
||||||
want "$flag" || continue
|
|
||||||
if [ "$NO_DISPLAY" -eq 1 ]; then
|
|
||||||
SKIPPED+=("$flag (no DISPLAY and no xvfb-run)")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [ "$flag" = "test_exe_game" ] && [ "$HAVE_GAME" -eq 0 ]; then
|
|
||||||
SKIPPED+=("$flag (no game/ directory)")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
run_exe_tier "$flag"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=============================================================="
|
|
||||||
echo " Summary"
|
|
||||||
echo "=============================================================="
|
|
||||||
for t in ${PASSED[@]+"${PASSED[@]}"}; do echo " PASS $t"; done
|
|
||||||
for t in ${SKIPPED[@]+"${SKIPPED[@]}"}; do echo " SKIP $t"; done
|
|
||||||
for t in ${FAILED[@]+"${FAILED[@]}"}; do echo " FAIL $t"; done
|
|
||||||
|
|
||||||
if [ ${#FAILED[@]} -ne 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "${#FAILED[@]} tier(s) failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# A run where everything was skipped is not a pass.
|
|
||||||
if [ ${#PASSED[@]} -eq 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "Nothing ran."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "All ${#PASSED[@]} tier(s) passed."
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
master_volume 0.15
|
master_volume 0.364372
|
||||||
music_volume 1
|
music_volume 1
|
||||||
sfx_volume 1
|
sfx_volume 1
|
||||||
fullscreen 0
|
fullscreen 1
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
#load "iprof.jai";
|
#load "iprof.jai";
|
||||||
#load "trile_thumbnails.jai";
|
#load "trile_thumbnails.jai";
|
||||||
#load "picker.jai";
|
#load "picker.jai";
|
||||||
#load "gizmo.jai";
|
|
||||||
#load "trile_editor.jai";
|
#load "trile_editor.jai";
|
||||||
#load "level_editor.jai";
|
#load "level_editor.jai";
|
||||||
#load "particle_editor.jai";
|
#load "particle_editor.jai";
|
||||||
@ -79,10 +78,7 @@ draw_editor_ui :: (theme: *GR.Overall_Theme) {
|
|||||||
|
|
||||||
draw_editor :: () {
|
draw_editor :: () {
|
||||||
#if OS != .WASM {
|
#if OS != .WASM {
|
||||||
disable_dof_post_process = false;
|
|
||||||
if !in_editor_view then return;
|
if !in_editor_view then return;
|
||||||
disable_dof_post_process = true;
|
|
||||||
|
|
||||||
|
|
||||||
if current_editor_view == {
|
if current_editor_view == {
|
||||||
case .Trile_Editor;
|
case .Trile_Editor;
|
||||||
@ -111,14 +107,14 @@ tick_editor_ui :: () {
|
|||||||
|
|
||||||
#if OS != .WASM {
|
#if OS != .WASM {
|
||||||
if in_editor_view {
|
if in_editor_view {
|
||||||
if current_editor_view == {
|
if current_editor_view == {
|
||||||
case .Trile_Editor;
|
case .Trile_Editor;
|
||||||
tick_trile_editor();
|
tick_trile_editor();
|
||||||
case .Level_Editor;
|
case .Level_Editor;
|
||||||
tick_level_editor();
|
tick_level_editor();
|
||||||
case .Particle_Editor;
|
case .Particle_Editor;
|
||||||
tick_particle_editor();
|
tick_particle_editor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,455 +0,0 @@
|
|||||||
// Blender-style gizmos: a translate gizmo with three RGB axis arrows plus three
|
|
||||||
// plane handles for two-axis drags, and a rotate gizmo with three RGB rings.
|
|
||||||
// Drawn with depth-ignoring overlay lines so they stay visible through terrain,
|
|
||||||
// and picked in world space so the pick thresholds scale with the gizmo itself.
|
|
||||||
|
|
||||||
Gizmo_Handle :: enum {
|
|
||||||
NONE;
|
|
||||||
AXIS_X;
|
|
||||||
AXIS_Y;
|
|
||||||
AXIS_Z;
|
|
||||||
PLANE_YZ; // normal +X
|
|
||||||
PLANE_XZ; // normal +Y
|
|
||||||
PLANE_XY; // normal +Z
|
|
||||||
RING_X; // turns about +X
|
|
||||||
RING_Y;
|
|
||||||
RING_Z;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gizmo_State :: struct {
|
|
||||||
hover : Gizmo_Handle;
|
|
||||||
active : Gizmo_Handle;
|
|
||||||
|
|
||||||
start_position : Vector3; // object position when the drag began
|
|
||||||
start_t : float; // axis drags: parameter along the axis at grab time
|
|
||||||
start_hit : Vector3; // plane drags: world point grabbed
|
|
||||||
start_angle : float; // ring drags: angle around the ring at grab time
|
|
||||||
applied_steps : int; // ring drags: quarter turns already handed to the caller
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gizmo length as a fraction of the camera distance, so it keeps a constant
|
|
||||||
// on-screen size. Everything else is expressed in units of that length.
|
|
||||||
GIZMO_SCALE :: 0.14;
|
|
||||||
GIZMO_PLANE_INNER :: 0.22;
|
|
||||||
GIZMO_PLANE_OUTER :: 0.52;
|
|
||||||
GIZMO_PICK_RADIUS :: 0.08;
|
|
||||||
// Below this |dot(plane normal, view dir)| the plane handle is edge-on: too thin
|
|
||||||
// to aim at, so it is neither drawn nor picked.
|
|
||||||
GIZMO_PLANE_MIN_FACING :: 0.15;
|
|
||||||
// Ring radius, and how far off it a click can land, both in units of gizmo length.
|
|
||||||
GIZMO_RING_RADIUS :: 1.0;
|
|
||||||
GIZMO_RING_PICK :: 0.14;
|
|
||||||
GIZMO_RING_SEGMENTS :: 48;
|
|
||||||
// Line widths, in pixels.
|
|
||||||
GIZMO_LINE_WIDTH :: 3.0;
|
|
||||||
GIZMO_FILL_WIDTH :: 2.0;
|
|
||||||
|
|
||||||
// Runs one frame of the gizmo: hover, click-to-grab, drag, release, and drawing.
|
|
||||||
// Returns the dragged-to position; the caller owns snapping and writing it back.
|
|
||||||
gizmo_translate :: (using state: *Gizmo_State, position: Vector3, cam: Camera, ray: Ray) -> (position: Vector3, changed: bool) {
|
|
||||||
len := max(length(cam.position - position) * GIZMO_SCALE, 0.001);
|
|
||||||
pos := position;
|
|
||||||
changed := false;
|
|
||||||
|
|
||||||
mouse := get_mouse_state(Key_Code.MOUSE_BUTTON_LEFT);
|
|
||||||
pressed := (mouse & .START) != .NONE;
|
|
||||||
held := (mouse & .DOWN) != .NONE;
|
|
||||||
|
|
||||||
if active == .NONE {
|
|
||||||
hover = gizmo_pick(position, len, cam, ray);
|
|
||||||
if hover != .NONE && pressed {
|
|
||||||
start_position = position;
|
|
||||||
if gizmo_is_plane(hover) {
|
|
||||||
ok, point := gizmo_plane_hit(position, hover, ray);
|
|
||||||
if ok {
|
|
||||||
start_hit = point;
|
|
||||||
active = hover;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t, _, _ := gizmo_closest_axis_t(ray, position, gizmo_axis(hover));
|
|
||||||
start_t = t;
|
|
||||||
active = hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !held {
|
|
||||||
active = .NONE;
|
|
||||||
} else {
|
|
||||||
if gizmo_is_plane(active) {
|
|
||||||
ok, point := gizmo_plane_hit(start_position, active, ray);
|
|
||||||
if ok {
|
|
||||||
pos = start_position + (point - start_hit);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
axis := gizmo_axis(active);
|
|
||||||
t, _, _ := gizmo_closest_axis_t(ray, start_position, axis);
|
|
||||||
pos = start_position + axis * (t - start_t);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drawn at the object's own position rather than the dragged-to one: the
|
|
||||||
// caller may snap what we hand back, and the gizmo has to sit on the object.
|
|
||||||
gizmo_draw(position, len, cam, ifx active != .NONE then active else hover);
|
|
||||||
return pos, changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// One frame of the rotate gizmo. Entities only hold the 24 axis-aligned cube
|
|
||||||
// rotations, so a drag reports whole quarter turns: the returned basis is the
|
|
||||||
// (u, v, n) of the ring being dragged and 'steps' is how many turns of u toward
|
|
||||||
// v to apply since the last frame, which is usually zero.
|
|
||||||
gizmo_rotate :: (using state: *Gizmo_State, position: Vector3, cam: Camera, ray: Ray) -> (u: Vector3, v: Vector3, n: Vector3, steps: int) {
|
|
||||||
len := max(length(cam.position - position) * GIZMO_SCALE, 0.001);
|
|
||||||
|
|
||||||
mouse := get_mouse_state(Key_Code.MOUSE_BUTTON_LEFT);
|
|
||||||
pressed := (mouse & .START) != .NONE;
|
|
||||||
held := (mouse & .DOWN) != .NONE;
|
|
||||||
|
|
||||||
steps := 0;
|
|
||||||
|
|
||||||
if active == .NONE {
|
|
||||||
hover = gizmo_pick_ring(position, len, cam, ray);
|
|
||||||
if hover != .NONE && pressed {
|
|
||||||
ok, angle := gizmo_ring_angle(position, hover, ray);
|
|
||||||
if ok {
|
|
||||||
start_position = position;
|
|
||||||
start_angle = angle;
|
|
||||||
applied_steps = 0;
|
|
||||||
active = hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !held {
|
|
||||||
active = .NONE;
|
|
||||||
} else {
|
|
||||||
ok, angle := gizmo_ring_angle(start_position, active, ray);
|
|
||||||
if ok {
|
|
||||||
// Wrap into (-180, 180] so passing the seam doesn't spin the object.
|
|
||||||
delta := angle - start_angle;
|
|
||||||
while delta > PI delta -= 2 * PI;
|
|
||||||
while delta < -PI delta += 2 * PI;
|
|
||||||
total := cast(int) floor(delta / (PI * 0.5) + 0.5);
|
|
||||||
steps = total - applied_steps;
|
|
||||||
applied_steps = total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_draw_rings(position, len, cam, ifx active != .NONE then active else hover);
|
|
||||||
|
|
||||||
handle := ifx active != .NONE then active else Gizmo_Handle.RING_Y;
|
|
||||||
u, v := gizmo_plane_axes(handle);
|
|
||||||
return u, v, gizmo_axis(handle), steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_axis :: (h: Gizmo_Handle) -> Vector3 {
|
|
||||||
if h == {
|
|
||||||
case .AXIS_X; #through;
|
|
||||||
case .PLANE_YZ; #through;
|
|
||||||
case .RING_X; return .{1, 0, 0};
|
|
||||||
case .AXIS_Y; #through;
|
|
||||||
case .PLANE_XZ; #through;
|
|
||||||
case .RING_Y; return .{0, 1, 0};
|
|
||||||
case; return .{0, 0, 1};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_is_plane :: (h: Gizmo_Handle) -> bool {
|
|
||||||
return h == .PLANE_YZ || h == .PLANE_XZ || h == .PLANE_XY;
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_is_ring :: (h: Gizmo_Handle) -> bool {
|
|
||||||
return h == .RING_X || h == .RING_Y || h == .RING_Z;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The two in-plane axes of a plane or ring handle; gizmo_axis gives the third
|
|
||||||
// (the plane normal, or the axis the ring turns about).
|
|
||||||
gizmo_plane_axes :: (h: Gizmo_Handle) -> (u: Vector3, v: Vector3) {
|
|
||||||
if h == {
|
|
||||||
case .PLANE_YZ; #through;
|
|
||||||
case .RING_X; return .{0, 1, 0}, .{0, 0, 1};
|
|
||||||
case .PLANE_XZ; #through;
|
|
||||||
case .RING_Y; return .{1, 0, 0}, .{0, 0, 1};
|
|
||||||
case; return .{1, 0, 0}, .{0, 1, 0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles are colored by their axis; plane handles by the axis they are
|
|
||||||
// perpendicular to, which is what gizmo_axis returns for them.
|
|
||||||
gizmo_color :: (h: Gizmo_Handle, highlighted: bool) -> Vector4 {
|
|
||||||
if highlighted then return .{1.0, 0.85, 0.2, 1.0};
|
|
||||||
axis := gizmo_axis(h);
|
|
||||||
if axis.x != 0 then return .{0.95, 0.28, 0.30, 1.0};
|
|
||||||
if axis.y != 0 then return .{0.40, 0.90, 0.32, 1.0};
|
|
||||||
return .{0.30, 0.50, 0.98, 1.0};
|
|
||||||
}
|
|
||||||
|
|
||||||
#scope_file
|
|
||||||
|
|
||||||
// Parameter along the line (origin + axis*t) of the point closest to the ray,
|
|
||||||
// plus that point and the distance between the two lines at closest approach.
|
|
||||||
gizmo_closest_axis_t :: (ray: Ray, origin: Vector3, axis: Vector3) -> (t: float, point: Vector3, dist: float) {
|
|
||||||
r := origin - ray.origin;
|
|
||||||
a := dot(axis, axis);
|
|
||||||
b := dot(axis, ray.direction);
|
|
||||||
e := dot(ray.direction, ray.direction);
|
|
||||||
c := dot(axis, r);
|
|
||||||
f := dot(ray.direction, r);
|
|
||||||
|
|
||||||
denom := a*e - b*b;
|
|
||||||
if abs(denom) < 0.00001 then return 0, origin, 99999; // ray parallel to the axis
|
|
||||||
|
|
||||||
t := (b*f - c*e) / denom;
|
|
||||||
s := (a*f - c*b) / denom;
|
|
||||||
pa := origin + axis * t;
|
|
||||||
pb := ray.origin + ray.direction * s;
|
|
||||||
return t, pa, length(pa - pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_plane_hit :: (origin: Vector3, h: Gizmo_Handle, ray: Ray) -> (bool, Vector3) {
|
|
||||||
n := gizmo_axis(h);
|
|
||||||
denom := dot(n, ray.direction);
|
|
||||||
if abs(denom) < 0.0001 then return false, .{};
|
|
||||||
t := dot(n, origin - ray.origin) / denom;
|
|
||||||
if t < 0 then return false, .{}; // plane is behind the camera
|
|
||||||
return true, ray.origin + ray.direction * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Which way each plane handle is offset from the origin: toward the camera, so
|
|
||||||
// the handles always sit in the octant facing the viewer.
|
|
||||||
gizmo_plane_signs :: (origin: Vector3, h: Gizmo_Handle, cam: Camera) -> (su: float, sv: float) {
|
|
||||||
u, v := gizmo_plane_axes(h);
|
|
||||||
to_cam := cam.position - origin;
|
|
||||||
su := ifx dot(u, to_cam) >= 0 then cast(float)1 else cast(float)-1;
|
|
||||||
sv := ifx dot(v, to_cam) >= 0 then cast(float)1 else cast(float)-1;
|
|
||||||
return su, sv;
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_pick :: (origin: Vector3, len: float, cam: Camera, ray: Ray) -> Gizmo_Handle {
|
|
||||||
// Planes first: they sit closer to the origin and never overlap the arrows,
|
|
||||||
// but grabbing a plane is the more common intent when they are near.
|
|
||||||
for h: Gizmo_Handle.[.PLANE_YZ, .PLANE_XZ, .PLANE_XY] {
|
|
||||||
n := gizmo_axis(h);
|
|
||||||
if abs(dot(n, normalize(cam.position - origin))) < GIZMO_PLANE_MIN_FACING then continue;
|
|
||||||
ok, point := gizmo_plane_hit(origin, h, ray);
|
|
||||||
if !ok then continue;
|
|
||||||
u, v := gizmo_plane_axes(h);
|
|
||||||
su, sv := gizmo_plane_signs(origin, h, cam);
|
|
||||||
d := point - origin;
|
|
||||||
cu := dot(d, u) * su;
|
|
||||||
cv := dot(d, v) * sv;
|
|
||||||
if cu >= GIZMO_PLANE_INNER * len && cu <= GIZMO_PLANE_OUTER * len
|
|
||||||
&& cv >= GIZMO_PLANE_INNER * len && cv <= GIZMO_PLANE_OUTER * len {
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
best := Gizmo_Handle.NONE;
|
|
||||||
best_dist := GIZMO_PICK_RADIUS * len;
|
|
||||||
for h: Gizmo_Handle.[.AXIS_X, .AXIS_Y, .AXIS_Z] {
|
|
||||||
t, _, dist := gizmo_closest_axis_t(ray, origin, gizmo_axis(h));
|
|
||||||
if t < 0 || t > len then continue;
|
|
||||||
if dist < best_dist {
|
|
||||||
best_dist = dist;
|
|
||||||
best = h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_draw :: (origin: Vector3, len: float, cam: Camera, highlighted: Gizmo_Handle) {
|
|
||||||
for h: Gizmo_Handle.[.PLANE_YZ, .PLANE_XZ, .PLANE_XY] {
|
|
||||||
n := gizmo_axis(h);
|
|
||||||
if abs(dot(n, normalize(cam.position - origin))) < GIZMO_PLANE_MIN_FACING then continue;
|
|
||||||
u, v := gizmo_plane_axes(h);
|
|
||||||
su, sv := gizmo_plane_signs(origin, h, cam);
|
|
||||||
col := gizmo_color(h, h == highlighted);
|
|
||||||
gizmo_draw_plane(origin, u * su, v * sv, GIZMO_PLANE_INNER * len, GIZMO_PLANE_OUTER * len, col, h == highlighted);
|
|
||||||
}
|
|
||||||
|
|
||||||
for h: Gizmo_Handle.[.AXIS_X, .AXIS_Y, .AXIS_Z] {
|
|
||||||
gizmo_draw_arrow(origin, gizmo_axis(h) * len, gizmo_color(h, h == highlighted));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_draw_arrow :: (origin: Vector3, vec: Vector3, col: Vector4) {
|
|
||||||
tip := origin + vec;
|
|
||||||
shaft := normalize(vec);
|
|
||||||
debug_line_overlay(origin, tip, col, GIZMO_LINE_WIDTH);
|
|
||||||
|
|
||||||
head := length(vec) * 0.18;
|
|
||||||
back := tip - shaft * head;
|
|
||||||
perp : Vector3;
|
|
||||||
if abs(shaft.x) < 0.9 {
|
|
||||||
perp = normalize(cross(shaft, .{1, 0, 0}));
|
|
||||||
} else {
|
|
||||||
perp = normalize(cross(shaft, .{0, 1, 0}));
|
|
||||||
}
|
|
||||||
perp2 := cross(shaft, perp);
|
|
||||||
w1 := perp * (head * 0.4);
|
|
||||||
w2 := perp2 * (head * 0.4);
|
|
||||||
// Four fins plus a ring, so the head reads as a cone from any angle.
|
|
||||||
debug_line_overlay(back + w1, tip, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(back - w1, tip, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(back + w2, tip, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(back - w2, tip, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(back + w1, back + w2, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(back + w2, back - w1, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(back - w1, back - w2, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(back - w2, back + w1, col, GIZMO_LINE_WIDTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A square in the (u, v) plane between inner and outer, outlined at full alpha
|
|
||||||
// and hatched at low alpha so it reads as a translucent pane.
|
|
||||||
gizmo_draw_plane :: (origin: Vector3, u: Vector3, v: Vector3, inner: float, outer: float, col: Vector4, filled: bool) {
|
|
||||||
p00 := origin + u * inner + v * inner;
|
|
||||||
p10 := origin + u * outer + v * inner;
|
|
||||||
p11 := origin + u * outer + v * outer;
|
|
||||||
p01 := origin + u * inner + v * outer;
|
|
||||||
debug_line_overlay(p00, p10, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(p10, p11, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(p11, p01, col, GIZMO_LINE_WIDTH);
|
|
||||||
debug_line_overlay(p01, p00, col, GIZMO_LINE_WIDTH);
|
|
||||||
|
|
||||||
fill := col;
|
|
||||||
fill.w = ifx filled then 0.55 else 0.18;
|
|
||||||
// Enough hatch lines that the pane reads as filled rather than striped.
|
|
||||||
HATCH :: 14;
|
|
||||||
for i: 1..HATCH {
|
|
||||||
f := cast(float)i / cast(float)(HATCH + 1);
|
|
||||||
c := inner + (outer - inner) * f;
|
|
||||||
debug_line_overlay(origin + u * c + v * inner, origin + u * c + v * outer, fill, GIZMO_FILL_WIDTH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Angle of the mouse ray's hit on the ring's plane, measured from u toward v.
|
|
||||||
gizmo_ring_angle :: (origin: Vector3, h: Gizmo_Handle, ray: Ray) -> (ok: bool, angle: float) {
|
|
||||||
ok, point := gizmo_plane_hit(origin, h, ray);
|
|
||||||
if !ok then return false, 0;
|
|
||||||
u, v := gizmo_plane_axes(h);
|
|
||||||
d := point - origin;
|
|
||||||
return true, atan2(dot(d, v), dot(d, u));
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_pick_ring :: (origin: Vector3, len: float, cam: Camera, ray: Ray) -> Gizmo_Handle {
|
|
||||||
best := Gizmo_Handle.NONE;
|
|
||||||
best_dist := GIZMO_RING_PICK * len;
|
|
||||||
for h: Gizmo_Handle.[.RING_X, .RING_Y, .RING_Z] {
|
|
||||||
if !gizmo_ring_faces_camera(origin, h, cam) then continue;
|
|
||||||
ok, point := gizmo_plane_hit(origin, h, ray);
|
|
||||||
if !ok then continue;
|
|
||||||
// Distance from the ring itself, not from its centre.
|
|
||||||
off := abs(length(point - origin) - GIZMO_RING_RADIUS * len);
|
|
||||||
if off < best_dist {
|
|
||||||
best_dist = off;
|
|
||||||
best = h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
// An edge-on ring is a line on screen: impossible to aim at, and its plane
|
|
||||||
// intersection shoots off to infinity, so skip it.
|
|
||||||
gizmo_ring_faces_camera :: (origin: Vector3, h: Gizmo_Handle, cam: Camera) -> bool {
|
|
||||||
return abs(dot(gizmo_axis(h), normalize(cam.position - origin))) >= GIZMO_PLANE_MIN_FACING;
|
|
||||||
}
|
|
||||||
|
|
||||||
gizmo_draw_rings :: (origin: Vector3, len: float, cam: Camera, highlighted: Gizmo_Handle) {
|
|
||||||
for h: Gizmo_Handle.[.RING_X, .RING_Y, .RING_Z] {
|
|
||||||
is_hot := h == highlighted;
|
|
||||||
if !gizmo_ring_faces_camera(origin, h, cam) && !is_hot then continue;
|
|
||||||
u, v := gizmo_plane_axes(h);
|
|
||||||
col := gizmo_color(h, is_hot);
|
|
||||||
r := GIZMO_RING_RADIUS * len;
|
|
||||||
prev := origin + u * r;
|
|
||||||
for i: 1..GIZMO_RING_SEGMENTS {
|
|
||||||
a := (cast(float)i / cast(float)GIZMO_RING_SEGMENTS) * 2 * PI;
|
|
||||||
next := origin + u * (cos(a) * r) + v * (sin(a) * r);
|
|
||||||
debug_line_overlay(prev, next, col, GIZMO_LINE_WIDTH);
|
|
||||||
prev = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FLAG_TEST_ENGINE {
|
|
||||||
gizmo_test_cam :: () -> Camera {
|
|
||||||
// Off the +X+Y+Z octant, so every plane handle faces the viewer and its
|
|
||||||
// signs are all positive.
|
|
||||||
cam : Camera;
|
|
||||||
cam.position = .{5, 5, 5};
|
|
||||||
cam.target = .{0, 0, 0};
|
|
||||||
return cam;
|
|
||||||
}
|
|
||||||
|
|
||||||
test_gizmo_closest_axis :: () {
|
|
||||||
s := begin_suite("gizmo closest point on axis");
|
|
||||||
// Vertical ray dropping onto the +X axis at x = 0.5.
|
|
||||||
ray := Ray.{ origin = .{0.5, 3, 0}, direction = .{0, -1, 0} };
|
|
||||||
t, point, dist := gizmo_closest_axis_t(ray, .{0, 0, 0}, .{1, 0, 0});
|
|
||||||
check(*s, "t is the distance along the axis", abs(t - 0.5) < 0.001);
|
|
||||||
check(*s, "closest point sits on the axis", abs(point.x - 0.5) < 0.001 && abs(point.y) < 0.001 && abs(point.z) < 0.001);
|
|
||||||
check(*s, "intersecting lines have 0 distance", dist < 0.001);
|
|
||||||
|
|
||||||
// A ray offset in Z never meets the axis; the gap is that offset.
|
|
||||||
off := Ray.{ origin = .{0.5, 3, 0.25}, direction = .{0, -1, 0} };
|
|
||||||
_, _, off_dist := gizmo_closest_axis_t(off, .{0, 0, 0}, .{1, 0, 0});
|
|
||||||
check(*s, "offset ray reports its offset as distance", abs(off_dist - 0.25) < 0.001);
|
|
||||||
|
|
||||||
// Parallel lines have no unique closest point.
|
|
||||||
par := Ray.{ origin = .{0, 3, 0}, direction = .{1, 0, 0} };
|
|
||||||
_, _, par_dist := gizmo_closest_axis_t(par, .{0, 0, 0}, .{1, 0, 0});
|
|
||||||
check(*s, "parallel ray is rejected", par_dist > 1000);
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
test_gizmo_pick :: () {
|
|
||||||
s := begin_suite("gizmo handle picking");
|
|
||||||
cam := gizmo_test_cam();
|
|
||||||
len : float : 1.0;
|
|
||||||
|
|
||||||
// Straight down onto the middle of the +X arrow.
|
|
||||||
axis_ray := Ray.{ origin = .{0.5, 3, 0}, direction = .{0, -1, 0} };
|
|
||||||
check(*s, "aiming at the X arrow picks AXIS_X", gizmo_pick(.{0,0,0}, len, cam, axis_ray) == .AXIS_X);
|
|
||||||
|
|
||||||
// Down onto the XZ pane, which spans 0.22..0.52 on both of its axes.
|
|
||||||
plane_ray := Ray.{ origin = .{0.35, 3, 0.35}, direction = .{0, -1, 0} };
|
|
||||||
check(*s, "aiming at the XZ pane picks PLANE_XZ", gizmo_pick(.{0,0,0}, len, cam, plane_ray) == .PLANE_XZ);
|
|
||||||
|
|
||||||
// Inside the pane's plane but short of its inner edge: no handle there.
|
|
||||||
gap_ray := Ray.{ origin = .{0.1, 3, 0.1}, direction = .{0, -1, 0} };
|
|
||||||
check(*s, "the gap inside the panes picks nothing", gizmo_pick(.{0,0,0}, len, cam, gap_ray) == .NONE);
|
|
||||||
|
|
||||||
// Past the end of every arrow.
|
|
||||||
miss_ray := Ray.{ origin = .{2, 3, 2}, direction = .{0, -1, 0} };
|
|
||||||
check(*s, "aiming past the gizmo picks nothing", gizmo_pick(.{0,0,0}, len, cam, miss_ray) == .NONE);
|
|
||||||
|
|
||||||
// Picking follows the gizmo, not the world origin.
|
|
||||||
moved := Vector3.{10, 4, -7};
|
|
||||||
moved_ray := Ray.{ origin = moved + Vector3.{0.5, 3, 0}, direction = .{0, -1, 0} };
|
|
||||||
check(*s, "picking is relative to the gizmo origin", gizmo_pick(moved, len, cam, moved_ray) == .AXIS_X);
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
test_gizmo_plane_hit :: () {
|
|
||||||
s := begin_suite("gizmo plane intersection");
|
|
||||||
// PLANE_XZ has a +Y normal, so it is the horizontal plane through the origin.
|
|
||||||
ray := Ray.{ origin = .{1, 4, 2}, direction = .{0, -1, 0} };
|
|
||||||
ok, point := gizmo_plane_hit(.{0, 0, 0}, .PLANE_XZ, ray);
|
|
||||||
check(*s, "downward ray hits the XZ pane's plane", ok);
|
|
||||||
check(*s, "hit lands at the ray's XZ", abs(point.x - 1) < 0.001 && abs(point.z - 2) < 0.001);
|
|
||||||
check(*s, "hit lands on the plane", abs(point.y) < 0.001);
|
|
||||||
|
|
||||||
// The plane passes through the gizmo origin, not the world origin.
|
|
||||||
shifted, sp := gizmo_plane_hit(.{0, 2.5, 0}, .PLANE_XZ, ray);
|
|
||||||
check(*s, "plane follows the gizmo origin", shifted && abs(sp.y - 2.5) < 0.001);
|
|
||||||
|
|
||||||
away := Ray.{ origin = .{1, 4, 2}, direction = .{0, 1, 0} };
|
|
||||||
no_hit, _ := gizmo_plane_hit(.{0, 0, 0}, .PLANE_XZ, away);
|
|
||||||
check(*s, "ray pointing away misses", !no_hit);
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
#run {
|
|
||||||
test_gizmo_closest_axis();
|
|
||||||
test_gizmo_pick();
|
|
||||||
test_gizmo_plane_hit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -29,7 +29,6 @@ Level_Editor_Tool_Mode :: enum {
|
|||||||
LINE;
|
LINE;
|
||||||
INSPECTOR;
|
INSPECTOR;
|
||||||
VIEWER;
|
VIEWER;
|
||||||
ENTITY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
current_tool_mode : Level_Editor_Tool_Mode = .POINT;
|
current_tool_mode : Level_Editor_Tool_Mode = .POINT;
|
||||||
@ -60,21 +59,6 @@ inspector_rdm_roughness : int = 0;
|
|||||||
inspector_note_deactivate : bool;
|
inspector_note_deactivate : bool;
|
||||||
inspector_note_activate_next : bool;
|
inspector_note_activate_next : bool;
|
||||||
|
|
||||||
// Entity tool: click to select, drag the gizmo to move or turn, right-click to
|
|
||||||
// place. The selection is held by id rather than index so removing an entity
|
|
||||||
// can't silently reselect another.
|
|
||||||
Entity_Gizmo_Mode :: enum {
|
|
||||||
MOVE;
|
|
||||||
ROTATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_gizmo : Gizmo_State;
|
|
||||||
entity_gizmo_mode : Entity_Gizmo_Mode;
|
|
||||||
entity_selected_id : s64 = -1;
|
|
||||||
entity_add_type : s32 = -1;
|
|
||||||
entity_snap_enabled : bool = true;
|
|
||||||
entity_snap_step : float = 1.0;
|
|
||||||
|
|
||||||
editor_billboards_visible : bool = true;
|
editor_billboards_visible : bool = true;
|
||||||
|
|
||||||
get_current_orientation :: () -> u8 {
|
get_current_orientation :: () -> u8 {
|
||||||
@ -336,6 +320,14 @@ draw_tools_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
|||||||
r.h = ui_h(4, 0);
|
r.h = ui_h(4, 0);
|
||||||
|
|
||||||
if keybind_button(r, "Save world", .SAVE, *theme.button_theme) then sworld();
|
if keybind_button(r, "Save world", .SAVE, *theme.button_theme) then sworld();
|
||||||
|
r.y += r.h;
|
||||||
|
if GR.button(r, "Delete buried triles", *theme.button_theme, 400) {
|
||||||
|
curworld := get_current_world();
|
||||||
|
if curworld.valid {
|
||||||
|
removed := delete_buried_triles(*curworld.world);
|
||||||
|
log_info("Deleted % buried triles\n", removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
r.y += r.h * 2;
|
r.y += r.h * 2;
|
||||||
|
|
||||||
// Tool mode buttons
|
// Tool mode buttons
|
||||||
@ -349,8 +341,6 @@ draw_tools_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
|||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
if keybind_button(r, "Inspector", .LEVEL_TOOL_INSPECTOR, *t_button_selectable(theme, current_tool_mode == .INSPECTOR)) then current_tool_mode = .INSPECTOR;
|
if keybind_button(r, "Inspector", .LEVEL_TOOL_INSPECTOR, *t_button_selectable(theme, current_tool_mode == .INSPECTOR)) then current_tool_mode = .INSPECTOR;
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
if keybind_button(r, "Entity", .LEVEL_TOOL_ENTITY, *t_button_selectable(theme, current_tool_mode == .ENTITY)) then current_tool_mode = .ENTITY;
|
|
||||||
r.y += r.h;
|
|
||||||
if keybind_button(r, "Viewer", .LEVEL_TOOL_VIEWER, *t_button_selectable(theme, current_tool_mode == .VIEWER)) then current_tool_mode = .VIEWER;
|
if keybind_button(r, "Viewer", .LEVEL_TOOL_VIEWER, *t_button_selectable(theme, current_tool_mode == .VIEWER)) then current_tool_mode = .VIEWER;
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
|
|
||||||
@ -388,7 +378,7 @@ draw_tools_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
|||||||
GR.label(r, "Click start of line", *t_label_left(theme));
|
GR.label(r, "Click start of line", *t_label_left(theme));
|
||||||
}
|
}
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
} else if current_tool_mode == .INSPECTOR || current_tool_mode == .ENTITY {
|
} else if current_tool_mode == .INSPECTOR {
|
||||||
r.h = ui_h(3, 2);
|
r.h = ui_h(3, 2);
|
||||||
if GR.button(r, ifx editor_billboards_visible then "Hide Markers" else "Show Markers", *theme.button_theme, 199) {
|
if GR.button(r, ifx editor_billboards_visible then "Hide Markers" else "Show Markers", *theme.button_theme, 199) {
|
||||||
editor_billboards_visible = !editor_billboards_visible;
|
editor_billboards_visible = !editor_billboards_visible;
|
||||||
@ -497,7 +487,6 @@ add_trile :: (name: string, x: s32, y: s32, z: s32, orientation: u8 = 0) {
|
|||||||
array_add(*group.instances, inst);
|
array_add(*group.instances, inst);
|
||||||
array_add(*chunk.groups, group);
|
array_add(*chunk.groups, group);
|
||||||
}
|
}
|
||||||
invalidate_buried_around(*curworld.world, x, y, z);
|
|
||||||
} @Command
|
} @Command
|
||||||
|
|
||||||
remove_trile :: (x: s32, y: s32, z: s32) {
|
remove_trile :: (x: s32, y: s32, z: s32) {
|
||||||
@ -509,140 +498,18 @@ remove_trile :: (x: s32, y: s32, z: s32) {
|
|||||||
|
|
||||||
lx, ly, lz := world_to_local(x, y, z);
|
lx, ly, lz := world_to_local(x, y, z);
|
||||||
|
|
||||||
removed := false;
|
|
||||||
for *group: chunk.groups {
|
for *group: chunk.groups {
|
||||||
for inst, idx: group.instances {
|
for inst, idx: group.instances {
|
||||||
if inst.x == lx && inst.y == ly && inst.z == lz {
|
if inst.x == lx && inst.y == ly && inst.z == lz {
|
||||||
array_unordered_remove_by_index(*group.instances, idx);
|
array_unordered_remove_by_index(*group.instances, idx);
|
||||||
if group.is_buried.count > idx {
|
return;
|
||||||
array_unordered_remove_by_index(*group.is_buried, idx);
|
|
||||||
}
|
|
||||||
removed = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if removed break;
|
|
||||||
}
|
}
|
||||||
if removed invalidate_buried_around(*curworld.world, x, y, z);
|
|
||||||
} @Command
|
} @Command
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
level_editor_clear_entity_selection :: () {
|
|
||||||
entity_selected_id = -1;
|
|
||||||
entity_gizmo.hover = .NONE;
|
|
||||||
entity_gizmo.active = .NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_selected_entity :: (world: *World) -> *Entity {
|
|
||||||
if entity_selected_id < 0 then return null;
|
|
||||||
for e: world.entities if cast(s64)e.id == entity_selected_id return e;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// World-space box covering an entity's cell plus every part it draws, used for
|
|
||||||
// click-picking and for the selection outline.
|
|
||||||
entity_bounds :: (e: *Entity) -> (mn: Vector3, mx: Vector3) {
|
|
||||||
mn := e.position;
|
|
||||||
mx := e.position + Vector3.{1, 1, 1};
|
|
||||||
for part: get_entity_parts(e.type) {
|
|
||||||
p, q : Vector3 = ---;
|
|
||||||
if part.kind == .TRILE {
|
|
||||||
p = entity_trile_point(e, part.offset);
|
|
||||||
q = p + Vector3.{1, 1, 1};
|
|
||||||
} else {
|
|
||||||
// Billboards and emitters are points; give them enough of a box to click.
|
|
||||||
c := entity_point(e, part.offset);
|
|
||||||
p = c - Vector3.{0.25, 0.25, 0.25};
|
|
||||||
q = c + Vector3.{0.25, 0.25, 0.25};
|
|
||||||
}
|
|
||||||
mn.x = min(mn.x, p.x); mn.y = min(mn.y, p.y); mn.z = min(mn.z, p.z);
|
|
||||||
mx.x = max(mx.x, q.x); mx.y = max(mx.y, q.y); mx.z = max(mx.z, q.z);
|
|
||||||
}
|
|
||||||
return mn, mx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The billboard that stands in for an entity in the editor: the type's declared
|
|
||||||
// EDITOR_MARKER, or a default one when the entity draws nothing you could aim
|
|
||||||
// at. Null for entities that are already visible on their own.
|
|
||||||
entity_marker_animation :: (e: *Entity) -> *Animation {
|
|
||||||
marker := get_entity_editor_marker(e.type);
|
|
||||||
if marker.count > 0 return get_animation_from_string(marker);
|
|
||||||
for part: get_entity_parts(e.type) {
|
|
||||||
if part.kind == .TRILE || part.kind == .BILLBOARD return null;
|
|
||||||
}
|
|
||||||
return get_animation_from_string("game_core.ball");
|
|
||||||
}
|
|
||||||
|
|
||||||
pick_entity :: (world: *World, ray: Ray) -> *Entity {
|
|
||||||
best : *Entity = null;
|
|
||||||
best_dist : float = FLOAT32_MAX;
|
|
||||||
for e: world.entities {
|
|
||||||
mn, mx := entity_bounds(e);
|
|
||||||
col := does_ray_hit_cube(ray, .{position = mn, size = mx - mn});
|
|
||||||
if !col.hit then continue;
|
|
||||||
d := abs(col.distance);
|
|
||||||
if d < best_dist {
|
|
||||||
best_dist = d;
|
|
||||||
best = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
snap_to_step :: (v: float, step: float) -> float {
|
|
||||||
if step <= 0 then return v;
|
|
||||||
return floor(v / step + 0.5) * step;
|
|
||||||
}
|
|
||||||
|
|
||||||
tick_entity_tool :: (ray: Ray) {
|
|
||||||
curworld := get_current_world();
|
|
||||||
if !curworld.valid then return;
|
|
||||||
world := *curworld.world;
|
|
||||||
|
|
||||||
e := get_selected_entity(world);
|
|
||||||
if e == null {
|
|
||||||
entity_gizmo.hover = .NONE;
|
|
||||||
entity_gizmo.active = .NONE;
|
|
||||||
} else {
|
|
||||||
mn, mx := entity_bounds(e);
|
|
||||||
debug_aabb_3d_overlay(mn, mx, .{1.0, 0.75, 0.1, 0.8}, 2.0);
|
|
||||||
|
|
||||||
// The gizmo sits at the middle of the entity's cell, which is both what
|
|
||||||
// the entity's marker draws on and what it rotates about.
|
|
||||||
cam := get_level_editor_camera();
|
|
||||||
origin := e.position + ENTITY_PIVOT;
|
|
||||||
if entity_gizmo_mode == .MOVE {
|
|
||||||
moved, changed := gizmo_translate(*entity_gizmo, origin, cam, ray);
|
|
||||||
if changed {
|
|
||||||
p := moved - ENTITY_PIVOT;
|
|
||||||
if entity_snap_enabled {
|
|
||||||
p.x = snap_to_step(p.x, entity_snap_step);
|
|
||||||
p.y = snap_to_step(p.y, entity_snap_step);
|
|
||||||
p.z = snap_to_step(p.z, entity_snap_step);
|
|
||||||
}
|
|
||||||
e.position = p;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
u, v, n, steps := gizmo_rotate(*entity_gizmo, origin, cam, ray);
|
|
||||||
if steps != 0 {
|
|
||||||
// A negative drag is the same turn taken the other way round.
|
|
||||||
turn := ifx steps > 0 then orientation_quarter_turn(u, v, n) else orientation_quarter_turn(v, u, n);
|
|
||||||
for 1..abs(steps) e.orientation = compose_orientations(turn, e.orientation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for *inst: e.emitters inst.position = entity_point(e, inst.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A click that didn't land on the gizmo selects (or clears) the selection.
|
|
||||||
if entity_gizmo.active == .NONE && entity_gizmo.hover == .NONE {
|
|
||||||
if get_mouse_state(Key_Code.MOUSE_BUTTON_LEFT) & .START {
|
|
||||||
picked := pick_entity(world, ray);
|
|
||||||
entity_selected_id = ifx picked then cast(s64)picked.id else cast(s64)-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editor_edit_y :: () -> float {
|
editor_edit_y :: () -> float {
|
||||||
y := cast(float)editY;
|
y := cast(float)editY;
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
@ -658,9 +525,6 @@ tick_level_editor :: () {
|
|||||||
tick_level_editor_camera();
|
tick_level_editor_camera();
|
||||||
tick_particles(cast(float)delta_time);
|
tick_particles(cast(float)delta_time);
|
||||||
|
|
||||||
curworld := get_current_world();
|
|
||||||
if curworld.valid then tick_entity_emitters(*curworld.world, cast(float)delta_time);
|
|
||||||
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TWIST_CCW) {
|
if is_action_start(Editor_Action.LEVEL_TWIST_CCW) {
|
||||||
lastInputTime = get_time();
|
lastInputTime = get_time();
|
||||||
current_orientation_twist = (current_orientation_twist + 1) % 4;
|
current_orientation_twist = (current_orientation_twist + 1) % 4;
|
||||||
@ -674,14 +538,9 @@ tick_level_editor :: () {
|
|||||||
current_orientation_face = (current_orientation_face + 1) % 6;
|
current_orientation_face = (current_orientation_face + 1) % 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
cam := get_level_editor_camera();
|
ray := get_mouse_ray(*get_level_editor_camera());
|
||||||
ray := get_mouse_ray(*cam);
|
|
||||||
hit, point := ray_plane_collision_point(ray, editor_edit_y(), 20);
|
hit, point := ray_plane_collision_point(ray, editor_edit_y(), 20);
|
||||||
|
|
||||||
// The entity tool works off the entities themselves, not the edit plane, so
|
|
||||||
// it runs whether or not the cursor is over the plane.
|
|
||||||
if current_tool_mode == .ENTITY then tick_entity_tool(ray);
|
|
||||||
|
|
||||||
show_trile_preview = false;
|
show_trile_preview = false;
|
||||||
if hit {
|
if hit {
|
||||||
show_trile_preview = true;
|
show_trile_preview = true;
|
||||||
@ -773,12 +632,6 @@ tick_level_editor :: () {
|
|||||||
inspector_z = cast(s32)pz;
|
inspector_z = cast(s32)pz;
|
||||||
inspector_note_deactivate = true;
|
inspector_note_deactivate = true;
|
||||||
}
|
}
|
||||||
} else if current_tool_mode == .ENTITY {
|
|
||||||
// Right-click places the type picked in the panel, so the cursor never
|
|
||||||
// has to leave the spot you're aiming at.
|
|
||||||
if get_mouse_state(Key_Code.MOUSE_BUTTON_RIGHT) & .START {
|
|
||||||
add_entity_at(cast(s32)px, cast(s32)py, cast(s32)pz);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -789,24 +642,19 @@ create_level_editor_preview_tasks :: () {
|
|||||||
|
|
||||||
px := trile_preview_x; py := trile_preview_y; pz := trile_preview_z;
|
px := trile_preview_x; py := trile_preview_y; pz := trile_preview_z;
|
||||||
|
|
||||||
// The entity tool never paints triles, so it shows no brush preview.
|
|
||||||
if current_tool_mode == .ENTITY then return;
|
|
||||||
|
|
||||||
if current_tool_mode == .INSPECTOR {
|
if current_tool_mode == .INSPECTOR {
|
||||||
name, orientation, found := get_trile_at(*curworld.world, xx px, xx py, xx pz);
|
name, orientation, found := get_trile_at(*curworld.world, xx px, xx py, xx pz);
|
||||||
cursor_trile := ifx found then name else (ifx editor_current_trile then editor_current_trile.name else "");
|
cursor_trile := ifx found then name else (ifx editor_current_trile then editor_current_trile.name else "");
|
||||||
if cursor_trile != "" {
|
if cursor_trile != "" {
|
||||||
ck := world_to_chunk_coord(xx px, xx py, xx pz);
|
|
||||||
lx, ly, lz := world_to_local(xx px, xx py, xx pz);
|
|
||||||
positions: [..]Trile_Instance;
|
positions: [..]Trile_Instance;
|
||||||
positions.allocator = temp;
|
positions.allocator = temp;
|
||||||
array_add(*positions, Trile_Instance.{lx, ly, lz, xx orientation});
|
array_add(*positions, Trile_Instance.{xx (px % 32), xx (py % 32), xx (pz % 32), xx orientation});
|
||||||
task: Rendering_Task_Trile;
|
task: Rendering_Task_Trile;
|
||||||
task.trile = cursor_trile;
|
task.trile = cursor_trile;
|
||||||
task.positions = positions;
|
task.positions = positions;
|
||||||
task.worldConf = *curworld.world.conf;
|
task.worldConf = *curworld.world.conf;
|
||||||
task.preview_mode = 1;
|
task.preview_mode = 1;
|
||||||
task.chunk_key = ck;
|
task.chunk_key = world_to_chunk_coord(xx px, xx py, xx pz);
|
||||||
add_rendering_task(task);
|
add_rendering_task(task);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -816,8 +664,7 @@ create_level_editor_preview_tasks :: () {
|
|||||||
|
|
||||||
positions: [..]Vector4;
|
positions: [..]Vector4;
|
||||||
positions.allocator = temp;
|
positions.allocator = temp;
|
||||||
ori_u8 := get_current_orientation();
|
ori := cast(float) get_current_orientation();
|
||||||
ori := cast(float) ori_u8;
|
|
||||||
|
|
||||||
if current_tool_mode == .POINT {
|
if current_tool_mode == .POINT {
|
||||||
array_add(*positions, .{cast(float)px, cast(float)py, cast(float)pz, ori});
|
array_add(*positions, .{cast(float)px, cast(float)py, cast(float)pz, ori});
|
||||||
@ -873,37 +720,12 @@ create_level_editor_preview_tasks :: () {
|
|||||||
is_delete := (current_tool_mode == .AREA && area_active && area_delete)
|
is_delete := (current_tool_mode == .AREA && area_active && area_delete)
|
||||||
|| (current_tool_mode == .LINE && line_active && line_delete);
|
|| (current_tool_mode == .LINE && line_active && line_delete);
|
||||||
|
|
||||||
chunk_groups: [..] struct { key: Chunk_Key; instances: [..]Trile_Instance; };
|
task: Rendering_Task_Trile;
|
||||||
chunk_groups.allocator = temp;
|
task.trile = editor_current_trile.name;
|
||||||
for pos : positions {
|
// task.positions = positions;
|
||||||
wx := cast(s32) pos.x; wy := cast(s32) pos.y; wz := cast(s32) pos.z;
|
task.worldConf = *curworld.world.conf;
|
||||||
ck := world_to_chunk_coord(wx, wy, wz);
|
task.preview_mode = ifx is_delete then cast(s32)2 else cast(s32)1;
|
||||||
lx, ly, lz := world_to_local(wx, wy, wz);
|
add_rendering_task(task);
|
||||||
found := false;
|
|
||||||
for *grp : chunk_groups {
|
|
||||||
if grp.key == ck {
|
|
||||||
array_add(*grp.instances, Trile_Instance.{lx, ly, lz, ori_u8});
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
instances: [..]Trile_Instance;
|
|
||||||
instances.allocator = temp;
|
|
||||||
array_add(*instances, Trile_Instance.{lx, ly, lz, ori_u8});
|
|
||||||
array_add(*chunk_groups, .{ck, instances});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for grp : chunk_groups {
|
|
||||||
task: Rendering_Task_Trile;
|
|
||||||
task.trile = editor_current_trile.name;
|
|
||||||
task.positions = grp.instances;
|
|
||||||
task.worldConf = *curworld.world.conf;
|
|
||||||
task.chunk_key = grp.key;
|
|
||||||
task.preview_mode = ifx is_delete then cast(s32)2 else cast(s32)1;
|
|
||||||
add_rendering_task(task);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_trile_at :: (world: *World, wx: s32, wy: s32, wz: s32) -> (name: string, orientation: u8, found: bool) {
|
get_trile_at :: (world: *World, wx: s32, wy: s32, wz: s32) -> (name: string, orientation: u8, found: bool) {
|
||||||
@ -1130,131 +952,11 @@ draw_inspector_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_entity_tool_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
|
||||||
curworld := get_current_world();
|
|
||||||
if !curworld.valid then return;
|
|
||||||
world := *curworld.world;
|
|
||||||
|
|
||||||
r.h = ui_h(3, 2);
|
|
||||||
GR.label(r.*, "-- Entity Tool --", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
|
|
||||||
e := get_selected_entity(world);
|
|
||||||
if e != null {
|
|
||||||
GR.label(r.*, tprint("Type: %", entity_type_name(e.type)), *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
GR.label(r.*, tprint("Pos: %, %, %",
|
|
||||||
formatFloat(e.position.x, trailing_width=2),
|
|
||||||
formatFloat(e.position.y, trailing_width=2),
|
|
||||||
formatFloat(e.position.z, trailing_width=2)), *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
|
|
||||||
GR.label(r.*, tprint("Orientation: %", e.orientation), *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
|
|
||||||
r.h = ui_h(4, 0);
|
|
||||||
mode_r := r.*;
|
|
||||||
mode_r.w = r.w / 2;
|
|
||||||
if GR.button(mode_r, "Move", *t_button_selectable(theme, entity_gizmo_mode == .MOVE), 524) {
|
|
||||||
entity_gizmo_mode = .MOVE;
|
|
||||||
entity_gizmo.active = .NONE;
|
|
||||||
}
|
|
||||||
mode_r.x += mode_r.w;
|
|
||||||
if GR.button(mode_r, "Rotate", *t_button_selectable(theme, entity_gizmo_mode == .ROTATE), 525) {
|
|
||||||
entity_gizmo_mode = .ROTATE;
|
|
||||||
entity_gizmo.active = .NONE;
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
|
|
||||||
// Rotation is always in quarter turns, so snapping is a move-only setting.
|
|
||||||
if entity_gizmo_mode == .MOVE {
|
|
||||||
snap_label := ifx entity_snap_enabled then tprint("Snap: %", formatFloat(entity_snap_step, trailing_width=2)) else "Snap: off";
|
|
||||||
if GR.button(r.*, snap_label, *t_button_selectable(theme, entity_snap_enabled), 520) {
|
|
||||||
entity_snap_enabled = !entity_snap_enabled;
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
if entity_snap_enabled {
|
|
||||||
GR.slider(r.*, *entity_snap_step, 0.125, 1.0, 0.125, *theme.slider_theme);
|
|
||||||
r.y += r.h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fields_r := r.*;
|
|
||||||
fields_r.h = ui_h(30, 0);
|
|
||||||
entity_autoedit(fields_r, e, theme);
|
|
||||||
r.y += fields_r.h;
|
|
||||||
|
|
||||||
if GR.button(r.*, "Remove Entity", *theme.button_theme, 521) {
|
|
||||||
remove_entity(world, e);
|
|
||||||
level_editor_clear_entity_selection();
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
if GR.button(r.*, "Deselect", *theme.button_theme, 522) {
|
|
||||||
level_editor_clear_entity_selection();
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
} else {
|
|
||||||
GR.label(r.*, "Click an entity to select it.", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
GR.label(r.*, "Drag the gizmo to move or turn.", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
}
|
|
||||||
|
|
||||||
r.h = ui_h(3, 2);
|
|
||||||
r.y += r.h * 0.5;
|
|
||||||
GR.label(r.*, "-- Spawn --", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
|
|
||||||
r.h = ui_h(4, 0);
|
|
||||||
for info, idx: ENTITY_TYPE_TABLE {
|
|
||||||
selected := cast(s32)idx == entity_add_type;
|
|
||||||
if GR.button(r.*, info.name, *t_button_selectable(theme, selected), cast(s32)(500 + idx)) {
|
|
||||||
entity_add_type = cast(s32)idx;
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
}
|
|
||||||
r.h = ui_h(3, 2);
|
|
||||||
if entity_add_type >= 0 && entity_add_type < cast(s32)ENTITY_TYPE_TABLE.count {
|
|
||||||
GR.label(r.*, "Right-click in the world to place.", *t_label_left(theme));
|
|
||||||
} else {
|
|
||||||
GR.label(r.*, "Pick a type to place.", *t_label_left(theme));
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Places the type picked in the panel. Called from the right-click handler, so
|
|
||||||
// the position is whatever cell the cursor is over on the edit plane.
|
|
||||||
add_entity_at :: (x: s32, y: s32, z: s32) {
|
|
||||||
if entity_add_type < 0 || entity_add_type >= cast(s32)ENTITY_TYPE_TABLE.count then return;
|
|
||||||
curworld := get_current_world();
|
|
||||||
if !curworld.valid then return;
|
|
||||||
spawned := spawn_entity(*curworld.world, ENTITY_TYPE_TABLE[entity_add_type].name, .{cast(float)x, cast(float)y, cast(float)z});
|
|
||||||
if spawned then entity_selected_id = cast(s64)spawned.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runs autoedit on the entity's concrete type, so each type gets an editor
|
|
||||||
// generated from its own fields.
|
|
||||||
entity_autoedit :: (rect: GR.Rect, e: *Entity, theme: *GR.Overall_Theme) {
|
|
||||||
#insert #run,stallable gen_entity_dispatch("autoedit(rect, cast(*%)e, theme, 400)");
|
|
||||||
}
|
|
||||||
|
|
||||||
add_editor_billboards :: () {
|
add_editor_billboards :: () {
|
||||||
if !editor_billboards_visible then return;
|
if !editor_billboards_visible then return;
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
if !curworld.valid then return;
|
if !curworld.valid then return;
|
||||||
|
|
||||||
// Entities are marked first, before the fallback animation is required, so a
|
|
||||||
// type with its own EDITOR_MARKER still shows up if "game_core.ball" is gone.
|
|
||||||
for e: curworld.world.entities {
|
|
||||||
marker := entity_marker_animation(e);
|
|
||||||
if marker == null then continue;
|
|
||||||
task : Rendering_Task_Billboard;
|
|
||||||
task.position = e.position + ENTITY_PIVOT;
|
|
||||||
task.animation = marker;
|
|
||||||
task.frame = 0;
|
|
||||||
add_rendering_task(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
anim := get_animation_from_string("game_core.ball");
|
anim := get_animation_from_string("game_core.ball");
|
||||||
if anim == null then return;
|
if anim == null then return;
|
||||||
|
|
||||||
@ -1282,7 +984,6 @@ draw_level_editor :: () {
|
|||||||
ph := effective_plane_height(*curworld.world.conf);
|
ph := effective_plane_height(*curworld.world.conf);
|
||||||
create_set_cam_rendering_task(cam, ph);
|
create_set_cam_rendering_task(cam, ph);
|
||||||
create_world_rendering_tasks(*curworld.world, cam, ph);
|
create_world_rendering_tasks(*curworld.world, cam, ph);
|
||||||
add_entity_render_tasks(*curworld.world);
|
|
||||||
if show_trile_preview && !trile_preview_disabled {
|
if show_trile_preview && !trile_preview_disabled {
|
||||||
create_level_editor_preview_tasks();
|
create_level_editor_preview_tasks();
|
||||||
}
|
}
|
||||||
@ -1314,14 +1015,12 @@ draw_level_editor_ui :: (theme: *GR.Overall_Theme) {
|
|||||||
case .INFO;
|
case .INFO;
|
||||||
if curworld.valid then autoedit(r, *curworld.world.conf, theme);
|
if curworld.valid then autoedit(r, *curworld.world.conf, theme);
|
||||||
}
|
}
|
||||||
if current_tool_mode == .INSPECTOR || current_tool_mode == .ENTITY {
|
if current_tool_mode == .INSPECTOR {
|
||||||
rr := GR.get_rect(ui_w(85,0), ui_h(5,0), ui_w(15, 0), ui_h(95, 0));
|
rr := GR.get_rect(ui_w(85,0), ui_h(5,0), ui_w(15, 0), ui_h(95, 0));
|
||||||
draw_bg_rectangle(rr, theme);
|
draw_bg_rectangle(rr, theme);
|
||||||
ui_add_mouse_occluder(rr);
|
ui_add_mouse_occluder(rr);
|
||||||
rr.y += ui_h(1, 0);
|
rr.y += ui_h(1, 0);
|
||||||
if current_tool_mode == .ENTITY {
|
if inspector_selected {
|
||||||
draw_entity_tool_panel(*rr, theme);
|
|
||||||
} else if inspector_selected {
|
|
||||||
draw_inspector_panel(*rr, theme);
|
draw_inspector_panel(*rr, theme);
|
||||||
} else {
|
} else {
|
||||||
rr.h = ui_h(3, 2);
|
rr.h = ui_h(3, 2);
|
||||||
|
|||||||
@ -164,11 +164,7 @@ draw_particle_editor_ui :: (theme: *GR.Overall_Theme) {
|
|||||||
|
|
||||||
er.y += er.h;
|
er.y += er.h;
|
||||||
|
|
||||||
alive_count : s32 = 0;
|
pe_label(*er, tprint("Alive particles: %", count_alive_particles()), theme);
|
||||||
for p : g_particles {
|
|
||||||
if p.alive then alive_count += 1;
|
|
||||||
}
|
|
||||||
pe_label(*er, tprint("Alive particles: %", alive_count), theme);
|
|
||||||
pe_label(*er, tprint("Emitter defs: %", g_emitter_defs.count), theme);
|
pe_label(*er, tprint("Emitter defs: %", g_emitter_defs.count), theme);
|
||||||
pe_label(*er, tprint("Preview active: %", pe_preview_emitter.active), theme);
|
pe_label(*er, tprint("Preview active: %", pe_preview_emitter.active), theme);
|
||||||
pe_label(*er, tprint("Preview def: %", pe_preview_emitter.definition != null), theme);
|
pe_label(*er, tprint("Preview def: %", pe_preview_emitter.definition != null), theme);
|
||||||
|
|||||||
@ -218,7 +218,7 @@ trile_copy :: (from: string) {
|
|||||||
tick_trile_editor :: () {
|
tick_trile_editor :: () {
|
||||||
if console_open_ignore_input then return;
|
if console_open_ignore_input then return;
|
||||||
|
|
||||||
if !editor_current_trile then editor_current_trile = get_any_trile();
|
if !editor_current_trile then editor_current_trile = get_trile("test");
|
||||||
|
|
||||||
if input_button_states[Key_Code.MOUSE_BUTTON_LEFT] & .START {
|
if input_button_states[Key_Code.MOUSE_BUTTON_LEFT] & .START {
|
||||||
handle_tool_click();
|
handle_tool_click();
|
||||||
|
|||||||
408
src/entities.jai
408
src/entities.jai
@ -1,408 +0,0 @@
|
|||||||
// Entity types are plain structs declared in game code, registered with an
|
|
||||||
// @Entity note the way console commands register with @Command. The metaprogram
|
|
||||||
// (src/meta/entity_types.jai) collects them into ENTITY_TYPES, and spawning,
|
|
||||||
// dispatch, editor UI and saving all come from that. Marking a struct @Entity
|
|
||||||
// without embedding Entity, or embedding Entity without the note, is an error.
|
|
||||||
//
|
|
||||||
// Foo :: struct {
|
|
||||||
// #as using base : Entity;
|
|
||||||
// base.type = Foo;
|
|
||||||
// PARTS :: Entity_Part.[...]; // visuals, may be empty
|
|
||||||
// EDITOR_MARKER :: "pack.animation"; // optional, editor-only billboard
|
|
||||||
// } @Entity
|
|
||||||
// entity_tick :: (using foo: *Foo, dt: float) {} // optional
|
|
||||||
// entity_draw :: (foo: *Foo) {} // optional
|
|
||||||
//
|
|
||||||
// Fields of type float, s32, bool, string or Vector3 are editable and saved;
|
|
||||||
// @Slider / @Color notes pick the editor widget.
|
|
||||||
//
|
|
||||||
// Each frame the game calls tick_entities, then tick_entity_emitters, then
|
|
||||||
// add_entity_render_tasks. The level editor calls the last two only: it shows
|
|
||||||
// entities and their particles without running their game logic.
|
|
||||||
|
|
||||||
#scope_export
|
|
||||||
|
|
||||||
Entity :: struct {
|
|
||||||
type : Type;
|
|
||||||
id : u32;
|
|
||||||
position : Vector3;
|
|
||||||
orientation : u8; // 0..23, see orientation.jai
|
|
||||||
emitters : [..]Particle_Emitter_Instance; // built from PARTS, not saved
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity_Part_Kind :: enum {
|
|
||||||
TRILE;
|
|
||||||
BILLBOARD;
|
|
||||||
EMITTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity_Part :: struct {
|
|
||||||
kind : Entity_Part_Kind;
|
|
||||||
trile : string; // TRILE
|
|
||||||
orientation : u8; // TRILE, 0..23
|
|
||||||
animation : string; // BILLBOARD, e.g. "game_core.ball"
|
|
||||||
emitter : string; // EMITTER, a particle definition name
|
|
||||||
offset : Vector3;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity_Type_Info :: struct {
|
|
||||||
name : string;
|
|
||||||
type : Type;
|
|
||||||
parts : []Entity_Part;
|
|
||||||
editor_marker : string;
|
|
||||||
create : () -> *Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
#insert #run,stallable gen_entity_type_table();
|
|
||||||
|
|
||||||
get_entity_type_info :: (type: Type) -> *Entity_Type_Info {
|
|
||||||
for * ENTITY_TYPE_TABLE if it.type == type return it;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_entity_parts :: (type: Type) -> []Entity_Part {
|
|
||||||
info := get_entity_type_info(type);
|
|
||||||
return ifx info then info.parts else .[];
|
|
||||||
}
|
|
||||||
|
|
||||||
get_entity_editor_marker :: (type: Type) -> string {
|
|
||||||
info := get_entity_type_info(type);
|
|
||||||
return ifx info then info.editor_marker else "";
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_type_name :: (type: Type) -> string {
|
|
||||||
return (cast(*Type_Info_Struct) type).name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entities turn about the middle of their own cell, so a rotated entity stays
|
|
||||||
// on the block it was placed on.
|
|
||||||
ENTITY_PIVOT :: Vector3.{0.5, 0.5, 0.5};
|
|
||||||
|
|
||||||
entity_point :: (e: *Entity, offset: Vector3) -> Vector3 {
|
|
||||||
return e.position + ENTITY_PIVOT + rotate_by_orientation(e.orientation, offset - ENTITY_PIVOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A trile part fills a cell rather than being a point, so the pivot cancels out.
|
|
||||||
entity_trile_point :: (e: *Entity, offset: Vector3) -> Vector3 {
|
|
||||||
return e.position + rotate_by_orientation(e.orientation, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Orientation is taken up front rather than assigned afterwards because the
|
|
||||||
// emitters are placed relative to it.
|
|
||||||
spawn_entity :: (world: *World, type_name: string, position: Vector3, orientation: u8 = 0) -> *Entity {
|
|
||||||
for ENTITY_TYPE_TABLE {
|
|
||||||
if it.name != type_name continue;
|
|
||||||
e := it.create();
|
|
||||||
e.position = position;
|
|
||||||
e.orientation = orientation;
|
|
||||||
e.id = world.next_entity_id;
|
|
||||||
world.next_entity_id += 1;
|
|
||||||
init_entity_emitters(e);
|
|
||||||
array_add(*world.entities, e);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
log_error("Unknown entity type: %", type_name);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_first_entity :: (world: *World, type: Type) -> *Entity {
|
|
||||||
for e: world.entities if e.type == type return e;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_entity :: (world: *World, e: *Entity) {
|
|
||||||
for other, i: world.entities {
|
|
||||||
if other != e continue;
|
|
||||||
array_ordered_remove_by_index(*world.entities, i);
|
|
||||||
free_entity(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free_entity :: (e: *Entity) {
|
|
||||||
array_free(e.emitters);
|
|
||||||
free(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
free_all_entities :: (world: *World) {
|
|
||||||
for e: world.entities free_entity(e);
|
|
||||||
array_reset(*world.entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concrete per-type overloads in game code take precedence over these.
|
|
||||||
entity_tick :: (e: *$T/Entity, dt: float) {}
|
|
||||||
entity_draw :: (e: *$T/Entity) { draw_entity_parts(e); }
|
|
||||||
|
|
||||||
tick_entities :: (world: *World, dt: float) {
|
|
||||||
for e: world.entities entity_dispatch_tick(e, dt);
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_dispatch_tick :: (e: *Entity, dt: float) {
|
|
||||||
#insert #run,stallable gen_entity_dispatch("entity_tick(cast(*%)e, dt)");
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_dispatch_draw :: (e: *Entity) {
|
|
||||||
#insert #run,stallable gen_entity_dispatch("entity_draw(cast(*%)e)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatches a call on the entity's concrete type. '%' in 'call' becomes the type
|
|
||||||
// name. Also used by the editor to run autoedit on an entity's own fields.
|
|
||||||
gen_entity_dispatch :: (call: string) -> string {
|
|
||||||
builder : String_Builder;
|
|
||||||
for ENTITY_TYPES {
|
|
||||||
name := (cast(*Type_Info_Struct) it).name;
|
|
||||||
print_to_builder(*builder, "if e.type == % { %; return; }\n", name, tprint(call, name));
|
|
||||||
}
|
|
||||||
return builder_to_string(*builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
init_entity_emitters :: (e: *Entity) {
|
|
||||||
for part: get_entity_parts(e.type) {
|
|
||||||
if part.kind != .EMITTER continue;
|
|
||||||
inst : Particle_Emitter_Instance;
|
|
||||||
inst.definition_name = part.emitter;
|
|
||||||
inst.definition = get_emitter_def(part.emitter);
|
|
||||||
inst.offset = part.offset;
|
|
||||||
inst.position = entity_point(e, part.offset);
|
|
||||||
inst.active = true;
|
|
||||||
array_add(*e.emitters, inst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tick_entity_emitters :: (world: *World, dt: float) {
|
|
||||||
for e: world.entities {
|
|
||||||
for *inst: e.emitters {
|
|
||||||
if inst.definition == null {
|
|
||||||
inst.definition = get_emitter_def(inst.definition_name);
|
|
||||||
if inst.definition == null continue;
|
|
||||||
}
|
|
||||||
inst.position = entity_point(e, inst.offset);
|
|
||||||
tick_emitter_instance(inst, dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once per frame, from the game and from the editor. Trile draws are batched per
|
|
||||||
// (trile, chunk) and flushed as TRILE_DYNAMIC tasks at the end.
|
|
||||||
add_entity_render_tasks :: (world: *World) {
|
|
||||||
array_reset_keeping_memory(*entity_trile_batches);
|
|
||||||
for e: world.entities entity_dispatch_draw(e);
|
|
||||||
for *batch: entity_trile_batches {
|
|
||||||
task : Rendering_Task_Trile_Dynamic;
|
|
||||||
task.trile = batch.trile;
|
|
||||||
task.positions = batch.positions;
|
|
||||||
task.chunk_key = batch.chunk_key;
|
|
||||||
task.worldConf = *world.conf;
|
|
||||||
add_rendering_task(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_draw_trile :: (e: *Entity, trile: string, offset: Vector3, orientation: u8 = 0) {
|
|
||||||
pos := entity_trile_point(e, offset);
|
|
||||||
ori := compose_orientations(e.orientation, orientation);
|
|
||||||
key := world_to_chunk_coord(cast(s32) floor(pos.x), cast(s32) floor(pos.y), cast(s32) floor(pos.z));
|
|
||||||
instance := Vector4.{pos.x, pos.y, pos.z, cast(float) ori};
|
|
||||||
|
|
||||||
for *batch: entity_trile_batches {
|
|
||||||
if batch.trile == trile && batch.chunk_key == key {
|
|
||||||
array_add(*batch.positions, instance);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
batch : Entity_Trile_Batch;
|
|
||||||
batch.trile = trile;
|
|
||||||
batch.chunk_key = key;
|
|
||||||
batch.positions.allocator = temp;
|
|
||||||
array_add(*batch.positions, instance);
|
|
||||||
array_add(*entity_trile_batches, batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_draw_billboard :: (e: *Entity, animation: string, offset: Vector3, frame: s32 = 0) {
|
|
||||||
anim := get_animation_from_string(animation);
|
|
||||||
if anim == null return;
|
|
||||||
task : Rendering_Task_Billboard;
|
|
||||||
task.position = entity_point(e, offset);
|
|
||||||
task.animation = anim;
|
|
||||||
task.frame = frame;
|
|
||||||
add_rendering_task(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_entity_parts :: (e: *Entity) {
|
|
||||||
for part: get_entity_parts(e.type) {
|
|
||||||
if part.kind == {
|
|
||||||
case .TRILE; entity_draw_trile(e, part.trile, part.offset, part.orientation);
|
|
||||||
case .BILLBOARD; entity_draw_billboard(e, part.animation, part.offset);
|
|
||||||
case .EMITTER; // tick_entity_emitters handles these
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields are saved as name/value string pairs rather than typed JSON, so adding,
|
|
||||||
// removing or renaming one never breaks loading an older world.
|
|
||||||
Entity_Field :: struct {
|
|
||||||
name : string;
|
|
||||||
value : string;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_fields_to_strings :: (e: *Entity) -> []Entity_Field {
|
|
||||||
result : [..]Entity_Field;
|
|
||||||
result.allocator = temp;
|
|
||||||
for member: entity_saved_members(e.type) {
|
|
||||||
ptr := (cast(*u8) e) + member.offset_in_bytes;
|
|
||||||
value : string;
|
|
||||||
if member.type == type_info(float) value = tprint("%", (cast(*float) ptr).*);
|
|
||||||
else if member.type == type_info(s32) value = tprint("%", (cast(*s32) ptr).*);
|
|
||||||
else if member.type == type_info(bool) value = tprint("%", (cast(*bool) ptr).*);
|
|
||||||
else if member.type == type_info(string) value = (cast(*string) ptr).*;
|
|
||||||
else if member.type == type_info(Vector3) {
|
|
||||||
v := (cast(*Vector3) ptr).*;
|
|
||||||
value = tprint("% % %", v.x, v.y, v.z);
|
|
||||||
} else continue;
|
|
||||||
array_add(*result, .{member.name, value});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_apply_field :: (e: *Entity, name: string, value: string) {
|
|
||||||
for member: entity_saved_members(e.type) {
|
|
||||||
if member.name != name continue;
|
|
||||||
ptr := (cast(*u8) e) + member.offset_in_bytes;
|
|
||||||
if member.type == type_info(float) {
|
|
||||||
v, ok := string_to_float(value);
|
|
||||||
if ok { (cast(*float) ptr).* = v; }
|
|
||||||
} else if member.type == type_info(s32) {
|
|
||||||
v, ok := string_to_int(value);
|
|
||||||
if ok { (cast(*s32) ptr).* = cast(s32) v; }
|
|
||||||
} else if member.type == type_info(bool) {
|
|
||||||
(cast(*bool) ptr).* = (value == "true");
|
|
||||||
} else if member.type == type_info(string) {
|
|
||||||
(cast(*string) ptr).* = copy_string(value);
|
|
||||||
} else if member.type == type_info(Vector3) {
|
|
||||||
parts := split(value, " ",, temp);
|
|
||||||
if parts.count != 3 return;
|
|
||||||
v : Vector3;
|
|
||||||
x, okx := string_to_float(parts[0]);
|
|
||||||
y, oky := string_to_float(parts[1]);
|
|
||||||
z, okz := string_to_float(parts[2]);
|
|
||||||
if okx && oky && okz { (cast(*Vector3) ptr).* = .{x, y, z}; }
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log_warn("Entity type % has no field '%', skipping", entity_type_name(e.type), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
#scope_file
|
|
||||||
|
|
||||||
#import "String";
|
|
||||||
|
|
||||||
// The members an entity type declares itself: constants and the Entity base are
|
|
||||||
// neither editable nor saved.
|
|
||||||
entity_saved_members :: (type: Type) -> []Type_Info_Struct_Member {
|
|
||||||
result : [..]Type_Info_Struct_Member;
|
|
||||||
result.allocator = temp;
|
|
||||||
for (cast(*Type_Info_Struct) type).members {
|
|
||||||
if it.flags & (.CONSTANT | .USING) continue;
|
|
||||||
array_add(*result, it);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity_Trile_Batch :: struct {
|
|
||||||
trile : string;
|
|
||||||
chunk_key : Chunk_Key;
|
|
||||||
positions : [..]Vector4;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_trile_batches : [..]Entity_Trile_Batch;
|
|
||||||
|
|
||||||
#if FLAG_TEST_ENGINE {
|
|
||||||
// The two placement helpers use different pivots, and getting them the wrong
|
|
||||||
// way round only shows up as parts drifting off a rotated entity, so both are
|
|
||||||
// pinned to the invariant they exist for.
|
|
||||||
test_entity_part_placement :: () {
|
|
||||||
s := begin_suite("entity part placement");
|
|
||||||
|
|
||||||
e : Entity;
|
|
||||||
e.position = .{10, 3, -5};
|
|
||||||
|
|
||||||
// A trile part on the entity's own cell has to stay on that cell however
|
|
||||||
// the entity is turned, or rotating would walk it off the block it was
|
|
||||||
// placed on.
|
|
||||||
on_its_block := true;
|
|
||||||
for ori: 0..ORIENTATION_COUNT-1 {
|
|
||||||
e.orientation = cast(u8) ori;
|
|
||||||
if entity_trile_point(*e, .{0, 0, 0}) != e.position on_its_block = false;
|
|
||||||
}
|
|
||||||
check(*s, "a trile part on the entity's own cell never moves", on_its_block);
|
|
||||||
|
|
||||||
// Point parts turn about the middle of that same cell, so a part sitting
|
|
||||||
// at the middle is the fixed point of all 24 rotations.
|
|
||||||
at_the_middle := true;
|
|
||||||
for ori: 0..ORIENTATION_COUNT-1 {
|
|
||||||
e.orientation = cast(u8) ori;
|
|
||||||
if entity_point(*e, ENTITY_PIVOT) != e.position + ENTITY_PIVOT at_the_middle = false;
|
|
||||||
}
|
|
||||||
check(*s, "a point part at the middle never moves", at_the_middle);
|
|
||||||
|
|
||||||
e.orientation = 0;
|
|
||||||
check(*s, "unturned point parts sit at their authored offset",
|
|
||||||
entity_point(*e, .{1, 2, 3}) == e.position + Vector3.{1, 2, 3});
|
|
||||||
check(*s, "unturned trile parts sit at their authored offset",
|
|
||||||
entity_trile_point(*e, .{1, 2, 3}) == e.position + Vector3.{1, 2, 3});
|
|
||||||
|
|
||||||
// Orientation 1 is the quarter twist about Y, which sends X to Z.
|
|
||||||
e.orientation = 1;
|
|
||||||
check(*s, "a turned entity carries its parts around with it",
|
|
||||||
entity_trile_point(*e, .{1, 0, 0}) == e.position + Vector3.{0, 0, 1});
|
|
||||||
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every declared type must reach the table, or it would be unspawnable and
|
|
||||||
// unsaveable with no diagnostic anywhere.
|
|
||||||
test_entity_type_table :: () {
|
|
||||||
s := begin_suite("entity type table");
|
|
||||||
check(*s, "the table covers every declared type", ENTITY_TYPE_TABLE.count == ENTITY_TYPES.count);
|
|
||||||
|
|
||||||
found_all := true;
|
|
||||||
for type: ENTITY_TYPES if get_entity_type_info(type) == null found_all = false;
|
|
||||||
check(*s, "every declared type is findable by type", found_all);
|
|
||||||
|
|
||||||
names_match := true;
|
|
||||||
for info: ENTITY_TYPE_TABLE if info.name != entity_type_name(info.type) names_match = false;
|
|
||||||
check(*s, "table names match the type names saves use", names_match);
|
|
||||||
|
|
||||||
// Entity itself is never a declared type, so it stands in for one that
|
|
||||||
// this build does not have.
|
|
||||||
check(*s, "an unknown type has no info", get_entity_type_info(Entity) == null);
|
|
||||||
check(*s, "an unknown type has no parts", get_entity_parts(Entity).count == 0);
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stallable because the type table is: ENTITY_TYPES only exists once the
|
|
||||||
// metaprogram has seen every @Entity in the program.
|
|
||||||
#run,stallable {
|
|
||||||
test_entity_part_placement();
|
|
||||||
test_entity_type_table();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gen_entity_type_table :: () -> string {
|
|
||||||
builder : String_Builder;
|
|
||||||
append(*builder, "ENTITY_TYPE_TABLE :: Entity_Type_Info.[\n");
|
|
||||||
for ENTITY_TYPES {
|
|
||||||
ti := cast(*Type_Info_Struct) it;
|
|
||||||
marker := "\"\"";
|
|
||||||
for member: ti.members {
|
|
||||||
if member.name == "EDITOR_MARKER" && (member.flags & .CONSTANT) {
|
|
||||||
marker = tprint("%.EDITOR_MARKER", ti.name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print_to_builder(*builder,
|
|
||||||
" .{name = \"%\", type = %, parts = %.PARTS, editor_marker = %, create = () -> *Entity { return New(%); }},\n",
|
|
||||||
ti.name, ti.name, ti.name, marker, ti.name);
|
|
||||||
}
|
|
||||||
append(*builder, "];\n");
|
|
||||||
return builder_to_string(*builder);
|
|
||||||
}
|
|
||||||
@ -28,7 +28,6 @@ Editor_Action :: enum {
|
|||||||
LEVEL_TOOL_LINE;
|
LEVEL_TOOL_LINE;
|
||||||
LEVEL_TOOL_INSPECTOR;
|
LEVEL_TOOL_INSPECTOR;
|
||||||
LEVEL_TOOL_VIEWER;
|
LEVEL_TOOL_VIEWER;
|
||||||
LEVEL_TOOL_ENTITY;
|
|
||||||
// Level editor — tabs
|
// Level editor — tabs
|
||||||
LEVEL_TAB_TOOLS;
|
LEVEL_TAB_TOOLS;
|
||||||
LEVEL_TAB_INFO;
|
LEVEL_TAB_INFO;
|
||||||
@ -181,7 +180,6 @@ set_default_bindings :: () {
|
|||||||
set(.LEVEL_TOOL_LINE, cast(Key_Code) #char "4");
|
set(.LEVEL_TOOL_LINE, cast(Key_Code) #char "4");
|
||||||
set(.LEVEL_TOOL_INSPECTOR, cast(Key_Code) #char "5");
|
set(.LEVEL_TOOL_INSPECTOR, cast(Key_Code) #char "5");
|
||||||
set(.LEVEL_TOOL_VIEWER, cast(Key_Code) #char "6");
|
set(.LEVEL_TOOL_VIEWER, cast(Key_Code) #char "6");
|
||||||
set(.LEVEL_TOOL_ENTITY, cast(Key_Code) #char "7");
|
|
||||||
set(.LEVEL_TAB_TOOLS, cast(Key_Code) #char "T");
|
set(.LEVEL_TAB_TOOLS, cast(Key_Code) #char "T");
|
||||||
set(.LEVEL_TAB_INFO, cast(Key_Code) #char "I");
|
set(.LEVEL_TAB_INFO, cast(Key_Code) #char "I");
|
||||||
set(.LEVEL_TAB_TACOMA, cast(Key_Code) #char "X");
|
set(.LEVEL_TAB_TACOMA, cast(Key_Code) #char "X");
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#if FLAG_USE_TEST_GAME {
|
#if FLAG_TEST_EXE_ENGINE {
|
||||||
PACK_DIR :: "./test_packs";
|
PACK_DIR :: "./test_packs";
|
||||||
GAME_RESOURCES_DIR :: "./test_game/resources";
|
GAME_RESOURCES_DIR :: "./test_game/resources";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -18,10 +18,8 @@ _emit :: (level: Log_Level, message: string) {
|
|||||||
else "[INFO] ";
|
else "[INFO] ";
|
||||||
|
|
||||||
// We need to protect the logger from custom allocators since we don't
|
// We need to protect the logger from custom allocators since we don't
|
||||||
// want those meddling with our log row allocation! Before the engine has
|
// want those meddling with our log row allocation!
|
||||||
// set one up — at compile time, in a #run — there is nothing to protect
|
push_allocator(default_allocator);
|
||||||
// against and nothing to allocate from, so the current one has to do.
|
|
||||||
push_allocator(ifx default_allocator.proc then default_allocator else context.allocator);
|
|
||||||
line := copy_string(tprint("%1%2", prefix, message));
|
line := copy_string(tprint("%1%2", prefix, message));
|
||||||
print("%\n", line);
|
print("%\n", line);
|
||||||
console_add_output_line(line);
|
console_add_output_line(line);
|
||||||
|
|||||||
25
src/main.jai
25
src/main.jai
@ -15,17 +15,6 @@ String :: #import "String";
|
|||||||
Jaison :: #import "Jaison";
|
Jaison :: #import "Jaison";
|
||||||
stbi :: #import "stb_image";
|
stbi :: #import "stb_image";
|
||||||
|
|
||||||
FLAG_ANY_TEST :: FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE || FLAG_TEST_GAME || FLAG_TEST_EXE_GAME;
|
|
||||||
FLAG_ANY_EXE_TEST :: FLAG_TEST_EXE_ENGINE || FLAG_TEST_EXE_GAME;
|
|
||||||
|
|
||||||
// The engine's own tests build against test_game/, so they still compile on a
|
|
||||||
// checkout that does not have the game/ directory. Game tests build against the
|
|
||||||
// real game, which is the whole point of them.
|
|
||||||
FLAG_USE_TEST_GAME :: FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE;
|
|
||||||
|
|
||||||
// The framework (suites, checks, the exe-test command list) is available under
|
|
||||||
// every test flag; the engine's own test cases are not.
|
|
||||||
#if FLAG_ANY_TEST { #load "tests/framework.jai"; }
|
|
||||||
#if (FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE) { #load "tests/index.jai"; }
|
#if (FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE) { #load "tests/index.jai"; }
|
||||||
|
|
||||||
#load "logging.jai";
|
#load "logging.jai";
|
||||||
@ -43,8 +32,6 @@ FLAG_USE_TEST_GAME :: FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE;
|
|||||||
#load "ray.jai";
|
#load "ray.jai";
|
||||||
#load "profiling.jai";
|
#load "profiling.jai";
|
||||||
#load "particles/particles.jai";
|
#load "particles/particles.jai";
|
||||||
#load "orientation.jai";
|
|
||||||
#load "entities.jai";
|
|
||||||
#load "world.jai";
|
#load "world.jai";
|
||||||
#load "utils.jai";
|
#load "utils.jai";
|
||||||
#load "audio/audio.jai";
|
#load "audio/audio.jai";
|
||||||
@ -53,10 +40,10 @@ FLAG_USE_TEST_GAME :: FLAG_TEST_ENGINE || FLAG_TEST_EXE_ENGINE;
|
|||||||
#load "loading_screen.jai";
|
#load "loading_screen.jai";
|
||||||
#load "ui/demo.jai";
|
#load "ui/demo.jai";
|
||||||
|
|
||||||
#if FLAG_USE_TEST_GAME {
|
#if !FLAG_TEST_EXE_ENGINE {
|
||||||
#load "../test_game/game.jai";
|
|
||||||
} else {
|
|
||||||
#load "../game/game.jai";
|
#load "../game/game.jai";
|
||||||
|
} else {
|
||||||
|
#load "../test_game/game.jai";
|
||||||
}
|
}
|
||||||
|
|
||||||
last_frame_time : float64; // timestamp of the last frame
|
last_frame_time : float64; // timestamp of the last frame
|
||||||
@ -152,10 +139,6 @@ init :: () {
|
|||||||
#if FLAG_TEST_EXE_ENGINE {
|
#if FLAG_TEST_EXE_ENGINE {
|
||||||
engine_exe_tests_add();
|
engine_exe_tests_add();
|
||||||
}
|
}
|
||||||
#if FLAG_TEST_EXE_GAME {
|
|
||||||
// Fixed-name hook, same convention as game_init / game_tick.
|
|
||||||
game_exe_tests_add();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init_after_core :: () {
|
init_after_core :: () {
|
||||||
@ -242,7 +225,7 @@ frame :: () {
|
|||||||
|
|
||||||
add_frame_profiling_point("After loading logic");
|
add_frame_profiling_point("After loading logic");
|
||||||
|
|
||||||
#if FLAG_ANY_EXE_TEST {
|
#if FLAG_TEST_EXE_ENGINE {
|
||||||
run_exe_tests();
|
run_exe_tests();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
Entity types register themselves with an @Entity note on the struct, the same
|
|
||||||
way console commands register with @Command. The generators in entities.jai
|
|
||||||
all read one list, ENTITY_TYPES, which is built here and injected once the
|
|
||||||
rest of the program has been typechecked — that is why those generators are
|
|
||||||
written as #run,stallable.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "Sort";
|
|
||||||
|
|
||||||
entity_type_names : [..]string;
|
|
||||||
entity_types_emitted := false;
|
|
||||||
|
|
||||||
add_entity_types :: (message: *Message, w: *Workspace) {
|
|
||||||
if message.kind == .PHASE {
|
|
||||||
phase := cast(*Message_Phase) message;
|
|
||||||
if phase.phase == .TYPECHECKED_ALL_WE_CAN && !entity_types_emitted {
|
|
||||||
entity_types_emitted = true;
|
|
||||||
emit_entity_types(w);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if message.kind != .TYPECHECKED then return;
|
|
||||||
code := cast(*Message_Typechecked) message;
|
|
||||||
|
|
||||||
for code.declarations {
|
|
||||||
decl := it.expression;
|
|
||||||
if !decl.expression || decl.expression.kind != .STRUCT then continue;
|
|
||||||
if !is_main_program(decl) then continue;
|
|
||||||
|
|
||||||
noted := false;
|
|
||||||
for note: decl.notes if note.text == "Entity" noted = true;
|
|
||||||
embeds := struct_embeds_entity(cast(*Code_Struct) decl.expression);
|
|
||||||
|
|
||||||
// Without this the note is as easy to forget as the hand-written list it
|
|
||||||
// replaced, and forgetting it is silent: the type compiles fine and is
|
|
||||||
// simply invisible to spawning, the editor and saving.
|
|
||||||
if embeds && !noted {
|
|
||||||
report(decl, sprint("Struct % embeds Entity but is not marked @Entity, so nothing can spawn, edit or save it.", decl.name));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !noted then continue;
|
|
||||||
|
|
||||||
if !embeds {
|
|
||||||
report(decl, sprint("Struct % is marked @Entity but has no '#as using base : Entity;'.", decl.name));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if decl.flags & .SCOPE_FILE {
|
|
||||||
report(decl, sprint("Entity type % is file scoped, that is not okay. The generated type table has to name it.", decl.name));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
array_add(*entity_type_names, sprint("%", decl.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#scope_file
|
|
||||||
|
|
||||||
// Emitted even when the game declares no entity types at all: the generators in
|
|
||||||
// entities.jai are stalled waiting for this name and would never come unstuck.
|
|
||||||
emit_entity_types :: (w: *Workspace) {
|
|
||||||
// Sorted by name so the generated table — and with it the order of the
|
|
||||||
// editor's spawn list — does not shuffle between builds just because
|
|
||||||
// typechecking happened to finish in a different order.
|
|
||||||
quick_sort(entity_type_names, compare_strings);
|
|
||||||
|
|
||||||
builder : String_Builder;
|
|
||||||
append(*builder, "ENTITY_TYPES :: Type.[");
|
|
||||||
for entity_type_names {
|
|
||||||
if it_index > 0 then append(*builder, ", ");
|
|
||||||
append(*builder, it);
|
|
||||||
}
|
|
||||||
append(*builder, "];\n");
|
|
||||||
add_build_string(builder_to_string(*builder), w.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct_embeds_entity :: (s: *Code_Struct) -> bool {
|
|
||||||
if !s.defined_type then return false;
|
|
||||||
for s.defined_type.members {
|
|
||||||
if !(it.flags & .USING) then continue;
|
|
||||||
if it.type.type != .STRUCT then continue;
|
|
||||||
if (cast(*Type_Info_Struct) it.type).name == "Entity" then return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Game and engine code only. Module code never declares entity types, and
|
|
||||||
// build strings have no enclosing load to ask about.
|
|
||||||
is_main_program :: (decl: *Code_Declaration) -> bool {
|
|
||||||
if !decl.enclosing_load then return false;
|
|
||||||
if !decl.enclosing_load.enclosing_import then return false;
|
|
||||||
return decl.enclosing_load.enclosing_import.module_type == .MAIN_PROGRAM;
|
|
||||||
}
|
|
||||||
|
|
||||||
report :: (decl: *Code_Declaration, error: string) {
|
|
||||||
compiler_report(error, make_location(cast(*Code_Node) decl), .ERROR);
|
|
||||||
}
|
|
||||||
@ -1,10 +1,8 @@
|
|||||||
#load "ascii.jai";
|
#load "ascii.jai";
|
||||||
#load "../resource_mirror.jai";
|
|
||||||
#load "pack.jai";
|
#load "pack.jai";
|
||||||
#load "lint.jai";
|
#load "lint.jai";
|
||||||
#load "hacks.jai";
|
#load "hacks.jai";
|
||||||
#load "console_commands.jai";
|
#load "console_commands.jai";
|
||||||
#load "entity_types.jai";
|
|
||||||
#load "shaderload.jai";
|
#load "shaderload.jai";
|
||||||
|
|
||||||
endframe_modified := false;
|
endframe_modified := false;
|
||||||
@ -16,6 +14,4 @@ custom_message_handler :: (message: *Message, w: *Workspace) {
|
|||||||
remove_getrect_custom_cursor(message, w);
|
remove_getrect_custom_cursor(message, w);
|
||||||
|
|
||||||
add_console_commands(message, w);
|
add_console_commands(message, w);
|
||||||
|
|
||||||
add_entity_types(message, w);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
should_ignore_file :: (name: string) -> bool {
|
should_ignore_file :: (name: string) -> bool {
|
||||||
if name.count > 0 && name[0] == #char "." return true;
|
if name.count > 0 && name[0] == #char "." return true;
|
||||||
|
|
||||||
// Aseprite sources are inputs (see resource_mirror.jai), not shippable
|
ok, left, right := split_from_right(name, #char ".");
|
||||||
// assets; only the .sheet.png/.sheet.json they export go into the pack.
|
if right == "aseprite" {
|
||||||
if is_aseprite_source(name) return true;
|
print("Ignoring % as aseprite file...\n", name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -51,10 +53,11 @@ create_pack :: (include_test_resources: bool = false, pack_dir: string = "./pack
|
|||||||
#import "Simple_Package";
|
#import "Simple_Package";
|
||||||
#import "File";
|
#import "File";
|
||||||
|
|
||||||
// Packs come from the processed copy of the resource trees, never the trees
|
util.visit_files("./resources", true, true, file_visit_handler);
|
||||||
// themselves. See resource_mirror.jai.
|
if include_test_resources {
|
||||||
for prepare_resource_mirror(include_test_resources) {
|
util.visit_files("./test_game/resources", true, true, file_visit_handler);
|
||||||
util.visit_files(it, true, true, file_visit_handler);
|
} else {
|
||||||
|
util.visit_files("./game/resources", true, true, file_visit_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
make_directory_if_it_does_not_exist(pack_dir);
|
make_directory_if_it_does_not_exist(pack_dir);
|
||||||
|
|||||||
@ -1,291 +0,0 @@
|
|||||||
// The 24 axis-aligned cube rotations, encoded as 'face * 4 + twist' exactly as
|
|
||||||
// the trile shaders decode them (see get_orientation_matrix in
|
|
||||||
// shader_trile_shadow.glsl). Everything here mirrors that decode, so a rotation
|
|
||||||
// applied to a position on the CPU lines up with the geometry the GPU draws.
|
|
||||||
//
|
|
||||||
// Note the shaders build their mat3s from columns, which makes each of their
|
|
||||||
// rot_* helpers the transpose of the usual form. The matrices below are the
|
|
||||||
// effective row-major operators (v' = M * v), so they match what the shader
|
|
||||||
// actually does rather than what its helper names suggest.
|
|
||||||
|
|
||||||
ORIENTATION_COUNT :: 24;
|
|
||||||
|
|
||||||
Orientation_Matrix :: struct {
|
|
||||||
m : [3][3]s8;
|
|
||||||
}
|
|
||||||
|
|
||||||
orientation_matrix :: (ori: u8) -> Orientation_Matrix {
|
|
||||||
return ORIENTATION_MATRICES[ori % ORIENTATION_COUNT];
|
|
||||||
}
|
|
||||||
|
|
||||||
rotate_by_orientation :: (ori: u8, v: Vector3) -> Vector3 {
|
|
||||||
m := orientation_matrix(ori).m;
|
|
||||||
return .{
|
|
||||||
cast(float)m[0][0] * v.x + cast(float)m[0][1] * v.y + cast(float)m[0][2] * v.z,
|
|
||||||
cast(float)m[1][0] * v.x + cast(float)m[1][1] * v.y + cast(float)m[1][2] * v.z,
|
|
||||||
cast(float)m[2][0] * v.x + cast(float)m[2][1] * v.y + cast(float)m[2][2] * v.z,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The orientation equal to applying 'inner' first and then 'outer'. Used to fold
|
|
||||||
// an entity's rotation into the orientation its parts were authored with.
|
|
||||||
compose_orientations :: (outer: u8, inner: u8) -> u8 {
|
|
||||||
return ORIENTATION_COMPOSE[outer % ORIENTATION_COUNT][inner % ORIENTATION_COUNT];
|
|
||||||
}
|
|
||||||
|
|
||||||
// The quarter turn that carries 'u' onto 'v' and leaves 'n' alone, where u, v, n
|
|
||||||
// are a set of perpendicular unit axes. Taking the basis from the caller keeps
|
|
||||||
// the result consistent with whatever handedness the caller draws with, instead
|
|
||||||
// of relying on a sign convention agreed at a distance.
|
|
||||||
orientation_quarter_turn :: (u: Vector3, v: Vector3, n: Vector3) -> u8 {
|
|
||||||
// M = v*uT - u*vT + n*nT sends u -> v, v -> -u, n -> n.
|
|
||||||
m : Orientation_Matrix;
|
|
||||||
ui := to_axis_ints(u);
|
|
||||||
vi := to_axis_ints(v);
|
|
||||||
ni := to_axis_ints(n);
|
|
||||||
for i: 0..2 {
|
|
||||||
for j: 0..2 {
|
|
||||||
m.m[i][j] = vi[i]*ui[j] - ui[i]*vi[j] + ni[i]*ni[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ori, ok := orientation_from_matrix(m);
|
|
||||||
if !ok {
|
|
||||||
log_error("Rotate gizmo basis is not axis-aligned; ignoring the turn.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return ori;
|
|
||||||
}
|
|
||||||
|
|
||||||
orientation_from_matrix :: (m: Orientation_Matrix) -> (ori: u8, ok: bool) {
|
|
||||||
for i: 0..ORIENTATION_COUNT-1 {
|
|
||||||
if mat3_equal(m, ORIENTATION_MATRICES[i]) return cast(u8) i, true;
|
|
||||||
}
|
|
||||||
return 0, false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#scope_file
|
|
||||||
|
|
||||||
ORIENTATION_MATRICES :: #run generate_orientation_matrices();
|
|
||||||
ORIENTATION_COMPOSE :: #run generate_orientation_compose_table();
|
|
||||||
|
|
||||||
// Axis vectors only ever hold 0 or +-1 here, but they arrive as floats.
|
|
||||||
to_axis_ints :: (v: Vector3) -> [3]s8 {
|
|
||||||
round_axis :: (f: float) -> s8 {
|
|
||||||
if f > 0.5 then return 1;
|
|
||||||
if f < -0.5 then return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return .[round_axis(v.x), round_axis(v.y), round_axis(v.z)];
|
|
||||||
}
|
|
||||||
|
|
||||||
mat3_of :: (a: s8, b: s8, c: s8, d: s8, e: s8, f: s8, g: s8, h: s8, i: s8) -> Orientation_Matrix {
|
|
||||||
r : Orientation_Matrix;
|
|
||||||
r.m[0][0] = a; r.m[0][1] = b; r.m[0][2] = c;
|
|
||||||
r.m[1][0] = d; r.m[1][1] = e; r.m[1][2] = f;
|
|
||||||
r.m[2][0] = g; r.m[2][1] = h; r.m[2][2] = i;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
// (c, s) are cos and sin of the angle, always in {-1, 0, 1} here.
|
|
||||||
rot_x_effective :: (s: s8, c: s8) -> Orientation_Matrix { return mat3_of(1, 0, 0, 0, c, s, 0, -s, c); }
|
|
||||||
rot_y_effective :: (s: s8, c: s8) -> Orientation_Matrix { return mat3_of(c, 0, -s, 0, 1, 0, s, 0, c); }
|
|
||||||
rot_z_effective :: (s: s8, c: s8) -> Orientation_Matrix { return mat3_of(c, s, 0, -s, c, 0, 0, 0, 1); }
|
|
||||||
|
|
||||||
mat3_mul :: (a: Orientation_Matrix, b: Orientation_Matrix) -> Orientation_Matrix {
|
|
||||||
r : Orientation_Matrix;
|
|
||||||
for i: 0..2 {
|
|
||||||
for j: 0..2 {
|
|
||||||
sum : s8 = 0;
|
|
||||||
for k: 0..2 sum += a.m[i][k] * b.m[k][j];
|
|
||||||
r.m[i][j] = sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat3_equal :: (a: Orientation_Matrix, b: Orientation_Matrix) -> bool {
|
|
||||||
for i: 0..2 for j: 0..2 if a.m[i][j] != b.m[i][j] return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirrors get_orientation_matrix: a face rotation followed by a twist about Y.
|
|
||||||
generate_orientation_matrices :: () -> [ORIENTATION_COUNT]Orientation_Matrix {
|
|
||||||
identity :: #run mat3_of(1, 0, 0, 0, 1, 0, 0, 0, 1);
|
|
||||||
// sin/cos of face and twist angles, written out to keep this exact.
|
|
||||||
faces : [6]Orientation_Matrix;
|
|
||||||
faces[0] = identity;
|
|
||||||
faces[1] = rot_x_effective( 0, -1); // PI
|
|
||||||
faces[2] = rot_z_effective(-1, 0); // -PI/2
|
|
||||||
faces[3] = rot_z_effective( 1, 0); // PI/2
|
|
||||||
faces[4] = rot_x_effective( 1, 0); // PI/2
|
|
||||||
faces[5] = rot_x_effective(-1, 0); // -PI/2
|
|
||||||
|
|
||||||
twists : [4]Orientation_Matrix;
|
|
||||||
twists[0] = identity;
|
|
||||||
twists[1] = rot_y_effective( 1, 0);
|
|
||||||
twists[2] = rot_y_effective( 0, -1);
|
|
||||||
twists[3] = rot_y_effective(-1, 0);
|
|
||||||
|
|
||||||
result : [ORIENTATION_COUNT]Orientation_Matrix;
|
|
||||||
for face: 0..5 {
|
|
||||||
for twist: 0..3 {
|
|
||||||
result[face * 4 + twist] = mat3_mul(faces[face], twists[twist]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_orientation_compose_table :: () -> [ORIENTATION_COUNT][ORIENTATION_COUNT]u8 {
|
|
||||||
mats := generate_orientation_matrices();
|
|
||||||
table : [ORIENTATION_COUNT][ORIENTATION_COUNT]u8;
|
|
||||||
for a: 0..ORIENTATION_COUNT-1 {
|
|
||||||
for b: 0..ORIENTATION_COUNT-1 {
|
|
||||||
product := mat3_mul(mats[a], mats[b]);
|
|
||||||
found := false;
|
|
||||||
for c: 0..ORIENTATION_COUNT-1 {
|
|
||||||
if mat3_equal(product, mats[c]) {
|
|
||||||
table[a][b] = cast(u8) c;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(found, "Cube rotation % * % is not one of the 24 orientations", a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if FLAG_TEST_ENGINE {
|
|
||||||
v3_eq :: (a: Vector3, b: Vector3) -> bool {
|
|
||||||
return abs(a.x - b.x) < 0.001 && abs(a.y - b.y) < 0.001 && abs(a.z - b.z) < 0.001;
|
|
||||||
}
|
|
||||||
|
|
||||||
test_orientation_set :: () {
|
|
||||||
s := begin_suite("cube orientations");
|
|
||||||
|
|
||||||
distinct := true;
|
|
||||||
for i: 0..ORIENTATION_COUNT-1 {
|
|
||||||
for j: i+1..ORIENTATION_COUNT-1 {
|
|
||||||
if mat3_equal(ORIENTATION_MATRICES[i], ORIENTATION_MATRICES[j]) distinct = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check(*s, "all 24 orientations are distinct", distinct);
|
|
||||||
|
|
||||||
// Rotations preserve handedness, so every determinant must be +1. A -1
|
|
||||||
// would mean the set had picked up a reflection.
|
|
||||||
proper := true;
|
|
||||||
for i: 0..ORIENTATION_COUNT-1 {
|
|
||||||
m := ORIENTATION_MATRICES[i].m;
|
|
||||||
det := cast(int)m[0][0] * (cast(int)m[1][1]*m[2][2] - cast(int)m[1][2]*m[2][1])
|
|
||||||
- cast(int)m[0][1] * (cast(int)m[1][0]*m[2][2] - cast(int)m[1][2]*m[2][0])
|
|
||||||
+ cast(int)m[0][2] * (cast(int)m[1][0]*m[2][1] - cast(int)m[1][1]*m[2][0]);
|
|
||||||
if det != 1 proper = false;
|
|
||||||
}
|
|
||||||
check(*s, "every orientation is a proper rotation", proper);
|
|
||||||
|
|
||||||
check(*s, "orientation 0 is the identity",
|
|
||||||
v3_eq(rotate_by_orientation(0, .{1, 2, 3}), .{1, 2, 3}));
|
|
||||||
|
|
||||||
// Orientations 1..3 are the twists: rotations about Y, so Y is fixed.
|
|
||||||
twists_about_y := true;
|
|
||||||
for ori: 1..3 {
|
|
||||||
if !v3_eq(rotate_by_orientation(cast(u8)ori, .{0, 1, 0}), .{0, 1, 0}) twists_about_y = false;
|
|
||||||
}
|
|
||||||
check(*s, "the twist orientations turn about Y", twists_about_y);
|
|
||||||
|
|
||||||
// Rotations are rigid: lengths survive.
|
|
||||||
lengths_kept := true;
|
|
||||||
for ori: 0..ORIENTATION_COUNT-1 {
|
|
||||||
r := rotate_by_orientation(cast(u8)ori, .{1, 2, 3});
|
|
||||||
if abs(length(r) - length(Vector3.{1, 2, 3})) > 0.001 lengths_kept = false;
|
|
||||||
}
|
|
||||||
check(*s, "rotating preserves length", lengths_kept);
|
|
||||||
|
|
||||||
// Pinned against the shader's decode. If these drift, entity part offsets
|
|
||||||
// will rotate the opposite way from the trile geometry they belong to.
|
|
||||||
// Orientation 1 is face 0 twist 1, the shader's rot_y(PI/2).
|
|
||||||
check(*s, "orientation 1 (twist 90) sends X to Z",
|
|
||||||
v3_eq(rotate_by_orientation(1, .{1, 0, 0}), .{0, 0, 1}));
|
|
||||||
// Orientation 4 is face 1 twist 0, the shader's rot_x(PI).
|
|
||||||
check(*s, "orientation 4 (face 1) flips Y and Z",
|
|
||||||
v3_eq(rotate_by_orientation(4, .{0, 1, 1}), .{0, -1, -1}));
|
|
||||||
// Orientation 16 is face 4 twist 0, the shader's rot_x(PI/2).
|
|
||||||
check(*s, "orientation 16 (face 4) sends Y to -Z",
|
|
||||||
v3_eq(rotate_by_orientation(16, .{0, 1, 0}), .{0, 0, -1}));
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
test_orientation_compose :: () {
|
|
||||||
s := begin_suite("orientation composition");
|
|
||||||
|
|
||||||
identity_ok := true;
|
|
||||||
for ori: 0..ORIENTATION_COUNT-1 {
|
|
||||||
if compose_orientations(0, cast(u8)ori) != cast(u8)ori identity_ok = false;
|
|
||||||
if compose_orientations(cast(u8)ori, 0) != cast(u8)ori identity_ok = false;
|
|
||||||
}
|
|
||||||
check(*s, "composing with orientation 0 changes nothing", identity_ok);
|
|
||||||
|
|
||||||
// Composing a fixed orientation with each of the 24 must hit each exactly
|
|
||||||
// once, or the table is not a group and some rotations are unreachable.
|
|
||||||
permutation := true;
|
|
||||||
for a: 0..ORIENTATION_COUNT-1 {
|
|
||||||
seen : [ORIENTATION_COUNT]bool;
|
|
||||||
for b: 0..ORIENTATION_COUNT-1 {
|
|
||||||
r := compose_orientations(cast(u8)a, cast(u8)b);
|
|
||||||
if seen[r] permutation = false;
|
|
||||||
seen[r] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check(*s, "every row of the compose table is a permutation", permutation);
|
|
||||||
|
|
||||||
// compose(outer, inner) must mean "inner first, then outer".
|
|
||||||
order_ok := true;
|
|
||||||
v := Vector3.{1, 2, 3};
|
|
||||||
for a: 0..ORIENTATION_COUNT-1 {
|
|
||||||
for b: 0..ORIENTATION_COUNT-1 {
|
|
||||||
combined := rotate_by_orientation(compose_orientations(cast(u8)a, cast(u8)b), v);
|
|
||||||
stepwise := rotate_by_orientation(cast(u8)a, rotate_by_orientation(cast(u8)b, v));
|
|
||||||
if !v3_eq(combined, stepwise) order_ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check(*s, "composing matches applying inner then outer", order_ok);
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
test_orientation_quarter_turn :: () {
|
|
||||||
s := begin_suite("orientation quarter turns");
|
|
||||||
X :: Vector3.{1, 0, 0};
|
|
||||||
Y :: Vector3.{0, 1, 0};
|
|
||||||
Z :: Vector3.{0, 0, 1};
|
|
||||||
|
|
||||||
q := orientation_quarter_turn(X, Z, Y); // turn X toward Z about Y
|
|
||||||
check(*s, "a quarter turn carries u onto v", v3_eq(rotate_by_orientation(q, X), Z));
|
|
||||||
check(*s, "a quarter turn carries v onto -u", v3_eq(rotate_by_orientation(q, Z), .{-1, 0, 0}));
|
|
||||||
check(*s, "a quarter turn leaves its axis alone", v3_eq(rotate_by_orientation(q, Y), Y));
|
|
||||||
|
|
||||||
four := compose_orientations(q, compose_orientations(q, compose_orientations(q, q)));
|
|
||||||
check(*s, "four quarter turns return to the start", four == 0);
|
|
||||||
|
|
||||||
// Dragging the ring back the other way must undo the turn exactly.
|
|
||||||
back := orientation_quarter_turn(Z, X, Y);
|
|
||||||
check(*s, "the reverse turn cancels the forward one", compose_orientations(q, back) == 0);
|
|
||||||
|
|
||||||
all_valid := true;
|
|
||||||
for pair: Vector3.[X, Y, Z] {
|
|
||||||
u := pair;
|
|
||||||
v := ifx v3_eq(u, X) then Y else ifx v3_eq(u, Y) then Z else X;
|
|
||||||
n := cross(u, v);
|
|
||||||
t := orientation_quarter_turn(u, v, n);
|
|
||||||
if !v3_eq(rotate_by_orientation(t, u), v) all_valid = false;
|
|
||||||
}
|
|
||||||
check(*s, "quarter turns work on every axis pair", all_valid);
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
#run {
|
|
||||||
test_orientation_set();
|
|
||||||
test_orientation_compose();
|
|
||||||
test_orientation_quarter_turn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,5 @@
|
|||||||
#if !FLAG_RELEASE_BUILD && OS != .WASM {
|
#if !FLAG_RELEASE_BUILD && OS != .WASM {
|
||||||
|
|
||||||
#load "resource_mirror.jai";
|
|
||||||
|
|
||||||
#import "String";
|
#import "String";
|
||||||
Pack_Writer :: #import "Simple_Package";
|
Pack_Writer :: #import "Simple_Package";
|
||||||
File_Util :: #import "File_Utilities";
|
File_Util :: #import "File_Utilities";
|
||||||
@ -38,11 +36,8 @@ _hotreload_visitor :: (info: *File_Util.File_Visit_Info, packs: *Table(string, P
|
|||||||
|
|
||||||
recreate_packs_on_disk :: () {
|
recreate_packs_on_disk :: () {
|
||||||
packs: Table(string, Pack_Writer.Create_Package);
|
packs: Table(string, Pack_Writer.Create_Package);
|
||||||
// Same mirror-and-process pipeline the build uses, so a hot reload picks up
|
File_Util.visit_files("./resources", true, *packs, _hotreload_visitor);
|
||||||
// edited .aseprite files too, not just plain assets.
|
File_Util.visit_files(GAME_RESOURCES_DIR, true, *packs, _hotreload_visitor);
|
||||||
for prepare_resource_mirror(FLAG_USE_TEST_GAME) {
|
|
||||||
File_Util.visit_files(it, true, *packs, _hotreload_visitor);
|
|
||||||
}
|
|
||||||
for pack, key: packs {
|
for pack, key: packs {
|
||||||
Pack_Writer.write(*pack, tprint("%/%.pack", PACK_DIR, key));
|
Pack_Writer.write(*pack, tprint("%/%.pack", PACK_DIR, key));
|
||||||
log_info("Hot-reload: wrote pack '%'", key);
|
log_info("Hot-reload: wrote pack '%'", key);
|
||||||
|
|||||||
@ -41,25 +41,61 @@ Particle :: struct {
|
|||||||
definition : *Particle_Emitter_Config;
|
definition : *Particle_Emitter_Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_PARTICLES :: 16384;
|
BUCKET_MAX_PARTICLES :: 2048;
|
||||||
|
PARTICLE_BUCKETS :: 16;
|
||||||
|
MAX_PARTICLES :: PARTICLE_BUCKETS * BUCKET_MAX_PARTICLES;
|
||||||
|
|
||||||
|
|
||||||
|
Particle_Bucket :: struct {
|
||||||
|
in_use : bool;
|
||||||
|
name : string;
|
||||||
|
particles : [BUCKET_MAX_PARTICLES] Particle;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_particle_buckets : [PARTICLE_BUCKETS] Particle_Bucket;
|
||||||
|
|
||||||
|
Particle_Render_Buffer :: struct {
|
||||||
|
total_count : s32;
|
||||||
|
pos_size : [MAX_PARTICLES] Vector4;
|
||||||
|
uv_rects : [MAX_PARTICLES] Vector4;
|
||||||
|
colors : [MAX_PARTICLES] Vector4;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_particle_render_buffer : Particle_Render_Buffer;
|
||||||
|
|
||||||
g_particles : [MAX_PARTICLES] Particle;
|
|
||||||
g_emitter_defs : [..] Particle_Emitter_Config;
|
g_emitter_defs : [..] Particle_Emitter_Config;
|
||||||
|
|
||||||
clear_particles :: () {
|
clear_particles :: () {
|
||||||
for *p: g_particles p.alive = false;
|
for *bucket: g_particle_buckets {
|
||||||
|
bucket.in_use = false;
|
||||||
|
bucket.name = "";
|
||||||
|
for *p: bucket.particles p.alive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count_alive_particles :: () -> s32 {
|
||||||
|
count : s32 = 0;
|
||||||
|
for *bucket: g_particle_buckets {
|
||||||
|
if !bucket.in_use then continue;
|
||||||
|
for p: bucket.particles {
|
||||||
|
if p.alive then count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
tick_particles_physics :: (dt: float) {
|
tick_particles_physics :: (dt: float) {
|
||||||
for *p: g_particles {
|
for *bucket: g_particle_buckets {
|
||||||
if !p.alive then continue;
|
for *p: bucket.particles {
|
||||||
p.age += dt;
|
if !p.alive then continue;
|
||||||
if p.age >= p.lifetime {
|
p.age += dt;
|
||||||
p.alive = false;
|
if p.age >= p.lifetime {
|
||||||
continue;
|
p.alive = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
p.velocity.y -= p.gravity * dt;
|
||||||
|
p.position += p.velocity * dt;
|
||||||
}
|
}
|
||||||
p.velocity.y -= p.gravity * dt;
|
|
||||||
p.position += p.velocity * dt;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,99 +134,66 @@ tick_emitter_instance :: (inst: *Particle_Emitter_Instance, dt: float) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_particle_render_tasks :: () {
|
add_particle_render_tasks :: () {
|
||||||
Batch :: struct {
|
buf := *g_particle_render_buffer;
|
||||||
anim_name : string;
|
|
||||||
blend_mode : Particle_Blend_Mode;
|
|
||||||
anim : *Animation;
|
|
||||||
sheet : sg_image;
|
|
||||||
count : s32;
|
|
||||||
pos_size : *[MAX_PARTICLES]Vector4;
|
|
||||||
uv_rects : *[MAX_PARTICLES]Vector4;
|
|
||||||
colors : *[MAX_PARTICLES]Vector4;
|
|
||||||
}
|
|
||||||
|
|
||||||
batches : [..] Batch;
|
|
||||||
batches.allocator = temp;
|
|
||||||
|
|
||||||
for *p: g_particles {
|
|
||||||
if !p.alive then continue;
|
|
||||||
if p.definition == null then continue;
|
|
||||||
def := p.definition;
|
|
||||||
if def.animation_name.count == 0 then continue;
|
|
||||||
|
|
||||||
batch : *Batch = null;
|
|
||||||
for *b: batches {
|
|
||||||
if b.anim_name == def.animation_name && b.blend_mode == def.blend_mode {
|
|
||||||
batch = b;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if batch == null {
|
|
||||||
anim := get_animation_from_string(def.animation_name);
|
|
||||||
if anim == null then continue;
|
|
||||||
if anim.frames.count == 0 then continue;
|
|
||||||
array_add(*batches, .{
|
|
||||||
anim_name = def.animation_name,
|
|
||||||
blend_mode = def.blend_mode,
|
|
||||||
anim = anim,
|
|
||||||
sheet = anim.sheet,
|
|
||||||
pos_size = New([MAX_PARTICLES]Vector4,, temp),
|
|
||||||
uv_rects = New([MAX_PARTICLES]Vector4,, temp),
|
|
||||||
colors = New([MAX_PARTICLES]Vector4,, temp),
|
|
||||||
});
|
|
||||||
batch = *batches[batches.count - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if batch.count >= MAX_PARTICLES then continue;
|
|
||||||
|
|
||||||
t := p.age / p.lifetime;
|
|
||||||
size := def.size_start + (def.size_end - def.size_start) * t;
|
|
||||||
col := lerp_color(def.color_start, def.color_end, t);
|
|
||||||
|
|
||||||
idx := batch.count;
|
|
||||||
batch.pos_size.*[idx] = .{p.position.x, p.position.y, p.position.z, size};
|
|
||||||
|
|
||||||
anim := batch.anim;
|
|
||||||
frame_count := anim.frames.count;
|
|
||||||
frame := cast(s32)(t * cast(float)frame_count);
|
|
||||||
if frame < 0 then frame = 0;
|
|
||||||
if frame >= frame_count then frame = cast(s32)(frame_count - 1);
|
|
||||||
f := anim.frames[frame];
|
|
||||||
batch.uv_rects.*[idx] = .{
|
|
||||||
cast(float)f.x / cast(float)anim.sheet_w,
|
|
||||||
cast(float)f.y / cast(float)anim.sheet_h,
|
|
||||||
cast(float)f.w / cast(float)anim.sheet_w,
|
|
||||||
cast(float)f.h / cast(float)anim.sheet_h,
|
|
||||||
};
|
|
||||||
|
|
||||||
batch.colors.*[idx] = col;
|
|
||||||
batch.count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf_task := New(Rendering_Task_Particles_Buffer,, temp);
|
|
||||||
total_count : s32 = 0;
|
total_count : s32 = 0;
|
||||||
|
|
||||||
for *b: batches {
|
for *bucket: g_particle_buckets {
|
||||||
if b.count <= 0 then continue;
|
if !bucket.in_use then continue;
|
||||||
|
|
||||||
|
def := get_emitter_def(bucket.name);
|
||||||
|
if def == null then continue;
|
||||||
|
if def.animation_name.count == 0 then continue;
|
||||||
|
|
||||||
|
anim := get_animation_from_string(def.animation_name);
|
||||||
|
if anim == null then continue;
|
||||||
|
if anim.frames.count == 0 then continue;
|
||||||
|
|
||||||
instance_offset := total_count;
|
instance_offset := total_count;
|
||||||
memcpy(*buf_task.pos_size[instance_offset], cast(*void)b.pos_size, b.count * size_of(Vector4));
|
bucket_count : s32 = 0;
|
||||||
memcpy(*buf_task.uv_rects[instance_offset], cast(*void)b.uv_rects, b.count * size_of(Vector4));
|
|
||||||
memcpy(*buf_task.colors[instance_offset], cast(*void)b.colors, b.count * size_of(Vector4));
|
|
||||||
total_count += b.count;
|
|
||||||
|
|
||||||
draw_task : Rendering_Task_Particles;
|
for *p: bucket.particles {
|
||||||
draw_task.count = b.count;
|
if !p.alive then continue;
|
||||||
draw_task.instance_offset = instance_offset;
|
if total_count + bucket_count >= MAX_PARTICLES then break;
|
||||||
draw_task.blend_mode = b.blend_mode;
|
|
||||||
draw_task.sheet = b.sheet;
|
t := p.age / p.lifetime;
|
||||||
add_rendering_task(draw_task);
|
size := def.size_start + (def.size_end - def.size_start) * t;
|
||||||
|
col := lerp_color(def.color_start, def.color_end, t);
|
||||||
|
|
||||||
|
idx := instance_offset + bucket_count;
|
||||||
|
buf.pos_size[idx] = .{p.position.x, p.position.y, p.position.z, size};
|
||||||
|
|
||||||
|
frame_count := anim.frames.count;
|
||||||
|
frame := cast(s32)(t * cast(float)frame_count);
|
||||||
|
if frame < 0 then frame = 0;
|
||||||
|
if frame >= frame_count then frame = cast(s32)(frame_count - 1);
|
||||||
|
f := anim.frames[frame];
|
||||||
|
buf.uv_rects[idx] = .{
|
||||||
|
cast(float)f.x / cast(float)anim.sheet_w,
|
||||||
|
cast(float)f.y / cast(float)anim.sheet_h,
|
||||||
|
cast(float)f.w / cast(float)anim.sheet_w,
|
||||||
|
cast(float)f.h / cast(float)anim.sheet_h,
|
||||||
|
};
|
||||||
|
buf.colors[idx] = col;
|
||||||
|
bucket_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if bucket_count > 0 {
|
||||||
|
draw_task : Rendering_Task_Particles;
|
||||||
|
draw_task.count = bucket_count;
|
||||||
|
draw_task.instance_offset = instance_offset;
|
||||||
|
draw_task.blend_mode = def.blend_mode;
|
||||||
|
draw_task.sheet = anim.sheet;
|
||||||
|
add_rendering_task(draw_task);
|
||||||
|
total_count += bucket_count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.total_count = total_count;
|
||||||
|
|
||||||
if total_count > 0 {
|
if total_count > 0 {
|
||||||
buf_task.total_count = total_count;
|
upload_task : Rendering_Task_Particles_Buffer;
|
||||||
add_rendering_task(buf_task.*);
|
upload_task.total_count = total_count;
|
||||||
|
add_rendering_task(upload_task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,8 +249,24 @@ rng :: inline (lo: float, hi: float) -> float {
|
|||||||
return lo + Random.random_get_zero_to_one() * (hi - lo);
|
return lo + Random.random_get_zero_to_one() * (hi - lo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
find_or_claim_bucket :: (name: string) -> *Particle_Bucket {
|
||||||
|
free_slot : *Particle_Bucket = null;
|
||||||
|
for *bucket: g_particle_buckets {
|
||||||
|
if bucket.in_use && bucket.name == name then return bucket;
|
||||||
|
if !bucket.in_use && free_slot == null then free_slot = bucket;
|
||||||
|
}
|
||||||
|
if free_slot {
|
||||||
|
free_slot.in_use = true;
|
||||||
|
free_slot.name = name;
|
||||||
|
}
|
||||||
|
return free_slot;
|
||||||
|
}
|
||||||
|
|
||||||
spawn_one_particle :: (position: Vector3, def: *Particle_Emitter_Config) {
|
spawn_one_particle :: (position: Vector3, def: *Particle_Emitter_Config) {
|
||||||
for *p: g_particles {
|
bucket := find_or_claim_bucket(def.name);
|
||||||
|
if bucket == null then return;
|
||||||
|
|
||||||
|
for *p: bucket.particles {
|
||||||
if p.alive then continue;
|
if p.alive then continue;
|
||||||
p.alive = true;
|
p.alive = true;
|
||||||
p.age = 0;
|
p.age = 0;
|
||||||
|
|||||||
@ -17,7 +17,6 @@ Render_Command_Type :: enum {
|
|||||||
SET_CAMERA;
|
SET_CAMERA;
|
||||||
DRAW_GROUND;
|
DRAW_GROUND;
|
||||||
ADD_TRILE_POSITIONS;
|
ADD_TRILE_POSITIONS;
|
||||||
ADD_TRILE_POSITIONS_F32;
|
|
||||||
DRAW_TRILE_POSITIONS;
|
DRAW_TRILE_POSITIONS;
|
||||||
ADD_TRILE_RDM_POSITION;
|
ADD_TRILE_RDM_POSITION;
|
||||||
DRAW_TRILE_RDM;
|
DRAW_TRILE_RDM;
|
||||||
@ -52,15 +51,6 @@ Render_Command_Add_Trile_Positions :: struct {
|
|||||||
chunk : Chunk_Key;
|
chunk : Chunk_Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like Add_Trile_Positions, but takes float world-space positions directly
|
|
||||||
// (xyz = position, w = orientation 0..23). Appends into the same instance
|
|
||||||
// buffer / offset list, so draws go through Render_Command_Draw_Trile_Positions.
|
|
||||||
Render_Command_Add_Trile_Positions_F32 :: struct {
|
|
||||||
#as using c : Render_Command;
|
|
||||||
c.type = .ADD_TRILE_POSITIONS_F32;
|
|
||||||
positions : []Vector4;
|
|
||||||
}
|
|
||||||
|
|
||||||
Render_Command_Draw_Trile_Positions :: struct {
|
Render_Command_Draw_Trile_Positions :: struct {
|
||||||
#as using c : Render_Command;
|
#as using c : Render_Command;
|
||||||
c.type = .DRAW_TRILE_POSITIONS;
|
c.type = .DRAW_TRILE_POSITIONS;
|
||||||
@ -120,9 +110,6 @@ Render_Command_Update_Particles :: struct {
|
|||||||
#as using c : Render_Command;
|
#as using c : Render_Command;
|
||||||
c.type = .UPDATE_PARTICLES;
|
c.type = .UPDATE_PARTICLES;
|
||||||
total_count : s32;
|
total_count : s32;
|
||||||
pos_size : [MAX_PARTICLES]Vector4;
|
|
||||||
uv_rects : [MAX_PARTICLES]Vector4;
|
|
||||||
colors : [MAX_PARTICLES]Vector4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Render_Command_Draw_Particles :: struct {
|
Render_Command_Draw_Particles :: struct {
|
||||||
|
|||||||
@ -22,9 +22,6 @@ backend_handle_command :: (cmd: *Render_Command) {
|
|||||||
case .ADD_TRILE_POSITIONS;
|
case .ADD_TRILE_POSITIONS;
|
||||||
add_command := cast(*Render_Command_Add_Trile_Positions)cmd;
|
add_command := cast(*Render_Command_Add_Trile_Positions)cmd;
|
||||||
backend_add_trile_positions(add_command.positions, add_command.chunk);
|
backend_add_trile_positions(add_command.positions, add_command.chunk);
|
||||||
case .ADD_TRILE_POSITIONS_F32;
|
|
||||||
add_f32_command := cast(*Render_Command_Add_Trile_Positions_F32)cmd;
|
|
||||||
backend_add_trile_positions_f32(add_f32_command.positions);
|
|
||||||
case .DRAW_TRILE_POSITIONS;
|
case .DRAW_TRILE_POSITIONS;
|
||||||
draw_command := cast(*Render_Command_Draw_Trile_Positions)cmd;
|
draw_command := cast(*Render_Command_Draw_Trile_Positions)cmd;
|
||||||
backend_draw_trile_positions(draw_command.trile, draw_command.amount, draw_command.conf, draw_command.chunk_key, draw_command.preview_mode, draw_command.offset_index, draw_command.lod_index);
|
backend_draw_trile_positions(draw_command.trile, draw_command.amount, draw_command.conf, draw_command.chunk_key, draw_command.preview_mode, draw_command.offset_index, draw_command.lod_index);
|
||||||
@ -160,14 +157,6 @@ backend_add_trile_positions :: (positions : []Trile_Instance, chunk: Chunk_Key)
|
|||||||
array_add(*trile_offsets, offset);
|
array_add(*trile_offsets, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
backend_add_trile_positions_f32 :: (positions : []Vector4) {
|
|
||||||
offset := sg_append_buffer(gPipelines.trile.bind.vertex_buffers[3], *(sg_range.{
|
|
||||||
ptr = positions.data,
|
|
||||||
size = size_of(Vector4) * cast(u64)positions.count,
|
|
||||||
}));
|
|
||||||
array_add(*trile_offsets, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
backend_draw_trile_positions :: (trile : string, amount : s32, worldConf: *World_Config, chunk_key: Chunk_Key, preview_mode: s32 = 0, offset_index: s32 = 0, lod_index: s32 = 0) {
|
backend_draw_trile_positions :: (trile : string, amount : s32, worldConf: *World_Config, chunk_key: Chunk_Key, preview_mode: s32 = 0, offset_index: s32 = 0, lod_index: s32 = 0) {
|
||||||
if in_gbuffer_pass {
|
if in_gbuffer_pass {
|
||||||
backend_draw_trile_positions_gbuffer(trile, amount, worldConf, offset_index, lod_index);
|
backend_draw_trile_positions_gbuffer(trile, amount, worldConf, offset_index, lod_index);
|
||||||
@ -555,9 +544,9 @@ backend_update_particle_buffers :: (cmd: *Render_Command_Update_Particles) {
|
|||||||
inst_buf1 := gPipelines.particle_additive.bind.vertex_buffers[1];
|
inst_buf1 := gPipelines.particle_additive.bind.vertex_buffers[1];
|
||||||
inst_buf2 := gPipelines.particle_additive.bind.vertex_buffers[2];
|
inst_buf2 := gPipelines.particle_additive.bind.vertex_buffers[2];
|
||||||
inst_buf3 := gPipelines.particle_additive.bind.vertex_buffers[3];
|
inst_buf3 := gPipelines.particle_additive.bind.vertex_buffers[3];
|
||||||
sg_update_buffer(inst_buf1, *(sg_range.{ ptr = cmd.pos_size.data, size = byte_size }));
|
sg_update_buffer(inst_buf1, *(sg_range.{ ptr = g_particle_render_buffer.pos_size.data, size = byte_size }));
|
||||||
sg_update_buffer(inst_buf2, *(sg_range.{ ptr = cmd.uv_rects.data, size = byte_size }));
|
sg_update_buffer(inst_buf2, *(sg_range.{ ptr = g_particle_render_buffer.uv_rects.data, size = byte_size }));
|
||||||
sg_update_buffer(inst_buf3, *(sg_range.{ ptr = cmd.colors.data, size = byte_size }));
|
sg_update_buffer(inst_buf3, *(sg_range.{ ptr = g_particle_render_buffer.colors.data, size = byte_size }));
|
||||||
}
|
}
|
||||||
|
|
||||||
backend_draw_particles :: (cmd: *Render_Command_Draw_Particles) {
|
backend_draw_particles :: (cmd: *Render_Command_Draw_Particles) {
|
||||||
@ -747,7 +736,6 @@ backend_process_command_buckets :: () {
|
|||||||
}
|
}
|
||||||
sg_apply_uniforms(UB_post_process_config, *(sg_range.{ ptr = *post_process_config_uniform, size = size_of(type_of(post_process_config_uniform)) }));
|
sg_apply_uniforms(UB_post_process_config, *(sg_range.{ ptr = *post_process_config_uniform, size = size_of(type_of(post_process_config_uniform)) }));
|
||||||
dof_config_uniform : Dof_Config;
|
dof_config_uniform : Dof_Config;
|
||||||
dof_config_uniform.dof_disabled = ifx disable_dof_post_process then cast(s32)1 else cast(s32)0;
|
|
||||||
dof_config_uniform.dof_max = current_post_process.dof_max;
|
dof_config_uniform.dof_max = current_post_process.dof_max;
|
||||||
dof_config_uniform.dof_point = current_post_process.dof_point;
|
dof_config_uniform.dof_point = current_post_process.dof_point;
|
||||||
sg_apply_uniforms(UB_dof_config, *(sg_range.{ ptr = *dof_config_uniform, size = size_of(type_of(dof_config_uniform)) }));
|
sg_apply_uniforms(UB_dof_config, *(sg_range.{ ptr = *dof_config_uniform, size = size_of(type_of(dof_config_uniform)) }));
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
Camera :: struct {
|
Camera :: struct {
|
||||||
fov : float = 75.0 * 3.141 / 180.0;
|
fov : float = 75.0 * 3.141 / 180.0;
|
||||||
near : float = 0.5;
|
near : float = 1.0;
|
||||||
far : float = 2000.0;
|
far : float = 20.0;
|
||||||
position : Vector3 = .{2, 2, 2};
|
position : Vector3 = .{2, 2, 2};
|
||||||
target : Vector3 = .{0, 0, 0};
|
target : Vector3 = .{0, 0, 0};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,8 @@
|
|||||||
DEBUG_LINE_MAX :: 65536;
|
DEBUG_LINE_MAX :: 65536;
|
||||||
DEBUG_OVERLAY_LINE_MAX :: 8192;
|
|
||||||
|
|
||||||
// One record per line, uploaded as instance data: the line pipeline expands each
|
g_debug_line_verts : [DEBUG_LINE_MAX * 2 * 7]float;
|
||||||
// into a screen-space quad so lines can have a real pixel width.
|
|
||||||
Debug_Line :: struct {
|
|
||||||
a : Vector3;
|
|
||||||
b : Vector3;
|
|
||||||
color : Vector4;
|
|
||||||
width : float; // in pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_LINE_DEFAULT_WIDTH :: 1.0;
|
|
||||||
|
|
||||||
g_debug_lines : [DEBUG_LINE_MAX]Debug_Line;
|
|
||||||
g_debug_line_count : int;
|
g_debug_line_count : int;
|
||||||
|
|
||||||
// Overlay lines ignore the depth buffer, so editor gizmos stay visible through
|
|
||||||
// terrain. They are UI rather than debug visualization, so they are not gated
|
|
||||||
// on debug_draw_enabled.
|
|
||||||
g_overlay_lines : [DEBUG_OVERLAY_LINE_MAX]Debug_Line;
|
|
||||||
g_overlay_line_count : int;
|
|
||||||
|
|
||||||
debug_draw_enabled : bool = !FLAG_RELEASE_BUILD;
|
debug_draw_enabled : bool = !FLAG_RELEASE_BUILD;
|
||||||
debug_draw_grid : bool = true;
|
debug_draw_grid : bool = true;
|
||||||
debug_draw_vectors : bool = true;
|
debug_draw_vectors : bool = true;
|
||||||
@ -42,35 +24,27 @@ toggle_debug_colliders :: () {
|
|||||||
debug_draw_colliders = !debug_draw_colliders;
|
debug_draw_colliders = !debug_draw_colliders;
|
||||||
} @Command
|
} @Command
|
||||||
|
|
||||||
debug_line :: (a: Vector3, b: Vector3, col: Vector4, width: float = DEBUG_LINE_DEFAULT_WIDTH) {
|
debug_line :: (a: Vector3, b: Vector3, col: Vector4) {
|
||||||
if !debug_draw_enabled then return;
|
if !debug_draw_enabled then return;
|
||||||
if g_debug_line_count >= DEBUG_LINE_MAX then return;
|
if g_debug_line_count >= DEBUG_LINE_MAX then return;
|
||||||
g_debug_lines[g_debug_line_count] = .{a, b, col, width};
|
base := g_debug_line_count * 14;
|
||||||
|
g_debug_line_verts[base + 0] = a.x;
|
||||||
|
g_debug_line_verts[base + 1] = a.y;
|
||||||
|
g_debug_line_verts[base + 2] = a.z;
|
||||||
|
g_debug_line_verts[base + 3] = col.x;
|
||||||
|
g_debug_line_verts[base + 4] = col.y;
|
||||||
|
g_debug_line_verts[base + 5] = col.z;
|
||||||
|
g_debug_line_verts[base + 6] = col.w;
|
||||||
|
g_debug_line_verts[base + 7] = b.x;
|
||||||
|
g_debug_line_verts[base + 8] = b.y;
|
||||||
|
g_debug_line_verts[base + 9] = b.z;
|
||||||
|
g_debug_line_verts[base + 10] = col.x;
|
||||||
|
g_debug_line_verts[base + 11] = col.y;
|
||||||
|
g_debug_line_verts[base + 12] = col.z;
|
||||||
|
g_debug_line_verts[base + 13] = col.w;
|
||||||
g_debug_line_count += 1;
|
g_debug_line_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_line_overlay :: (a: Vector3, b: Vector3, col: Vector4, width: float = DEBUG_LINE_DEFAULT_WIDTH) {
|
|
||||||
if g_overlay_line_count >= DEBUG_OVERLAY_LINE_MAX then return;
|
|
||||||
g_overlay_lines[g_overlay_line_count] = .{a, b, col, width};
|
|
||||||
g_overlay_line_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_aabb_3d_overlay :: (mn: Vector3, mx: Vector3, col: Vector4, width: float = DEBUG_LINE_DEFAULT_WIDTH) {
|
|
||||||
corner :: (mn: Vector3, mx: Vector3, i: int) -> Vector3 {
|
|
||||||
return .{ ifx i & 1 then mx.x else mn.x,
|
|
||||||
ifx i & 2 then mx.y else mn.y,
|
|
||||||
ifx i & 4 then mx.z else mn.z };
|
|
||||||
}
|
|
||||||
// Every pair of corners differing in exactly one bit is an edge of the box.
|
|
||||||
for i: 0..7 {
|
|
||||||
for bit: int.[1, 2, 4] {
|
|
||||||
j := i | bit;
|
|
||||||
if j == i then continue;
|
|
||||||
debug_line_overlay(corner(mn, mx, i), corner(mn, mx, j), col, width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_vector :: (origin: Vector3, vec: Vector3, col: Vector4) {
|
debug_vector :: (origin: Vector3, vec: Vector3, col: Vector4) {
|
||||||
if !debug_draw_enabled || !debug_draw_vectors then return;
|
if !debug_draw_enabled || !debug_draw_vectors then return;
|
||||||
tip := origin + vec;
|
tip := origin + vec;
|
||||||
@ -152,34 +126,21 @@ debug_aabb_3d :: (mn: Vector3, mx: Vector3, col: Vector4) {
|
|||||||
debug_line(.{mx.x, mn.y, mx.z}, .{mx.x, mx.y, mx.z}, col);
|
debug_line(.{mx.x, mn.y, mx.z}, .{mx.x, mx.y, mx.z}, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
flush_debug_lines :: (pipe: *Pipeline_Binding, lines: []Debug_Line, count: *int, params: *Debugline_Vs_Params) {
|
debug_draw_flush_gpu :: () {
|
||||||
if count.* == 0 then return;
|
if g_debug_line_count == 0 then return;
|
||||||
// Buffer 0 is the shared static quad; buffer 1 is this pipeline's instances.
|
|
||||||
sg_update_buffer(
|
sg_update_buffer(
|
||||||
pipe.bind.vertex_buffers[1],
|
gPipelines.debugline.bind.vertex_buffers[0],
|
||||||
*(sg_range.{
|
*(sg_range.{
|
||||||
ptr = lines.data,
|
ptr = g_debug_line_verts.data,
|
||||||
size = cast(u64)(count.* * size_of(Debug_Line)),
|
size = cast(u64)(g_debug_line_count * 14 * size_of(float)),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
sg_apply_pipeline(pipe.pipeline);
|
mvp := create_viewproj(*camera);
|
||||||
sg_apply_bindings(*pipe.bind);
|
|
||||||
sg_apply_uniforms(UB_debugline_vs_params, *(sg_range.{ ptr = params, size = size_of(Debugline_Vs_Params) }));
|
|
||||||
sg_draw(0, 6, xx count.*);
|
|
||||||
count.* = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_draw_flush_gpu :: () {
|
|
||||||
if g_debug_line_count == 0 && g_overlay_line_count == 0 then return;
|
|
||||||
|
|
||||||
w, h := get_window_size();
|
|
||||||
mvp := create_viewproj(*camera);
|
|
||||||
params : Debugline_Vs_Params;
|
params : Debugline_Vs_Params;
|
||||||
params.mvp = mvp.floats;
|
params.mvp = mvp.floats;
|
||||||
params.viewport = .[cast(float)w, cast(float)h, 0, 0];
|
sg_apply_pipeline(gPipelines.debugline.pipeline);
|
||||||
|
sg_apply_bindings(*gPipelines.debugline.bind);
|
||||||
flush_debug_lines(*gPipelines.debugline, g_debug_lines, *g_debug_line_count, *params);
|
sg_apply_uniforms(UB_debugline_vs_params, *(sg_range.{ ptr = *params, size = size_of(Debugline_Vs_Params) }));
|
||||||
flush_debug_lines(*gPipelines.debugline_overlay, g_overlay_lines, *g_overlay_line_count, *params);
|
sg_draw(0, xx (g_debug_line_count * 2), 1);
|
||||||
|
g_debug_line_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#assert size_of(Debug_Line) == 44 "Debug_Line must stay tightly packed: the pipeline layout hardcodes its field offsets.";
|
|
||||||
|
|||||||
@ -102,9 +102,11 @@ create_world_rendering_tasks :: (world: *World, camera: Camera, plane_height: fl
|
|||||||
|
|
||||||
gmin := group.bounding_min;
|
gmin := group.bounding_min;
|
||||||
gmax := group.bounding_max;
|
gmax := group.bounding_max;
|
||||||
|
gavg := group.average_pos;
|
||||||
|
|
||||||
gmin += Vector3.{cast(float)chunk.coord.x * 32, cast(float)chunk.coord.y * 32, cast(float)chunk.coord.z * 32};
|
gmin += Vector3.{cast(float)chunk.coord.x * 32, cast(float)chunk.coord.y * 32, cast(float)chunk.coord.z * 32};
|
||||||
gmax += Vector3.{cast(float)chunk.coord.x * 32, cast(float)chunk.coord.y * 32, cast(float)chunk.coord.z * 32};
|
gmax += Vector3.{cast(float)chunk.coord.x * 32, cast(float)chunk.coord.y * 32, cast(float)chunk.coord.z * 32};
|
||||||
|
gavg += Vector3.{cast(float)chunk.coord.x * 32, cast(float)chunk.coord.y * 32, cast(float)chunk.coord.z * 32};
|
||||||
in_cam := aabb_in_frustum(cam_planes, gmin, gmax);
|
in_cam := aabb_in_frustum(cam_planes, gmin, gmax);
|
||||||
in_reflect := aabb_in_frustum(reflect_planes, gmin, gmax);
|
in_reflect := aabb_in_frustum(reflect_planes, gmin, gmax);
|
||||||
in_shad := aabb_in_frustum(shadow_planes, gmin, gmax);
|
in_shad := aabb_in_frustum(shadow_planes, gmin, gmax);
|
||||||
@ -112,7 +114,7 @@ create_world_rendering_tasks :: (world: *World, camera: Camera, plane_height: fl
|
|||||||
|
|
||||||
if !vis then continue;
|
if !vis then continue;
|
||||||
|
|
||||||
diff := group.average_pos - camera.position;
|
diff := gavg - camera.position;
|
||||||
dist := length(diff);
|
dist := length(diff);
|
||||||
lod_index : s32 = 0;
|
lod_index : s32 = 0;
|
||||||
for lod_dist : LOD_DISTANCES {
|
for lod_dist : LOD_DISTANCES {
|
||||||
|
|||||||
@ -113,8 +113,6 @@ gPipelines : struct {
|
|||||||
sh_irradiance: Pipeline_Binding;
|
sh_irradiance: Pipeline_Binding;
|
||||||
|
|
||||||
debugline : Pipeline_Binding;
|
debugline : Pipeline_Binding;
|
||||||
// Same shader, but depth testing disabled so editor gizmos draw on top.
|
|
||||||
debugline_overlay : Pipeline_Binding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create_final_image :: () {
|
create_final_image :: () {
|
||||||
@ -1389,12 +1387,12 @@ create_ssao_images :: () {
|
|||||||
g_postprocess_attach_b = sg_make_attachments(*attachmentsDescB);
|
g_postprocess_attach_b = sg_make_attachments(*attachmentsDescB);
|
||||||
|
|
||||||
bloom_levels := sg_image.[g_bloom_down_1, g_bloom_down_2, g_bloom_down_3, g_bloom_down_4, g_bloom_up_3, g_bloom_up_2, g_bloom_up_1];
|
bloom_levels := sg_image.[g_bloom_down_1, g_bloom_down_2, g_bloom_down_3, g_bloom_down_4, g_bloom_up_3, g_bloom_up_2, g_bloom_up_1];
|
||||||
for * bloom_levels { if it.id != INVALID_ID then sg_destroy_image((.*)it); }
|
for * bloom_levels { if it.id != INVALID_ID then sg_destroy_image(<<it); }
|
||||||
|
|
||||||
make_bloom_level :: (img: *sg_image, attach: *sg_attachments, width: s32, height: s32) {
|
make_bloom_level :: (img: *sg_image, attach: *sg_attachments, width: s32, height: s32) {
|
||||||
(.*)img = sg_make_image(*(sg_image_desc.{ width=width, height=height, pixel_format=.RGBA16F, render_target=true, sample_count=1 }));
|
<<img = sg_make_image(*(sg_image_desc.{ width=width, height=height, pixel_format=.RGBA16F, render_target=true, sample_count=1 }));
|
||||||
sg_destroy_attachments((.*)attach);
|
sg_destroy_attachments(<<attach);
|
||||||
(.*)attach = sg_make_attachments(*(sg_attachments_desc.{ colors[0].image = (.*)img }));
|
<<attach = sg_make_attachments(*(sg_attachments_desc.{ colors[0].image = <<img }));
|
||||||
}
|
}
|
||||||
|
|
||||||
make_bloom_level(*g_bloom_down_1, *g_bloom_down_1_attach, w/2, h/2);
|
make_bloom_level(*g_bloom_down_1, *g_bloom_down_1_attach, w/2, h/2);
|
||||||
@ -1696,45 +1694,20 @@ create_particle_pipeline :: () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create_debugline_pipeline :: () {
|
create_debugline_pipeline :: () {
|
||||||
// The two corner coordinates of a quad: x picks the side of the line, y picks
|
|
||||||
// the end. The vertex shader turns those into a screen-space widened line.
|
|
||||||
corners := float.[
|
|
||||||
-1, 0, 1, 0, 1, 1,
|
|
||||||
-1, 0, 1, 1, -1, 1,
|
|
||||||
];
|
|
||||||
quad_desc := sg_buffer_desc.{
|
|
||||||
data = .{ ptr = corners.data, size = size_of(type_of(corners)) },
|
|
||||||
label = "debug_line_quad",
|
|
||||||
};
|
|
||||||
quad_buffer := sg_make_buffer(*quad_desc);
|
|
||||||
|
|
||||||
buf_desc := sg_buffer_desc.{
|
buf_desc := sg_buffer_desc.{
|
||||||
size = DEBUG_LINE_MAX * size_of(Debug_Line),
|
size = DEBUG_LINE_MAX * 2 * 7 * size_of(float),
|
||||||
usage = .DYNAMIC,
|
usage = .DYNAMIC,
|
||||||
label = "debug_line_instances",
|
label = "debug_line_verts",
|
||||||
};
|
};
|
||||||
gPipelines.debugline.bind.vertex_buffers[0] = quad_buffer;
|
gPipelines.debugline.bind.vertex_buffers[0] = sg_make_buffer(*buf_desc);
|
||||||
gPipelines.debugline.bind.vertex_buffers[1] = sg_make_buffer(*buf_desc);
|
|
||||||
|
|
||||||
overlay_buf_desc := sg_buffer_desc.{
|
|
||||||
size = DEBUG_OVERLAY_LINE_MAX * size_of(Debug_Line),
|
|
||||||
usage = .DYNAMIC,
|
|
||||||
label = "debug_line_overlay_instances",
|
|
||||||
};
|
|
||||||
gPipelines.debugline_overlay.bind.vertex_buffers[0] = quad_buffer;
|
|
||||||
gPipelines.debugline_overlay.bind.vertex_buffers[1] = sg_make_buffer(*overlay_buf_desc);
|
|
||||||
|
|
||||||
pipeline : sg_pipeline_desc;
|
pipeline : sg_pipeline_desc;
|
||||||
shader_desc := debugline_shader_desc(sg_query_backend());
|
shader_desc := debugline_shader_desc(sg_query_backend());
|
||||||
pipeline.shader = sg_make_shader(*shader_desc);
|
pipeline.shader = sg_make_shader(*shader_desc);
|
||||||
pipeline.layout.buffers[0].stride = 2 * size_of(float);
|
pipeline.primitive_type = .LINES;
|
||||||
pipeline.layout.buffers[1].stride = size_of(Debug_Line);
|
pipeline.layout.buffers[0].stride = 28;
|
||||||
pipeline.layout.buffers[1].step_func = .PER_INSTANCE;
|
pipeline.layout.attrs[ATTR_debugline_a_pos] = .{ format = .FLOAT3, buffer_index = 0, offset = 0 };
|
||||||
pipeline.layout.attrs[ATTR_debugline_a_corner] = .{ format = .FLOAT2, buffer_index = 0, offset = 0 };
|
pipeline.layout.attrs[ATTR_debugline_a_col] = .{ format = .FLOAT4, buffer_index = 0, offset = 12 };
|
||||||
pipeline.layout.attrs[ATTR_debugline_a_pos_a] = .{ format = .FLOAT3, buffer_index = 1, offset = 0 };
|
|
||||||
pipeline.layout.attrs[ATTR_debugline_a_pos_b] = .{ format = .FLOAT3, buffer_index = 1, offset = 12 };
|
|
||||||
pipeline.layout.attrs[ATTR_debugline_a_col] = .{ format = .FLOAT4, buffer_index = 1, offset = 24 };
|
|
||||||
pipeline.layout.attrs[ATTR_debugline_a_width] = .{ format = .FLOAT, buffer_index = 1, offset = 40 };
|
|
||||||
pipeline.depth = .{
|
pipeline.depth = .{
|
||||||
write_enabled = false,
|
write_enabled = false,
|
||||||
compare = .LESS_EQUAL,
|
compare = .LESS_EQUAL,
|
||||||
@ -1747,13 +1720,4 @@ create_debugline_pipeline :: () {
|
|||||||
pipeline.colors[0] = color_state;
|
pipeline.colors[0] = color_state;
|
||||||
pipeline.label = "debugline_pipeline";
|
pipeline.label = "debugline_pipeline";
|
||||||
gPipelines.debugline.pipeline = sg_make_pipeline(*pipeline);
|
gPipelines.debugline.pipeline = sg_make_pipeline(*pipeline);
|
||||||
|
|
||||||
pipeline.depth.compare = .ALWAYS;
|
|
||||||
pipeline.colors[0].blend = .{
|
|
||||||
enabled = true,
|
|
||||||
src_factor_rgb = .SRC_ALPHA,
|
|
||||||
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
|
||||||
};
|
|
||||||
pipeline.label = "debugline_overlay_pipeline";
|
|
||||||
gPipelines.debugline_overlay.pipeline = sg_make_pipeline(*pipeline);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
disable_dof_post_process : bool = false;
|
|
||||||
|
|
||||||
Post_Process :: struct {
|
Post_Process :: struct {
|
||||||
exposure : float = 1.0; @Slider,0,3,0.1;
|
exposure : float = 1.0; @Slider,0,3,0.1;
|
||||||
contrast : float = 1.0; @Slider,0,3,0.1;
|
contrast : float = 1.0; @Slider,0,3,0.1;
|
||||||
|
|||||||
@ -7,7 +7,6 @@ Rendering_Task_Type :: enum {
|
|||||||
SET_CAMERA;
|
SET_CAMERA;
|
||||||
SET_LIGHT;
|
SET_LIGHT;
|
||||||
TRILE; // We need to add an ability to invalidate buffer instead of updating it constantly. Also probably have a buffer for static world triles and one for moving ones.
|
TRILE; // We need to add an ability to invalidate buffer instead of updating it constantly. Also probably have a buffer for static world triles and one for moving ones.
|
||||||
TRILE_DYNAMIC;
|
|
||||||
TRILE_RDM;
|
TRILE_RDM;
|
||||||
TRIXELS;
|
TRIXELS;
|
||||||
BILLBOARD;
|
BILLBOARD;
|
||||||
@ -47,7 +46,7 @@ Rendering_Task_Billboard :: struct {
|
|||||||
t.type = .BILLBOARD;
|
t.type = .BILLBOARD;
|
||||||
position : Vector3;
|
position : Vector3;
|
||||||
animation : *Animation;
|
animation : *Animation;
|
||||||
frame : s32;
|
frame : s32;
|
||||||
flipX : bool;
|
flipX : bool;
|
||||||
flipY : bool;
|
flipY : bool;
|
||||||
faceDir : Vector3 = .{-100, -100, -100};
|
faceDir : Vector3 = .{-100, -100, -100};
|
||||||
@ -66,19 +65,6 @@ Rendering_Task_Trile :: struct {
|
|||||||
lod_index : s32 = 0; // 0 = full detail, 1 = 4^3 LOD, 2 = 2^3 LOD
|
lod_index : s32 = 0; // 0 = full detail, 1 = 4^3 LOD, 2 = 2^3 LOD
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triles at arbitrary float world positions (entities, moving things).
|
|
||||||
// Shares the instance buffer and pipelines with the static TRILE path, so it
|
|
||||||
// participates in main/gbuffer/shadow/reflection just like world triles.
|
|
||||||
// chunk_key is the enclosing chunk, used only for SH probe lookup.
|
|
||||||
Rendering_Task_Trile_Dynamic :: struct {
|
|
||||||
#as using t : Rendering_Task;
|
|
||||||
t.type = .TRILE_DYNAMIC;
|
|
||||||
trile : string;
|
|
||||||
positions : []Vector4; // xyz = world position, w = orientation 0..23
|
|
||||||
chunk_key : Chunk_Key;
|
|
||||||
worldConf : *World_Config;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rendering_Task_Trile_RDM :: struct {
|
Rendering_Task_Trile_RDM :: struct {
|
||||||
#as using t : Rendering_Task;
|
#as using t : Rendering_Task;
|
||||||
t.type = .TRILE_RDM;
|
t.type = .TRILE_RDM;
|
||||||
@ -112,9 +98,6 @@ Rendering_Task_Particles_Buffer :: struct {
|
|||||||
#as using t : Rendering_Task;
|
#as using t : Rendering_Task;
|
||||||
t.type = .PARTICLES_BUFFER;
|
t.type = .PARTICLES_BUFFER;
|
||||||
total_count : s32;
|
total_count : s32;
|
||||||
pos_size : [MAX_PARTICLES]Vector4;
|
|
||||||
uv_rects : [MAX_PARTICLES]Vector4;
|
|
||||||
colors : [MAX_PARTICLES]Vector4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rendering_Task_Set_Camera :: struct {
|
Rendering_Task_Set_Camera :: struct {
|
||||||
@ -191,22 +174,6 @@ tasks_to_commands :: () {
|
|||||||
array_add(*render_command_buckets.gbuffer, drawPositionsCmd);
|
array_add(*render_command_buckets.gbuffer, drawPositionsCmd);
|
||||||
array_add(*render_command_buckets.shadow, drawPositionsCmd);
|
array_add(*render_command_buckets.shadow, drawPositionsCmd);
|
||||||
}
|
}
|
||||||
case .TRILE_DYNAMIC;
|
|
||||||
dynTask := (cast(*Rendering_Task_Trile_Dynamic)it);
|
|
||||||
addF32Cmd := New(Render_Command_Add_Trile_Positions_F32,, temp);
|
|
||||||
addF32Cmd.positions = dynTask.positions;
|
|
||||||
array_add(*render_command_buckets.setup, addF32Cmd);
|
|
||||||
drawDynCmd := New(Render_Command_Draw_Trile_Positions,, temp);
|
|
||||||
drawDynCmd.trile = dynTask.trile;
|
|
||||||
drawDynCmd.chunk_key = dynTask.chunk_key;
|
|
||||||
drawDynCmd.amount = cast(s32)dynTask.positions.count;
|
|
||||||
drawDynCmd.conf = dynTask.worldConf;
|
|
||||||
drawDynCmd.offset_index = trile_add_counter;
|
|
||||||
trile_add_counter += 1;
|
|
||||||
array_add(*render_command_buckets.reflection, drawDynCmd);
|
|
||||||
array_add(*render_command_buckets.main, drawDynCmd);
|
|
||||||
array_add(*render_command_buckets.gbuffer, drawDynCmd);
|
|
||||||
array_add(*render_command_buckets.shadow, drawDynCmd);
|
|
||||||
case .TRILE_RDM;
|
case .TRILE_RDM;
|
||||||
rdmTask := (cast(*Rendering_Task_Trile_RDM)it);
|
rdmTask := (cast(*Rendering_Task_Trile_RDM)it);
|
||||||
addCmd := New(Render_Command_Add_Trile_RDM_Position,, temp);
|
addCmd := New(Render_Command_Add_Trile_RDM_Position,, temp);
|
||||||
@ -246,9 +213,6 @@ tasks_to_commands :: () {
|
|||||||
bufTask := cast(*Rendering_Task_Particles_Buffer)it;
|
bufTask := cast(*Rendering_Task_Particles_Buffer)it;
|
||||||
uploadCmd := New(Render_Command_Update_Particles,, temp);
|
uploadCmd := New(Render_Command_Update_Particles,, temp);
|
||||||
uploadCmd.total_count = bufTask.total_count;
|
uploadCmd.total_count = bufTask.total_count;
|
||||||
memcpy(uploadCmd.pos_size.data, bufTask.pos_size.data, bufTask.total_count * size_of(Vector4));
|
|
||||||
memcpy(uploadCmd.uv_rects.data, bufTask.uv_rects.data, bufTask.total_count * size_of(Vector4));
|
|
||||||
memcpy(uploadCmd.colors.data, bufTask.colors.data, bufTask.total_count * size_of(Vector4));
|
|
||||||
array_add(*render_command_buckets.setup, uploadCmd);
|
array_add(*render_command_buckets.setup, uploadCmd);
|
||||||
case .PARTICLES;
|
case .PARTICLES;
|
||||||
particleTask := cast(*Rendering_Task_Particles)it;
|
particleTask := cast(*Rendering_Task_Particles)it;
|
||||||
|
|||||||
@ -1,162 +0,0 @@
|
|||||||
// Packs are built from a throwaway copy of the resource trees, not from the
|
|
||||||
// trees themselves:
|
|
||||||
//
|
|
||||||
// wipe .build/generated -> copy the trees in -> process the copy -> pack it
|
|
||||||
//
|
|
||||||
// Processing (currently just the Aseprite export) writes its output into the
|
|
||||||
// copy, so the real resource trees only ever hold sources. Rebuilding the copy
|
|
||||||
// from scratch every time is what keeps this simple: there is no stale output to
|
|
||||||
// detect and nothing to prune, because a deleted source is simply not there.
|
|
||||||
//
|
|
||||||
// Used by both the build (meta/pack.jai) and the runtime hot-reloader
|
|
||||||
// (pack_hotreload.jai), so they process resources identically.
|
|
||||||
|
|
||||||
#import "Basic";
|
|
||||||
#import "String";
|
|
||||||
#import "Process";
|
|
||||||
#import "File";
|
|
||||||
#import "File_Utilities";
|
|
||||||
|
|
||||||
// Not ".build/generated/resources": pack.jai derives the pack name by splitting
|
|
||||||
// on the *first* "/resources/", so a "resources" segment here would shadow the
|
|
||||||
// real one and yield pack "game" instead of "game_core".
|
|
||||||
MIRROR_ROOT :: ".build/generated";
|
|
||||||
|
|
||||||
ASEPRITE_SOURCE_EXTENSIONS :: string.["aseprite", "ase"];
|
|
||||||
|
|
||||||
is_aseprite_source :: (short_name: string) -> bool {
|
|
||||||
ok, base, extension := split_from_right(short_name, #char ".");
|
|
||||||
if !ok return false;
|
|
||||||
for ASEPRITE_SOURCE_EXTENSIONS if extension == it return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wipes and rebuilds the mirror, runs the processing steps over it, and returns
|
|
||||||
// the mirrored resource roots to pack in place of the real ones.
|
|
||||||
prepare_resource_mirror :: (use_test_game: bool) -> [] string {
|
|
||||||
run_command("rm", "-rf", MIRROR_ROOT);
|
|
||||||
|
|
||||||
game_tree := ifx use_test_game then "test_game" else "game";
|
|
||||||
|
|
||||||
roots: [..] string;
|
|
||||||
roots.allocator = temp;
|
|
||||||
array_add(*roots, copy_tree_into("./resources", MIRROR_ROOT));
|
|
||||||
array_add(*roots, copy_tree_into(tprint("./%/resources", game_tree), tprint("%/%", MIRROR_ROOT, game_tree)));
|
|
||||||
|
|
||||||
export_aseprite_sheets(roots);
|
|
||||||
|
|
||||||
return roots;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "cp -r ./game/resources .build/generated/game" -> ".build/generated/game/resources"
|
|
||||||
copy_tree_into :: (source: string, destination_parent: string) -> string {
|
|
||||||
make_directory_if_it_does_not_exist(destination_parent, recursive = true);
|
|
||||||
run_command("cp", "-r", source, destination_parent);
|
|
||||||
|
|
||||||
ok, parent, name := split_from_right(source, #char "/");
|
|
||||||
return tprint("%/%", destination_parent, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aseprite's CLI, reproducing byte for byte what its "Export Sprite Sheet"
|
|
||||||
// dialog was producing for the sheets that used to be checked in.
|
|
||||||
// --format json-array and --list-tags are load-bearing: asset_manager.jai parses
|
|
||||||
// `frames` as an array and drives Animation off `meta.frameTags`.
|
|
||||||
ASEPRITE_EXPORT_FLAGS :: string.[
|
|
||||||
"--format", "json-array",
|
|
||||||
"--list-tags",
|
|
||||||
"--list-layers",
|
|
||||||
"--list-slices",
|
|
||||||
];
|
|
||||||
|
|
||||||
// "player.aseprite" -> player.sheet.png / player.sheet.json
|
|
||||||
// "anim.sheet.aseprite" -> anim.sheet.png / anim.sheet.json
|
|
||||||
//
|
|
||||||
// The doubled-up ".sheet" is dropped because asset_manager.jai keys sheets on
|
|
||||||
// everything before the *first* dot, so "anim.sheet.sheet.png" would load as a
|
|
||||||
// sheet named "anim" with extension "sheet.sheet.png" and be rejected.
|
|
||||||
sheet_output_paths :: (source: string) -> (png: string, json: string) {
|
|
||||||
ok, stem := split_from_right(source, #char ".");
|
|
||||||
if ends_with(stem, ".sheet") {
|
|
||||||
stem.count -= ".sheet".count;
|
|
||||||
}
|
|
||||||
return tprint("%.sheet.png", stem), tprint("%.sheet.json", stem);
|
|
||||||
}
|
|
||||||
|
|
||||||
find_aseprite_binary :: () -> (path: string, found: bool) {
|
|
||||||
candidates: [..] string;
|
|
||||||
candidates.allocator = temp;
|
|
||||||
|
|
||||||
#if OS == .LINUX || OS == .MACOS {
|
|
||||||
POSIX :: #import "POSIX";
|
|
||||||
read_env :: (name: string) -> string {
|
|
||||||
value := POSIX.getenv(name.data);
|
|
||||||
if !value return "";
|
|
||||||
return to_string(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ASEPRITE points the build at a different install without editing this.
|
|
||||||
override := read_env("ASEPRITE");
|
|
||||||
if override array_add(*candidates, override);
|
|
||||||
|
|
||||||
home := read_env("HOME");
|
|
||||||
if home array_add(*candidates, tprint("%/bin/aseprite", home));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if OS == .MACOS {
|
|
||||||
array_add(*candidates, "/Applications/Aseprite.app/Contents/MacOS/aseprite");
|
|
||||||
}
|
|
||||||
|
|
||||||
array_add(*candidates, "aseprite"); // whatever is on PATH
|
|
||||||
|
|
||||||
for candidates {
|
|
||||||
// Probing by running it also catches a path that exists but isn't executable.
|
|
||||||
result := run_command(it, "--version", capture_and_return_output = true);
|
|
||||||
if result.type == .EXITED && result.exit_code == 0 return it, true;
|
|
||||||
}
|
|
||||||
return "", false;
|
|
||||||
}
|
|
||||||
|
|
||||||
collect_aseprite_sources :: (root: string, sources: *[..] string) {
|
|
||||||
if !is_directory(root) return;
|
|
||||||
|
|
||||||
visit_files(root, true, sources, (info: *File_Visit_Info, sources: *[..] string) {
|
|
||||||
if info.is_directory return;
|
|
||||||
if !is_aseprite_source(info.short_name) return;
|
|
||||||
array_add(sources, copy_string(info.full_name));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exports every Aseprite file in the mirror to a sheet pair beside it. pack.jai
|
|
||||||
// skips the sources themselves, so only the exports end up in the pack.
|
|
||||||
export_aseprite_sheets :: (roots: [] string) {
|
|
||||||
sources: [..] string;
|
|
||||||
sources.allocator = temp;
|
|
||||||
for roots collect_aseprite_sources(it, *sources);
|
|
||||||
if !sources return;
|
|
||||||
|
|
||||||
aseprite, found := find_aseprite_binary();
|
|
||||||
if !found {
|
|
||||||
log_error("Aseprite export: % source file(s) to export but no aseprite binary was found.", sources.count);
|
|
||||||
log_error("Looked for $ASEPRITE, $HOME/bin/aseprite, and 'aseprite' on PATH.");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for source: sources {
|
|
||||||
png, json := sheet_output_paths(source);
|
|
||||||
|
|
||||||
args: [..] string;
|
|
||||||
args.allocator = temp;
|
|
||||||
array_add(*args, aseprite, "-b", source, "--sheet", png, "--data", json);
|
|
||||||
for flag: ASEPRITE_EXPORT_FLAGS array_add(*args, flag);
|
|
||||||
|
|
||||||
result, output, error := run_command(..args, capture_and_return_output = true);
|
|
||||||
if result.type != .EXITED || result.exit_code != 0 {
|
|
||||||
log_error("Aseprite export failed for %", source);
|
|
||||||
if output log_error(output,, logger = runtime_support_default_logger);
|
|
||||||
if error log_error(error,, logger = runtime_support_default_logger);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Aseprite: exported % sheet(s)\n", sources.count);
|
|
||||||
}
|
|
||||||
@ -13,55 +13,30 @@
|
|||||||
Vertex Shader: vs_debugline
|
Vertex Shader: vs_debugline
|
||||||
Fragment Shader: fs_debugline
|
Fragment Shader: fs_debugline
|
||||||
Attributes:
|
Attributes:
|
||||||
ATTR_debugline_a_corner => 0
|
ATTR_debugline_a_pos => 0
|
||||||
ATTR_debugline_a_pos_a => 1
|
ATTR_debugline_a_col => 1
|
||||||
ATTR_debugline_a_pos_b => 2
|
|
||||||
ATTR_debugline_a_col => 3
|
|
||||||
ATTR_debugline_a_width => 4
|
|
||||||
Bindings:
|
Bindings:
|
||||||
Uniform block 'debugline_vs_params':
|
Uniform block 'debugline_vs_params':
|
||||||
Jai struct: Debugline_Vs_Params
|
Jai struct: Debugline_Vs_Params
|
||||||
Bind slot: UB_debugline_vs_params => 0
|
Bind slot: UB_debugline_vs_params => 0
|
||||||
*/
|
*/
|
||||||
ATTR_debugline_a_corner :: 0;
|
ATTR_debugline_a_pos :: 0;
|
||||||
ATTR_debugline_a_pos_a :: 1;
|
ATTR_debugline_a_col :: 1;
|
||||||
ATTR_debugline_a_pos_b :: 2;
|
|
||||||
ATTR_debugline_a_col :: 3;
|
|
||||||
ATTR_debugline_a_width :: 4;
|
|
||||||
UB_debugline_vs_params :: 0;
|
UB_debugline_vs_params :: 0;
|
||||||
Debugline_Vs_Params :: struct {
|
Debugline_Vs_Params :: struct {
|
||||||
mvp: [16]float;
|
mvp: [16]float;
|
||||||
viewport: [4]float;
|
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
#version 430
|
#version 430
|
||||||
|
|
||||||
uniform vec4 debugline_vs_params[5];
|
uniform vec4 debugline_vs_params[4];
|
||||||
layout(location = 1) in vec3 a_pos_a;
|
layout(location = 0) in vec3 a_pos;
|
||||||
layout(location = 2) in vec3 a_pos_b;
|
|
||||||
layout(location = 0) in vec2 a_corner;
|
|
||||||
layout(location = 4) in float a_width;
|
|
||||||
layout(location = 0) out vec4 v_col;
|
layout(location = 0) out vec4 v_col;
|
||||||
layout(location = 3) in vec4 a_col;
|
layout(location = 1) in vec4 a_col;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
mat4 _18 = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]);
|
gl_Position = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]) * vec4(a_pos, 1.0);
|
||||||
vec4 _28 = _18 * vec4(a_pos_a, 1.0);
|
|
||||||
vec4 _38 = _18 * vec4(a_pos_b, 1.0);
|
|
||||||
vec2 _78 = ((_38.xy / vec2(max(_38.w, 9.9999997473787516355514526367188e-05))) * debugline_vs_params[4].xy) - ((_28.xy / vec2(max(_28.w, 9.9999997473787516355514526367188e-05))) * debugline_vs_params[4].xy);
|
|
||||||
float _81 = length(_78);
|
|
||||||
vec2 _86;
|
|
||||||
if (_81 > 9.9999997473787516355514526367188e-05)
|
|
||||||
{
|
|
||||||
_86 = _78 / vec2(_81);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_86 = vec2(1.0, 0.0);
|
|
||||||
}
|
|
||||||
vec4 _115 = mix(_28, _38, vec4(a_corner.y));
|
|
||||||
gl_Position = _115 + vec4((((vec2(-_86.y, _86.x) * a_corner.x) * ((a_width * 0.5) + 0.5)) / debugline_vs_params[4].xy) * _115.w, 0.0, 0.0);
|
|
||||||
v_col = a_col;
|
v_col = a_col;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,74 +44,26 @@ Debugline_Vs_Params :: struct {
|
|||||||
vs_debugline_source_glsl430 := u8.[
|
vs_debugline_source_glsl430 := u8.[
|
||||||
0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x33,0x30,0x0a,0x0a,0x75,0x6e,
|
0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x33,0x30,0x0a,0x0a,0x75,0x6e,
|
||||||
0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x64,0x65,0x62,0x75,0x67,
|
0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x64,0x65,0x62,0x75,0x67,
|
||||||
0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,
|
0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,
|
||||||
0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,
|
0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,
|
||||||
0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,
|
0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,
|
||||||
0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,
|
0x61,0x5f,0x70,0x6f,0x73,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,
|
||||||
0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,
|
0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,
|
||||||
0x20,0x76,0x65,0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x62,0x3b,0x0a,0x6c,
|
0x76,0x65,0x63,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x6c,0x61,0x79,0x6f,
|
||||||
0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,
|
0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,
|
||||||
0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x61,0x5f,0x63,0x6f,
|
0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,
|
||||||
0x72,0x6e,0x65,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,
|
0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,
|
||||||
0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x34,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,
|
|
||||||
0x6f,0x61,0x74,0x20,0x61,0x5f,0x77,0x69,0x64,0x74,0x68,0x3b,0x0a,0x6c,0x61,0x79,
|
|
||||||
0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,
|
|
||||||
0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,
|
|
||||||
0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,
|
|
||||||
0x6e,0x20,0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x61,
|
|
||||||
0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,
|
|
||||||
0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x74,0x34,0x20,0x5f,0x31,
|
|
||||||
0x38,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,
|
|
||||||
0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,
|
|
||||||
0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,
|
|
||||||
0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,
|
|
||||||
0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,
|
|
||||||
0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,
|
|
||||||
0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,
|
|
||||||
0x63,0x34,0x20,0x5f,0x32,0x38,0x20,0x3d,0x20,0x5f,0x31,0x38,0x20,0x2a,0x20,0x76,
|
|
||||||
0x65,0x63,0x34,0x28,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x2c,0x20,0x31,0x2e,0x30,
|
|
||||||
0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x5f,0x33,0x38,0x20,
|
|
||||||
0x3d,0x20,0x5f,0x31,0x38,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x61,0x5f,0x70,
|
|
||||||
0x6f,0x73,0x5f,0x62,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,
|
|
||||||
0x76,0x65,0x63,0x32,0x20,0x5f,0x37,0x38,0x20,0x3d,0x20,0x28,0x28,0x5f,0x33,0x38,
|
|
||||||
0x2e,0x78,0x79,0x20,0x2f,0x20,0x76,0x65,0x63,0x32,0x28,0x6d,0x61,0x78,0x28,0x5f,
|
|
||||||
0x33,0x38,0x2e,0x77,0x2c,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,0x39,0x37,0x34,
|
|
||||||
0x37,0x33,0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,0x34,0x35,0x32,
|
|
||||||
0x36,0x33,0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x29,0x29,0x20,0x2a,
|
|
||||||
0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,
|
|
||||||
0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2e,0x78,0x79,0x29,0x20,0x2d,0x20,0x28,0x28,
|
|
||||||
0x5f,0x32,0x38,0x2e,0x78,0x79,0x20,0x2f,0x20,0x76,0x65,0x63,0x32,0x28,0x6d,0x61,
|
|
||||||
0x78,0x28,0x5f,0x32,0x38,0x2e,0x77,0x2c,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,
|
|
||||||
0x39,0x37,0x34,0x37,0x33,0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,
|
|
||||||
0x34,0x35,0x32,0x36,0x33,0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x29,
|
|
||||||
0x29,0x20,0x2a,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,
|
|
||||||
0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2e,0x78,0x79,0x29,0x3b,0x0a,
|
|
||||||
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x38,0x31,0x20,0x3d,0x20,
|
|
||||||
0x6c,0x65,0x6e,0x67,0x74,0x68,0x28,0x5f,0x37,0x38,0x29,0x3b,0x0a,0x20,0x20,0x20,
|
|
||||||
0x20,0x76,0x65,0x63,0x32,0x20,0x5f,0x38,0x36,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,
|
|
||||||
0x66,0x20,0x28,0x5f,0x38,0x31,0x20,0x3e,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,
|
|
||||||
0x39,0x37,0x34,0x37,0x33,0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,
|
|
||||||
0x34,0x35,0x32,0x36,0x33,0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x0a,
|
|
||||||
0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x38,
|
|
||||||
0x36,0x20,0x3d,0x20,0x5f,0x37,0x38,0x20,0x2f,0x20,0x76,0x65,0x63,0x32,0x28,0x5f,
|
|
||||||
0x38,0x31,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x65,
|
|
||||||
0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,
|
|
||||||
0x20,0x20,0x5f,0x38,0x36,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,
|
|
||||||
0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,
|
|
||||||
0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x5f,0x31,0x31,0x35,0x20,0x3d,0x20,0x6d,0x69,
|
|
||||||
0x78,0x28,0x5f,0x32,0x38,0x2c,0x20,0x5f,0x33,0x38,0x2c,0x20,0x76,0x65,0x63,0x34,
|
|
||||||
0x28,0x61,0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,
|
|
||||||
0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,
|
0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,
|
||||||
0x20,0x5f,0x31,0x31,0x35,0x20,0x2b,0x20,0x76,0x65,0x63,0x34,0x28,0x28,0x28,0x28,
|
0x20,0x6d,0x61,0x74,0x34,0x28,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,
|
||||||
0x76,0x65,0x63,0x32,0x28,0x2d,0x5f,0x38,0x36,0x2e,0x79,0x2c,0x20,0x5f,0x38,0x36,
|
0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x64,0x65,
|
||||||
0x2e,0x78,0x29,0x20,0x2a,0x20,0x61,0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x2e,0x78,
|
0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,
|
||||||
0x29,0x20,0x2a,0x20,0x28,0x28,0x61,0x5f,0x77,0x69,0x64,0x74,0x68,0x20,0x2a,0x20,
|
0x73,0x5b,0x31,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,
|
||||||
0x30,0x2e,0x35,0x29,0x20,0x2b,0x20,0x30,0x2e,0x35,0x29,0x29,0x20,0x2f,0x20,0x64,
|
0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x64,0x65,
|
||||||
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,
|
||||||
0x6d,0x73,0x5b,0x34,0x5d,0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x5f,0x31,0x31,0x35,
|
0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x61,0x5f,0x70,
|
||||||
0x2e,0x77,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,0x0a,0x20,
|
0x6f,0x73,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x5f,
|
||||||
0x20,0x20,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x20,0x3d,0x20,0x61,0x5f,0x63,0x6f,0x6c,
|
0x63,0x6f,0x6c,0x20,0x3d,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x7d,0x0a,0x0a,
|
||||||
0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
0x00,
|
||||||
];
|
];
|
||||||
/*
|
/*
|
||||||
#version 430
|
#version 430
|
||||||
@ -164,32 +91,14 @@ fs_debugline_source_glsl430 := u8.[
|
|||||||
/*
|
/*
|
||||||
#version 300 es
|
#version 300 es
|
||||||
|
|
||||||
uniform vec4 debugline_vs_params[5];
|
uniform vec4 debugline_vs_params[4];
|
||||||
layout(location = 1) in vec3 a_pos_a;
|
layout(location = 0) in vec3 a_pos;
|
||||||
layout(location = 2) in vec3 a_pos_b;
|
|
||||||
layout(location = 0) in vec2 a_corner;
|
|
||||||
layout(location = 4) in float a_width;
|
|
||||||
out vec4 v_col;
|
out vec4 v_col;
|
||||||
layout(location = 3) in vec4 a_col;
|
layout(location = 1) in vec4 a_col;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
mat4 _18 = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]);
|
gl_Position = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]) * vec4(a_pos, 1.0);
|
||||||
vec4 _28 = _18 * vec4(a_pos_a, 1.0);
|
|
||||||
vec4 _38 = _18 * vec4(a_pos_b, 1.0);
|
|
||||||
vec2 _78 = ((_38.xy / vec2(max(_38.w, 9.9999997473787516355514526367188e-05))) * debugline_vs_params[4].xy) - ((_28.xy / vec2(max(_28.w, 9.9999997473787516355514526367188e-05))) * debugline_vs_params[4].xy);
|
|
||||||
float _81 = length(_78);
|
|
||||||
vec2 _86;
|
|
||||||
if (_81 > 9.9999997473787516355514526367188e-05)
|
|
||||||
{
|
|
||||||
_86 = _78 / vec2(_81);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_86 = vec2(1.0, 0.0);
|
|
||||||
}
|
|
||||||
vec4 _115 = mix(_28, _38, vec4(a_corner.y));
|
|
||||||
gl_Position = _115 + vec4((((vec2(-_86.y, _86.x) * a_corner.x) * ((a_width * 0.5) + 0.5)) / debugline_vs_params[4].xy) * _115.w, 0.0, 0.0);
|
|
||||||
v_col = a_col;
|
v_col = a_col;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,72 +107,23 @@ vs_debugline_source_glsl300es := u8.[
|
|||||||
0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a,
|
0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a,
|
||||||
0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x64,0x65,
|
0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x64,0x65,
|
||||||
0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,
|
0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,
|
||||||
0x73,0x5b,0x35,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,
|
0x73,0x5b,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,
|
||||||
0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,
|
0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,
|
||||||
0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x3b,0x0a,0x6c,0x61,0x79,0x6f,
|
0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,
|
||||||
0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,
|
0x63,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,
|
||||||
0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x62,
|
0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,
|
||||||
0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,
|
0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x0a,0x76,
|
||||||
0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x61,
|
0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,
|
||||||
0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,
|
0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,
|
||||||
0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x34,0x29,0x20,0x69,0x6e,
|
0x61,0x74,0x34,0x28,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,
|
||||||
0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x61,0x5f,0x77,0x69,0x64,0x74,0x68,0x3b,0x0a,
|
0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,
|
||||||
0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,
|
0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,
|
||||||
0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,
|
0x31,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,
|
||||||
0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x61,0x5f,0x63,
|
0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,
|
||||||
0x6f,0x6c,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,
|
0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,
|
||||||
0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x74,0x34,0x20,0x5f,0x31,0x38,0x20,
|
0x33,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x61,0x5f,0x70,0x6f,0x73,
|
||||||
0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,
|
0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x5f,0x63,0x6f,
|
||||||
0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x64,
|
0x6c,0x20,0x3d,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
||||||
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
|
||||||
0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,
|
|
||||||
0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x64,
|
|
||||||
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
|
||||||
0x6d,0x73,0x5b,0x33,0x5d,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,
|
|
||||||
0x20,0x5f,0x32,0x38,0x20,0x3d,0x20,0x5f,0x31,0x38,0x20,0x2a,0x20,0x76,0x65,0x63,
|
|
||||||
0x34,0x28,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,
|
|
||||||
0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x5f,0x33,0x38,0x20,0x3d,0x20,
|
|
||||||
0x5f,0x31,0x38,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x61,0x5f,0x70,0x6f,0x73,
|
|
||||||
0x5f,0x62,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,
|
|
||||||
0x63,0x32,0x20,0x5f,0x37,0x38,0x20,0x3d,0x20,0x28,0x28,0x5f,0x33,0x38,0x2e,0x78,
|
|
||||||
0x79,0x20,0x2f,0x20,0x76,0x65,0x63,0x32,0x28,0x6d,0x61,0x78,0x28,0x5f,0x33,0x38,
|
|
||||||
0x2e,0x77,0x2c,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,0x39,0x37,0x34,0x37,0x33,
|
|
||||||
0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,0x34,0x35,0x32,0x36,0x33,
|
|
||||||
0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x29,0x29,0x20,0x2a,0x20,0x64,
|
|
||||||
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
|
||||||
0x6d,0x73,0x5b,0x34,0x5d,0x2e,0x78,0x79,0x29,0x20,0x2d,0x20,0x28,0x28,0x5f,0x32,
|
|
||||||
0x38,0x2e,0x78,0x79,0x20,0x2f,0x20,0x76,0x65,0x63,0x32,0x28,0x6d,0x61,0x78,0x28,
|
|
||||||
0x5f,0x32,0x38,0x2e,0x77,0x2c,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,0x39,0x37,
|
|
||||||
0x34,0x37,0x33,0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,0x34,0x35,
|
|
||||||
0x32,0x36,0x33,0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x29,0x29,0x20,
|
|
||||||
0x2a,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,
|
|
||||||
0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x2e,0x78,0x79,0x29,0x3b,0x0a,0x20,0x20,
|
|
||||||
0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x38,0x31,0x20,0x3d,0x20,0x6c,0x65,
|
|
||||||
0x6e,0x67,0x74,0x68,0x28,0x5f,0x37,0x38,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,
|
|
||||||
0x65,0x63,0x32,0x20,0x5f,0x38,0x36,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,
|
|
||||||
0x28,0x5f,0x38,0x31,0x20,0x3e,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,0x39,0x37,
|
|
||||||
0x34,0x37,0x33,0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,0x34,0x35,
|
|
||||||
0x32,0x36,0x33,0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x0a,0x20,0x20,
|
|
||||||
0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x38,0x36,0x20,
|
|
||||||
0x3d,0x20,0x5f,0x37,0x38,0x20,0x2f,0x20,0x76,0x65,0x63,0x32,0x28,0x5f,0x38,0x31,
|
|
||||||
0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x65,0x6c,0x73,
|
|
||||||
0x65,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
|
|
||||||
0x5f,0x38,0x36,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x31,0x2e,0x30,0x2c,0x20,
|
|
||||||
0x30,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,
|
|
||||||
0x76,0x65,0x63,0x34,0x20,0x5f,0x31,0x31,0x35,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,
|
|
||||||
0x5f,0x32,0x38,0x2c,0x20,0x5f,0x33,0x38,0x2c,0x20,0x76,0x65,0x63,0x34,0x28,0x61,
|
|
||||||
0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,
|
|
||||||
0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,
|
|
||||||
0x31,0x31,0x35,0x20,0x2b,0x20,0x76,0x65,0x63,0x34,0x28,0x28,0x28,0x28,0x76,0x65,
|
|
||||||
0x63,0x32,0x28,0x2d,0x5f,0x38,0x36,0x2e,0x79,0x2c,0x20,0x5f,0x38,0x36,0x2e,0x78,
|
|
||||||
0x29,0x20,0x2a,0x20,0x61,0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x2e,0x78,0x29,0x20,
|
|
||||||
0x2a,0x20,0x28,0x28,0x61,0x5f,0x77,0x69,0x64,0x74,0x68,0x20,0x2a,0x20,0x30,0x2e,
|
|
||||||
0x35,0x29,0x20,0x2b,0x20,0x30,0x2e,0x35,0x29,0x29,0x20,0x2f,0x20,0x64,0x65,0x62,
|
|
||||||
0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,
|
|
||||||
0x5b,0x34,0x5d,0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x5f,0x31,0x31,0x35,0x2e,0x77,
|
|
||||||
0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,
|
|
||||||
0x20,0x76,0x5f,0x63,0x6f,0x6c,0x20,0x3d,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,
|
|
||||||
0x7d,0x0a,0x0a,0x00,
|
|
||||||
];
|
];
|
||||||
/*
|
/*
|
||||||
#version 300 es
|
#version 300 es
|
||||||
@ -301,7 +161,6 @@ fs_debugline_source_glsl300es := u8.[
|
|||||||
struct debugline_vs_params
|
struct debugline_vs_params
|
||||||
{
|
{
|
||||||
float4x4 mvp;
|
float4x4 mvp;
|
||||||
float4 viewport;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct main0_out
|
struct main0_out
|
||||||
@ -312,31 +171,14 @@ fs_debugline_source_glsl300es := u8.[
|
|||||||
|
|
||||||
struct main0_in
|
struct main0_in
|
||||||
{
|
{
|
||||||
float2 a_corner [[attribute(0)]];
|
float3 a_pos [[attribute(0)]];
|
||||||
float3 a_pos_a [[attribute(1)]];
|
float4 a_col [[attribute(1)]];
|
||||||
float3 a_pos_b [[attribute(2)]];
|
|
||||||
float4 a_col [[attribute(3)]];
|
|
||||||
float a_width [[attribute(4)]];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
vertex main0_out main0(main0_in in [[stage_in]], constant debugline_vs_params& _13 [[buffer(0)]])
|
vertex main0_out main0(main0_in in [[stage_in]], constant debugline_vs_params& _19 [[buffer(0)]])
|
||||||
{
|
{
|
||||||
main0_out out = {};
|
main0_out out = {};
|
||||||
float4 _28 = _13.mvp * float4(in.a_pos_a, 1.0);
|
out.gl_Position = _19.mvp * float4(in.a_pos, 1.0);
|
||||||
float4 _38 = _13.mvp * float4(in.a_pos_b, 1.0);
|
|
||||||
float2 _78 = ((_38.xy / float2(fast::max(_38.w, 9.9999997473787516355514526367188e-05))) * _13.viewport.xy) - ((_28.xy / float2(fast::max(_28.w, 9.9999997473787516355514526367188e-05))) * _13.viewport.xy);
|
|
||||||
float _81 = length(_78);
|
|
||||||
float2 _86;
|
|
||||||
if (_81 > 9.9999997473787516355514526367188e-05)
|
|
||||||
{
|
|
||||||
_86 = _78 / float2(_81);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_86 = float2(1.0, 0.0);
|
|
||||||
}
|
|
||||||
float4 _115 = mix(_28, _38, float4(in.a_corner.y));
|
|
||||||
out.gl_Position = _115 + float4((((float2(-_86.y, _86.x) * in.a_corner.x) * ((in.a_width * 0.5) + 0.5)) / _13.viewport.xy) * _115.w, 0.0, 0.0);
|
|
||||||
out.v_col = in.a_col;
|
out.v_col = in.a_col;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -350,82 +192,33 @@ vs_debugline_source_metal_macos := u8.[
|
|||||||
0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x64,
|
0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x64,
|
||||||
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
||||||
0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,
|
0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,
|
||||||
0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,
|
0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,
|
||||||
0x34,0x20,0x76,0x69,0x65,0x77,0x70,0x6f,0x72,0x74,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,
|
0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,
|
||||||
0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,
|
0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x20,0x5b,
|
||||||
0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x76,0x5f,
|
0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,
|
||||||
0x63,0x6f,0x6c,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,
|
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,
|
||||||
0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,
|
0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,
|
||||||
0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,
|
0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,
|
||||||
0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,
|
0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,
|
||||||
0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,
|
0x6c,0x6f,0x61,0x74,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x20,0x5b,0x5b,0x61,0x74,
|
||||||
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x61,0x5f,0x63,0x6f,0x72,
|
0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,
|
||||||
0x6e,0x65,0x72,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,
|
0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x20,0x5b,
|
||||||
0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,
|
0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,
|
||||||
0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,
|
0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,
|
||||||
0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,
|
0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,
|
||||||
0x6c,0x6f,0x61,0x74,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x62,0x20,0x5b,0x5b,
|
0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,
|
||||||
0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,
|
0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x64,
|
||||||
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,
|
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
||||||
0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x33,0x29,0x5d,
|
0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,
|
||||||
0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x61,0x5f,0x77,
|
0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,
|
||||||
0x69,0x64,0x74,0x68,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,
|
0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,
|
||||||
0x28,0x34,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,
|
0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,
|
||||||
0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,
|
0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a,
|
||||||
0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,
|
0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x61,0x5f,0x70,0x6f,0x73,
|
||||||
0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,
|
0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,
|
||||||
0x74,0x61,0x6e,0x74,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,
|
0x76,0x5f,0x63,0x6f,0x6c,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x61,0x5f,0x63,0x6f,0x6c,
|
||||||
0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x31,0x33,0x20,0x5b,0x5b,
|
0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,
|
||||||
0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,
|
0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
||||||
0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,
|
|
||||||
0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,
|
|
||||||
0x34,0x20,0x5f,0x32,0x38,0x20,0x3d,0x20,0x5f,0x31,0x33,0x2e,0x6d,0x76,0x70,0x20,
|
|
||||||
0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x61,0x5f,0x70,0x6f,
|
|
||||||
0x73,0x5f,0x61,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,
|
|
||||||
0x6c,0x6f,0x61,0x74,0x34,0x20,0x5f,0x33,0x38,0x20,0x3d,0x20,0x5f,0x31,0x33,0x2e,
|
|
||||||
0x6d,0x76,0x70,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,
|
|
||||||
0x61,0x5f,0x70,0x6f,0x73,0x5f,0x62,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,
|
|
||||||
0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x37,0x38,0x20,0x3d,0x20,
|
|
||||||
0x28,0x28,0x5f,0x33,0x38,0x2e,0x78,0x79,0x20,0x2f,0x20,0x66,0x6c,0x6f,0x61,0x74,
|
|
||||||
0x32,0x28,0x66,0x61,0x73,0x74,0x3a,0x3a,0x6d,0x61,0x78,0x28,0x5f,0x33,0x38,0x2e,
|
|
||||||
0x77,0x2c,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,0x39,0x37,0x34,0x37,0x33,0x37,
|
|
||||||
0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,0x34,0x35,0x32,0x36,0x33,0x36,
|
|
||||||
0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x29,0x29,0x20,0x2a,0x20,0x5f,0x31,
|
|
||||||
0x33,0x2e,0x76,0x69,0x65,0x77,0x70,0x6f,0x72,0x74,0x2e,0x78,0x79,0x29,0x20,0x2d,
|
|
||||||
0x20,0x28,0x28,0x5f,0x32,0x38,0x2e,0x78,0x79,0x20,0x2f,0x20,0x66,0x6c,0x6f,0x61,
|
|
||||||
0x74,0x32,0x28,0x66,0x61,0x73,0x74,0x3a,0x3a,0x6d,0x61,0x78,0x28,0x5f,0x32,0x38,
|
|
||||||
0x2e,0x77,0x2c,0x20,0x39,0x2e,0x39,0x39,0x39,0x39,0x39,0x39,0x37,0x34,0x37,0x33,
|
|
||||||
0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,0x35,0x35,0x31,0x34,0x35,0x32,0x36,0x33,
|
|
||||||
0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,0x35,0x29,0x29,0x29,0x20,0x2a,0x20,0x5f,
|
|
||||||
0x31,0x33,0x2e,0x76,0x69,0x65,0x77,0x70,0x6f,0x72,0x74,0x2e,0x78,0x79,0x29,0x3b,
|
|
||||||
0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x38,0x31,0x20,0x3d,
|
|
||||||
0x20,0x6c,0x65,0x6e,0x67,0x74,0x68,0x28,0x5f,0x37,0x38,0x29,0x3b,0x0a,0x20,0x20,
|
|
||||||
0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x38,0x36,0x3b,0x0a,0x20,0x20,
|
|
||||||
0x20,0x20,0x69,0x66,0x20,0x28,0x5f,0x38,0x31,0x20,0x3e,0x20,0x39,0x2e,0x39,0x39,
|
|
||||||
0x39,0x39,0x39,0x39,0x37,0x34,0x37,0x33,0x37,0x38,0x37,0x35,0x31,0x36,0x33,0x35,
|
|
||||||
0x35,0x35,0x31,0x34,0x35,0x32,0x36,0x33,0x36,0x37,0x31,0x38,0x38,0x65,0x2d,0x30,
|
|
||||||
0x35,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
|
|
||||||
0x20,0x5f,0x38,0x36,0x20,0x3d,0x20,0x5f,0x37,0x38,0x20,0x2f,0x20,0x66,0x6c,0x6f,
|
|
||||||
0x61,0x74,0x32,0x28,0x5f,0x38,0x31,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,
|
|
||||||
0x20,0x20,0x20,0x20,0x65,0x6c,0x73,0x65,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,
|
|
||||||
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x5f,0x38,0x36,0x20,0x3d,0x20,0x66,0x6c,0x6f,
|
|
||||||
0x61,0x74,0x32,0x28,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,0x0a,0x20,
|
|
||||||
0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,
|
|
||||||
0x5f,0x31,0x31,0x35,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x5f,0x32,0x38,0x2c,0x20,
|
|
||||||
0x5f,0x33,0x38,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x61,
|
|
||||||
0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20,
|
|
||||||
0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,
|
|
||||||
0x20,0x3d,0x20,0x5f,0x31,0x31,0x35,0x20,0x2b,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,
|
|
||||||
0x28,0x28,0x28,0x28,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x2d,0x5f,0x38,0x36,0x2e,
|
|
||||||
0x79,0x2c,0x20,0x5f,0x38,0x36,0x2e,0x78,0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x61,
|
|
||||||
0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x2e,0x78,0x29,0x20,0x2a,0x20,0x28,0x28,0x69,
|
|
||||||
0x6e,0x2e,0x61,0x5f,0x77,0x69,0x64,0x74,0x68,0x20,0x2a,0x20,0x30,0x2e,0x35,0x29,
|
|
||||||
0x20,0x2b,0x20,0x30,0x2e,0x35,0x29,0x29,0x20,0x2f,0x20,0x5f,0x31,0x33,0x2e,0x76,
|
|
||||||
0x69,0x65,0x77,0x70,0x6f,0x72,0x74,0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x5f,0x31,
|
|
||||||
0x31,0x35,0x2e,0x77,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,
|
|
||||||
0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x76,0x5f,0x63,0x6f,0x6c,0x20,0x3d,
|
|
||||||
0x20,0x69,0x6e,0x2e,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,
|
|
||||||
0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
|
||||||
];
|
];
|
||||||
/*
|
/*
|
||||||
#include <metal_stdlib>
|
#include <metal_stdlib>
|
||||||
@ -483,20 +276,14 @@ debugline_shader_desc :: (backend: sg_backend) -> sg_shader_desc {
|
|||||||
desc.fragment_func.source = xx *fs_debugline_source_glsl430;
|
desc.fragment_func.source = xx *fs_debugline_source_glsl430;
|
||||||
desc.fragment_func.entry = "main";
|
desc.fragment_func.entry = "main";
|
||||||
desc.attrs[0].base_type = .FLOAT;
|
desc.attrs[0].base_type = .FLOAT;
|
||||||
desc.attrs[0].glsl_name = "a_corner";
|
desc.attrs[0].glsl_name = "a_pos";
|
||||||
desc.attrs[1].base_type = .FLOAT;
|
desc.attrs[1].base_type = .FLOAT;
|
||||||
desc.attrs[1].glsl_name = "a_pos_a";
|
desc.attrs[1].glsl_name = "a_col";
|
||||||
desc.attrs[2].base_type = .FLOAT;
|
|
||||||
desc.attrs[2].glsl_name = "a_pos_b";
|
|
||||||
desc.attrs[3].base_type = .FLOAT;
|
|
||||||
desc.attrs[3].glsl_name = "a_col";
|
|
||||||
desc.attrs[4].base_type = .FLOAT;
|
|
||||||
desc.attrs[4].glsl_name = "a_width";
|
|
||||||
desc.uniform_blocks[0].stage = .VERTEX;
|
desc.uniform_blocks[0].stage = .VERTEX;
|
||||||
desc.uniform_blocks[0].layout = .STD140;
|
desc.uniform_blocks[0].layout = .STD140;
|
||||||
desc.uniform_blocks[0].size = 80;
|
desc.uniform_blocks[0].size = 64;
|
||||||
desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4;
|
desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4;
|
||||||
desc.uniform_blocks[0].glsl_uniforms[0].array_count = 5;
|
desc.uniform_blocks[0].glsl_uniforms[0].array_count = 4;
|
||||||
desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "debugline_vs_params";
|
desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "debugline_vs_params";
|
||||||
case .GLES3;
|
case .GLES3;
|
||||||
desc.vertex_func.source = xx *vs_debugline_source_glsl300es;
|
desc.vertex_func.source = xx *vs_debugline_source_glsl300es;
|
||||||
@ -504,20 +291,14 @@ debugline_shader_desc :: (backend: sg_backend) -> sg_shader_desc {
|
|||||||
desc.fragment_func.source = xx *fs_debugline_source_glsl300es;
|
desc.fragment_func.source = xx *fs_debugline_source_glsl300es;
|
||||||
desc.fragment_func.entry = "main";
|
desc.fragment_func.entry = "main";
|
||||||
desc.attrs[0].base_type = .FLOAT;
|
desc.attrs[0].base_type = .FLOAT;
|
||||||
desc.attrs[0].glsl_name = "a_corner";
|
desc.attrs[0].glsl_name = "a_pos";
|
||||||
desc.attrs[1].base_type = .FLOAT;
|
desc.attrs[1].base_type = .FLOAT;
|
||||||
desc.attrs[1].glsl_name = "a_pos_a";
|
desc.attrs[1].glsl_name = "a_col";
|
||||||
desc.attrs[2].base_type = .FLOAT;
|
|
||||||
desc.attrs[2].glsl_name = "a_pos_b";
|
|
||||||
desc.attrs[3].base_type = .FLOAT;
|
|
||||||
desc.attrs[3].glsl_name = "a_col";
|
|
||||||
desc.attrs[4].base_type = .FLOAT;
|
|
||||||
desc.attrs[4].glsl_name = "a_width";
|
|
||||||
desc.uniform_blocks[0].stage = .VERTEX;
|
desc.uniform_blocks[0].stage = .VERTEX;
|
||||||
desc.uniform_blocks[0].layout = .STD140;
|
desc.uniform_blocks[0].layout = .STD140;
|
||||||
desc.uniform_blocks[0].size = 80;
|
desc.uniform_blocks[0].size = 64;
|
||||||
desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4;
|
desc.uniform_blocks[0].glsl_uniforms[0].type = .FLOAT4;
|
||||||
desc.uniform_blocks[0].glsl_uniforms[0].array_count = 5;
|
desc.uniform_blocks[0].glsl_uniforms[0].array_count = 4;
|
||||||
desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "debugline_vs_params";
|
desc.uniform_blocks[0].glsl_uniforms[0].glsl_name = "debugline_vs_params";
|
||||||
case .METAL_MACOS;
|
case .METAL_MACOS;
|
||||||
desc.vertex_func.source = xx *vs_debugline_source_metal_macos;
|
desc.vertex_func.source = xx *vs_debugline_source_metal_macos;
|
||||||
@ -526,12 +307,9 @@ debugline_shader_desc :: (backend: sg_backend) -> sg_shader_desc {
|
|||||||
desc.fragment_func.entry = "main0";
|
desc.fragment_func.entry = "main0";
|
||||||
desc.attrs[0].base_type = .FLOAT;
|
desc.attrs[0].base_type = .FLOAT;
|
||||||
desc.attrs[1].base_type = .FLOAT;
|
desc.attrs[1].base_type = .FLOAT;
|
||||||
desc.attrs[2].base_type = .FLOAT;
|
|
||||||
desc.attrs[3].base_type = .FLOAT;
|
|
||||||
desc.attrs[4].base_type = .FLOAT;
|
|
||||||
desc.uniform_blocks[0].stage = .VERTEX;
|
desc.uniform_blocks[0].stage = .VERTEX;
|
||||||
desc.uniform_blocks[0].layout = .STD140;
|
desc.uniform_blocks[0].layout = .STD140;
|
||||||
desc.uniform_blocks[0].size = 80;
|
desc.uniform_blocks[0].size = 64;
|
||||||
desc.uniform_blocks[0].msl_buffer_n = 0;
|
desc.uniform_blocks[0].msl_buffer_n = 0;
|
||||||
}
|
}
|
||||||
return desc;
|
return desc;
|
||||||
|
|||||||
@ -556,9 +556,7 @@ vs_gbuffer_billboard_source_metal_macos := u8.[
|
|||||||
fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> gsprite [[texture(0)]], sampler gspritesmp [[sampler(0)]])
|
fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> gsprite [[texture(0)]], sampler gspritesmp [[sampler(0)]])
|
||||||
{
|
{
|
||||||
main0_out out = {};
|
main0_out out = {};
|
||||||
float2 _12 = in.uv_in;
|
if (gsprite.sample(gspritesmp, in.uv_in).w < 0.00999999977648258209228515625)
|
||||||
_12.y = 1.0 - _12.y;
|
|
||||||
if (gsprite.sample(gspritesmp, _12).w < 0.00999999977648258209228515625)
|
|
||||||
{
|
{
|
||||||
discard_fragment();
|
discard_fragment();
|
||||||
}
|
}
|
||||||
@ -598,27 +596,24 @@ fs_gbuffer_billboard_source_metal_macos := u8.[
|
|||||||
0x70,0x72,0x69,0x74,0x65,0x73,0x6d,0x70,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,
|
0x70,0x72,0x69,0x74,0x65,0x73,0x6d,0x70,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,
|
||||||
0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,
|
0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,
|
||||||
0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,
|
0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,
|
||||||
0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x5f,0x31,
|
0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x67,0x73,0x70,0x72,0x69,
|
||||||
0x32,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x75,0x76,0x5f,0x69,0x6e,0x3b,0x0a,0x20,0x20,
|
0x74,0x65,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x67,0x73,0x70,0x72,0x69,0x74,
|
||||||
0x20,0x20,0x5f,0x31,0x32,0x2e,0x79,0x20,0x3d,0x20,0x31,0x2e,0x30,0x20,0x2d,0x20,
|
0x65,0x73,0x6d,0x70,0x2c,0x20,0x69,0x6e,0x2e,0x75,0x76,0x5f,0x69,0x6e,0x29,0x2e,
|
||||||
0x5f,0x31,0x32,0x2e,0x79,0x3b,0x0a,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x67,
|
0x77,0x20,0x3c,0x20,0x30,0x2e,0x30,0x30,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x37,
|
||||||
0x73,0x70,0x72,0x69,0x74,0x65,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x67,0x73,
|
0x37,0x36,0x34,0x38,0x32,0x35,0x38,0x32,0x30,0x39,0x32,0x32,0x38,0x35,0x31,0x35,
|
||||||
0x70,0x72,0x69,0x74,0x65,0x73,0x6d,0x70,0x2c,0x20,0x5f,0x31,0x32,0x29,0x2e,0x77,
|
0x36,0x32,0x35,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,
|
||||||
0x20,0x3c,0x20,0x30,0x2e,0x30,0x30,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x37,0x37,
|
0x20,0x20,0x20,0x64,0x69,0x73,0x63,0x61,0x72,0x64,0x5f,0x66,0x72,0x61,0x67,0x6d,
|
||||||
0x36,0x34,0x38,0x32,0x35,0x38,0x32,0x30,0x39,0x32,0x32,0x38,0x35,0x31,0x35,0x36,
|
0x65,0x6e,0x74,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,
|
||||||
0x32,0x35,0x29,0x0a,0x20,0x20,0x20,0x20,0x7b,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,
|
0x20,0x6f,0x75,0x74,0x2e,0x6f,0x75,0x74,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,
|
||||||
0x20,0x20,0x64,0x69,0x73,0x63,0x61,0x72,0x64,0x5f,0x66,0x72,0x61,0x67,0x6d,0x65,
|
0x6e,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x76,0x69,
|
||||||
0x6e,0x74,0x28,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x7d,0x0a,0x20,0x20,0x20,0x20,
|
0x65,0x77,0x5f,0x73,0x70,0x61,0x63,0x65,0x5f,0x70,0x6f,0x73,0x2c,0x20,0x31,0x2e,
|
||||||
0x6f,0x75,0x74,0x2e,0x6f,0x75,0x74,0x5f,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,
|
0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x6f,0x75,0x74,0x5f,
|
||||||
0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x76,0x69,0x65,
|
0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,
|
||||||
0x77,0x5f,0x73,0x70,0x61,0x63,0x65,0x5f,0x70,0x6f,0x73,0x2c,0x20,0x31,0x2e,0x30,
|
0x66,0x61,0x73,0x74,0x3a,0x3a,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,
|
||||||
0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x6f,0x75,0x74,0x5f,0x6e,
|
0x69,0x6e,0x2e,0x76,0x69,0x65,0x77,0x5f,0x73,0x70,0x61,0x63,0x65,0x5f,0x6e,0x6f,
|
||||||
0x6f,0x72,0x6d,0x61,0x6c,0x20,0x3d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x66,
|
0x72,0x6d,0x61,0x6c,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,
|
||||||
0x61,0x73,0x74,0x3a,0x3a,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,0x69,
|
0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,
|
||||||
0x6e,0x2e,0x76,0x69,0x65,0x77,0x5f,0x73,0x70,0x61,0x63,0x65,0x5f,0x6e,0x6f,0x72,
|
0x00,
|
||||||
0x6d,0x61,0x6c,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,
|
|
||||||
0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
|
||||||
|
|
||||||
];
|
];
|
||||||
gbuffer_billboard_shader_desc :: (backend: sg_backend) -> sg_shader_desc {
|
gbuffer_billboard_shader_desc :: (backend: sg_backend) -> sg_shader_desc {
|
||||||
desc: sg_shader_desc;
|
desc: sg_shader_desc;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,44 +1,16 @@
|
|||||||
@vs vs_debugline
|
@vs vs_debugline
|
||||||
|
|
||||||
// Lines are drawn as camera-facing quads rather than GL lines, so they can have
|
in vec3 a_pos;
|
||||||
// a real pixel width. Buffer 0 holds the six corners of a unit quad, buffer 1
|
|
||||||
// one instance per line.
|
|
||||||
in vec2 a_corner; // x = which side (-1 / +1), y = which end (0 = a, 1 = b)
|
|
||||||
in vec3 a_pos_a;
|
|
||||||
in vec3 a_pos_b;
|
|
||||||
in vec4 a_col;
|
in vec4 a_col;
|
||||||
in float a_width; // in pixels
|
|
||||||
|
|
||||||
layout(binding=0) uniform debugline_vs_params {
|
layout(binding=0) uniform debugline_vs_params {
|
||||||
mat4 mvp;
|
mat4 mvp;
|
||||||
vec4 viewport; // xy = size in pixels, zw = unused
|
|
||||||
};
|
};
|
||||||
|
|
||||||
out vec4 v_col;
|
out vec4 v_col;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 clip_a = mvp * vec4(a_pos_a, 1.0);
|
gl_Position = mvp * vec4(a_pos, 1.0);
|
||||||
vec4 clip_b = mvp * vec4(a_pos_b, 1.0);
|
|
||||||
|
|
||||||
// Widen in screen space so the line keeps its pixel width at any depth.
|
|
||||||
// Guard against w <= 0: an endpoint behind the eye has no screen position,
|
|
||||||
// so fall back to the other end's and let clipping handle the rest.
|
|
||||||
float wa = max(clip_a.w, 0.0001);
|
|
||||||
float wb = max(clip_b.w, 0.0001);
|
|
||||||
vec2 screen_a = (clip_a.xy / wa) * viewport.xy;
|
|
||||||
vec2 screen_b = (clip_b.xy / wb) * viewport.xy;
|
|
||||||
|
|
||||||
vec2 delta = screen_b - screen_a;
|
|
||||||
float len = length(delta);
|
|
||||||
vec2 dir = (len > 0.0001) ? delta / len : vec2(1.0, 0.0);
|
|
||||||
vec2 normal = vec2(-dir.y, dir.x);
|
|
||||||
|
|
||||||
vec4 clip = mix(clip_a, clip_b, a_corner.y);
|
|
||||||
// Half a pixel of extra width keeps thin lines from disappearing between
|
|
||||||
// sample points.
|
|
||||||
vec2 offset = normal * a_corner.x * (a_width * 0.5 + 0.5) / viewport.xy;
|
|
||||||
gl_Position = clip + vec4(offset * clip.w, 0.0, 0.0);
|
|
||||||
|
|
||||||
v_col = a_col;
|
v_col = a_col;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,9 @@ layout(location=1) out vec4 out_normal;
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 uv = uv_in;
|
vec2 uv = uv_in;
|
||||||
uv.y = 1.0 - uv.y;
|
#if SOKOL_GLSL
|
||||||
|
uv.y = 1.0 - uv.y;
|
||||||
|
#endif
|
||||||
vec4 sampled = texture(sampler2D(gsprite, gspritesmp), uv);
|
vec4 sampled = texture(sampler2D(gsprite, gspritesmp), uv);
|
||||||
if(sampled.a < 0.01) discard;
|
if(sampled.a < 0.01) discard;
|
||||||
out_position = vec4(view_space_pos, 1.0);
|
out_position = vec4(view_space_pos, 1.0);
|
||||||
|
|||||||
@ -26,7 +26,6 @@ layout(binding = 4) uniform texture2D pos_buf;
|
|||||||
layout(binding = 4) uniform sampler pos_smp;
|
layout(binding = 4) uniform sampler pos_smp;
|
||||||
|
|
||||||
layout(binding=1) uniform dof_config {
|
layout(binding=1) uniform dof_config {
|
||||||
int dof_disabled;
|
|
||||||
float dof_max;
|
float dof_max;
|
||||||
float dof_point;
|
float dof_point;
|
||||||
};
|
};
|
||||||
@ -132,13 +131,11 @@ void main() {
|
|||||||
sampled_color_hdr = texture(sampler2D(pptex, ppsmp), distorted_texcoord).rgb;
|
sampled_color_hdr = texture(sampler2D(pptex, ppsmp), distorted_texcoord).rgb;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dof_disabled == 0) {
|
float view_z = texture(sampler2D(pos_buf, pos_smp), distorted_texcoord).z;
|
||||||
float view_z = texture(sampler2D(pos_buf, pos_smp), distorted_texcoord).z;
|
float depth = abs(view_z);
|
||||||
float depth = abs(view_z);
|
float coc = smoothstep(0.0, 1.0, abs(depth - dof_point) / max(dof_max, 0.0001));
|
||||||
float coc = smoothstep(0.0, 1.0, abs(depth - dof_point) / max(dof_max, 0.0001));
|
vec3 dof_blurred = texture(sampler2D(dof_tex, dof_smp), distorted_texcoord).rgb;
|
||||||
vec3 dof_blurred = texture(sampler2D(dof_tex, dof_smp), distorted_texcoord).rgb;
|
sampled_color_hdr = mix(sampled_color_hdr, dof_blurred, coc);
|
||||||
sampled_color_hdr = mix(sampled_color_hdr, dof_blurred, coc);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 bloom_color = texture(sampler2D(bloom_tex, bloom_smp), distorted_texcoord).rgb;
|
vec3 bloom_color = texture(sampler2D(bloom_tex, bloom_smp), distorted_texcoord).rgb;
|
||||||
vec3 color_hdr = (sampled_color_hdr + bloom_color * bloom_amount) * exposure;
|
vec3 color_hdr = (sampled_color_hdr + bloom_color * bloom_amount) * exposure;
|
||||||
|
|||||||
@ -1,39 +1,15 @@
|
|||||||
// A single test gets this long to run its commands before it is declared hung
|
|
||||||
// and the run moves on. This only fires while frames are still being produced;
|
|
||||||
// a genuine freeze has to be caught by a timeout around the process itself,
|
|
||||||
// which is what run_tests.sh does.
|
|
||||||
EXE_TEST_TIMEOUT_SECONDS :: 30.0;
|
|
||||||
|
|
||||||
Exe_Runner :: struct {
|
Exe_Runner :: struct {
|
||||||
suite_idx : int;
|
suite_idx : int;
|
||||||
test_idx : int;
|
test_idx : int;
|
||||||
cmd_idx : int;
|
cmd_idx : int;
|
||||||
wait_until : float64;
|
wait_until : float64;
|
||||||
test_failed : bool;
|
test_failed : bool;
|
||||||
test_started : bool;
|
test_started : bool;
|
||||||
done : bool;
|
done : bool;
|
||||||
passed : int;
|
|
||||||
failed : int;
|
|
||||||
test_deadline : float64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_exe_runner : Exe_Runner;
|
g_exe_runner : Exe_Runner;
|
||||||
|
|
||||||
// Ends the process with a status that reflects the run, so a failing exe test
|
|
||||||
// actually fails the build instead of quitting quietly with 0.
|
|
||||||
finish_exe_tests :: (r: *Exe_Runner) {
|
|
||||||
total := r.passed + r.failed;
|
|
||||||
if r.failed == 0 {
|
|
||||||
print("[exe tests] All % test(s) passed.\n", total);
|
|
||||||
} else {
|
|
||||||
print("[exe tests] %/% passed, % FAILED.\n", r.passed, total, r.failed);
|
|
||||||
}
|
|
||||||
r.done = true;
|
|
||||||
sapp_request_quit();
|
|
||||||
status : s32 = ifx r.failed > 0 then cast(s32) 1 else cast(s32) 0;
|
|
||||||
exit(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
run_exe_tests :: () {
|
run_exe_tests :: () {
|
||||||
r := *g_exe_runner;
|
r := *g_exe_runner;
|
||||||
if r.done then return;
|
if r.done then return;
|
||||||
@ -44,7 +20,8 @@ run_exe_tests :: () {
|
|||||||
|
|
||||||
if r.suite_idx >= g_test_runner_state.count {
|
if r.suite_idx >= g_test_runner_state.count {
|
||||||
print("[exe tests] All suites complete.\n");
|
print("[exe tests] All suites complete.\n");
|
||||||
finish_exe_tests(r);
|
r.done = true;
|
||||||
|
sapp_request_quit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,30 +40,15 @@ run_exe_tests :: () {
|
|||||||
|
|
||||||
if !r.test_started {
|
if !r.test_started {
|
||||||
print("[exe tests] Starting '%' / '%'\n", suite.name, test.name);
|
print("[exe tests] Starting '%' / '%'\n", suite.name, test.name);
|
||||||
r.test_started = true;
|
r.test_started = true;
|
||||||
r.test_deadline = get_time() + EXE_TEST_TIMEOUT_SECONDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A test that outlives its budget is failed where it stands rather than
|
|
||||||
// being allowed to stall the whole run.
|
|
||||||
if get_time() > r.test_deadline {
|
|
||||||
print("[exe tests] TIMEOUT '%' / '%' after %s (cmd % of %)\n",
|
|
||||||
suite.name, test.name, EXE_TEST_TIMEOUT_SECONDS, r.cmd_idx, test.cmds.count);
|
|
||||||
r.test_failed = true;
|
|
||||||
r.cmd_idx = test.cmds.count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.cmd_idx >= test.cmds.count {
|
if r.cmd_idx >= test.cmds.count {
|
||||||
if r.test_failed {
|
if r.test_failed {
|
||||||
r.failed += 1;
|
|
||||||
print("[exe tests] FAIL '%' / '%'\n", suite.name, test.name);
|
print("[exe tests] FAIL '%' / '%'\n", suite.name, test.name);
|
||||||
} else {
|
} else {
|
||||||
r.passed += 1;
|
|
||||||
print("[exe tests] PASS '%' / '%'\n", suite.name, test.name);
|
print("[exe tests] PASS '%' / '%'\n", suite.name, test.name);
|
||||||
}
|
}
|
||||||
// Cleared here too: timing out mid-WAIT leaves a stale deadline behind,
|
|
||||||
// which would make the next test's first WAIT elapse instantly.
|
|
||||||
r.wait_until = 0;
|
|
||||||
r.test_idx += 1;
|
r.test_idx += 1;
|
||||||
r.cmd_idx = 0;
|
r.cmd_idx = 0;
|
||||||
r.test_failed = false;
|
r.test_failed = false;
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
// Test framework only — no test cases. This is loaded under every test flag so
|
|
||||||
// that the game side can write suites and exe tests exactly like the engine does.
|
|
||||||
//
|
|
||||||
// Unit tests need nothing beyond this: put them in a #if FLAG_TEST_GAME block
|
|
||||||
// next to the code they cover, with a local #run to call them, and a failing
|
|
||||||
// check turns into a compile error.
|
|
||||||
//
|
|
||||||
// Exe tests are registered from game_exe_tests_add(), which the engine calls
|
|
||||||
// during init when built with test_exe_game.
|
|
||||||
|
|
||||||
#load "utils.jai";
|
|
||||||
#load "exe_tests/index.jai";
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
// Engine test cases. The framework itself lives in framework.jai, which is
|
#load "utils.jai";
|
||||||
// loaded separately so the game side gets it too.
|
|
||||||
|
|
||||||
#load "world_test.jai";
|
#load "world_test.jai";
|
||||||
#load "../editor/rdm_disk_test.jai";
|
#load "../editor/rdm_disk_test.jai";
|
||||||
|
|
||||||
#load "engine_exe_tests/index.jai";
|
#load "engine_exe_tests/index.jai";
|
||||||
|
#load "exe_tests/index.jai";
|
||||||
|
|||||||
@ -268,106 +268,11 @@ test_legacy_load_cursor_fix :: () {
|
|||||||
end_suite(s);
|
end_suite(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entity types come from the game, so these run against whatever the game
|
#run {
|
||||||
// declares first rather than naming a type the engine cannot know about.
|
|
||||||
test_entity_save_load_roundtrip :: () {
|
|
||||||
s := begin_suite("entity save/load roundtrip");
|
|
||||||
if ENTITY_TYPE_TABLE.count == 0 {
|
|
||||||
end_suite(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
type_name := ENTITY_TYPE_TABLE[0].name;
|
|
||||||
|
|
||||||
world := make_test_world();
|
|
||||||
a := spawn_entity(*world, type_name, .{3.5, 1.0, -2.0}, 17);
|
|
||||||
b := spawn_entity(*world, type_name, .{0.0, 0.0, 0.0});
|
|
||||||
check(*s, "spawning returns entities", a != null && b != null);
|
|
||||||
if a == null || b == null {
|
|
||||||
end_suite(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
check(*s, "spawned entities get distinct ids", a.id != b.id);
|
|
||||||
|
|
||||||
saved_fields := entity_fields_to_strings(a);
|
|
||||||
|
|
||||||
json_str, bin_data := save_world(*world);
|
|
||||||
bin_bytes: []u8;
|
|
||||||
bin_bytes.data = bin_data.data;
|
|
||||||
bin_bytes.count = bin_data.count;
|
|
||||||
|
|
||||||
loaded, ok := load_world_from_json(json_str, bin_bytes);
|
|
||||||
check(*s, "load succeeds", ok);
|
|
||||||
check(*s, "both entities come back", loaded.entities.count == 2);
|
|
||||||
if loaded.entities.count == 2 {
|
|
||||||
la := loaded.entities[0];
|
|
||||||
check(*s, "type survives", la.type == a.type);
|
|
||||||
check(*s, "id survives", la.id == a.id);
|
|
||||||
check(*s, "position survives", la.position == Vector3.{3.5, 1.0, -2.0});
|
|
||||||
check(*s, "orientation survives", la.orientation == 17);
|
|
||||||
|
|
||||||
loaded_fields := entity_fields_to_strings(la);
|
|
||||||
same := loaded_fields.count == saved_fields.count;
|
|
||||||
for i: 0..min(loaded_fields.count, saved_fields.count)-1 {
|
|
||||||
if loaded_fields[i].name != saved_fields[i].name then same = false;
|
|
||||||
if loaded_fields[i].value != saved_fields[i].value then same = false;
|
|
||||||
}
|
|
||||||
check(*s, "editable fields survive", same);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handing out an id a loaded entity already owns would make two entities
|
|
||||||
// indistinguishable to anything that refers to them by id.
|
|
||||||
check(*s, "the next id clears every loaded one", loaded.next_entity_id > max(a.id, b.id));
|
|
||||||
|
|
||||||
// The rest of the world is untouched by the entities riding along with it.
|
|
||||||
check(*s, "notes still load", loaded.notes.count == 1);
|
|
||||||
check(*s, "emitters still load", loaded.emitter_instances.count == 1);
|
|
||||||
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Saves are name-keyed so that a build with fewer types or fields than the one
|
|
||||||
// that wrote them still loads. Both halves of that log and carry on.
|
|
||||||
test_entity_unknown_names :: () {
|
|
||||||
s := begin_suite("entities tolerate names from other builds");
|
|
||||||
if ENTITY_TYPE_TABLE.count == 0 {
|
|
||||||
end_suite(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
world := make_test_world();
|
|
||||||
e := spawn_entity(*world, ENTITY_TYPE_TABLE[0].name, .{1.0, 2.0, 3.0});
|
|
||||||
check(*s, "the known type spawns", e != null);
|
|
||||||
if e == null {
|
|
||||||
end_suite(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expect one logged error here: that is the reported half of "skipped".
|
|
||||||
gone := spawn_entity(*world, "A_Type_That_Went_Away", .{0.0, 0.0, 0.0});
|
|
||||||
check(*s, "an unknown type spawns nothing", gone == null);
|
|
||||||
check(*s, "and adds nothing to the world", world.entities.count == 1);
|
|
||||||
|
|
||||||
before := entity_fields_to_strings(e);
|
|
||||||
entity_apply_field(e, "a_field_that_went_away", "12");
|
|
||||||
after := entity_fields_to_strings(e);
|
|
||||||
unchanged := before.count == after.count;
|
|
||||||
for i: 0..min(before.count, after.count)-1 {
|
|
||||||
if before[i].value != after[i].value then unchanged = false;
|
|
||||||
}
|
|
||||||
check(*s, "an unknown field name changes nothing", unchanged);
|
|
||||||
|
|
||||||
end_suite(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stallable: the entity tests read ENTITY_TYPE_TABLE, which cannot be built
|
|
||||||
// until the metaprogram has collected every @Entity in the program.
|
|
||||||
#run,stallable {
|
|
||||||
test_floor_div_mod();
|
test_floor_div_mod();
|
||||||
test_coord_roundtrip();
|
test_coord_roundtrip();
|
||||||
test_chunk_coord_values();
|
test_chunk_coord_values();
|
||||||
test_world_save_load_roundtrip();
|
test_world_save_load_roundtrip();
|
||||||
test_world_json_chunk_offsets();
|
test_world_json_chunk_offsets();
|
||||||
test_legacy_load_cursor_fix();
|
test_legacy_load_cursor_fix();
|
||||||
test_entity_save_load_roundtrip();
|
|
||||||
test_entity_unknown_names();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,15 +90,6 @@ set_trile :: (name: string, trile: Trile) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_any_trile :: () -> (*Trile, success: bool) {
|
|
||||||
for v : trile_table {
|
|
||||||
print("Found trile: %\n", v.name);
|
|
||||||
trileptr, err := get_trile(v.name);
|
|
||||||
return trileptr, err;
|
|
||||||
}
|
|
||||||
return null, false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_trile :: (name: string) -> (*Trile, success: bool) {
|
get_trile :: (name: string) -> (*Trile, success: bool) {
|
||||||
trileptr := table_find_pointer(*trile_table, name);
|
trileptr := table_find_pointer(*trile_table, name);
|
||||||
if !trileptr {
|
if !trileptr {
|
||||||
|
|||||||
@ -59,59 +59,18 @@ note_to_autoedit_conf :: (notes: []string) -> Autoedit_Conf {
|
|||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Types we can generate an editor widget for. Anything else (nested structs,
|
input_code_from_type_and_notes :: (name: string, type: *Type_Info, notes: []string) -> string {
|
||||||
// arrays, the '#as using base' of an entity, ...) is skipped entirely.
|
|
||||||
autoedit_supports_type :: (type: *Type_Info) -> bool {
|
|
||||||
return type == type_info(float) || type == type_info(s32) || type == type_info(bool)
|
|
||||||
|| type == type_info(Vector3) || type == type_info(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Widgets that need a GetRect id get one per field. Two autoedit panels can be
|
|
||||||
// on screen at once (the world config and an entity's fields), so the id is
|
|
||||||
// namespaced by the caller's identifier to keep the two sets apart.
|
|
||||||
autoedit_widget_id :: (identifier: s32, field_index: int) -> s32 {
|
|
||||||
return identifier * 1000 + cast(s32) field_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A text input hands back a view into the widget's own buffer, so the value has
|
|
||||||
// to be copied out on every keystroke. The previous copy is freed only when this
|
|
||||||
// same helper allocated it and the field still points at it: a field's starting
|
|
||||||
// value is usually a literal from the struct definition, which must not be freed.
|
|
||||||
autoedit_owned_text : Table(*string, string);
|
|
||||||
|
|
||||||
autoedit_set_text :: (field: *string, text: string) -> string {
|
|
||||||
found, previous := table_find(*autoedit_owned_text, field);
|
|
||||||
if found && previous.data == field.data free(previous);
|
|
||||||
result := copy_string(text);
|
|
||||||
table_set(*autoedit_owned_text, field, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
input_code_from_type_and_notes :: (name: string, type: *Type_Info, notes: []string, index: int) -> string {
|
|
||||||
autoconf := note_to_autoedit_conf(notes);
|
autoconf := note_to_autoedit_conf(notes);
|
||||||
builder : String_Builder;
|
builder : String_Builder;
|
||||||
// Each field resets the row height, so a field that wants a taller widget can
|
|
||||||
// just set it and let the trailing advance pick it up.
|
|
||||||
print_to_builder(*builder, "r.h = ui_h(4,0);\n");
|
|
||||||
print_to_builder(*builder, "GR.label(r, \"%\", *t_label_left(theme));\n", name);
|
print_to_builder(*builder, "GR.label(r, \"%\", *t_label_left(theme));\n", name);
|
||||||
print_to_builder(*builder, "r.y += r.h;\n");
|
print_to_builder(*builder, "r.y += r.h;\n");
|
||||||
|
|
||||||
if type == type_info(bool) {
|
if type == type_info(float) || type == type_info(s32) {
|
||||||
print_to_builder(*builder, "if GR.button(r, ifx value.% then \"true\" else \"false\", *t_button_selectable(theme, value.%), autoedit_widget_id(identifier, %)) then value.% = !value.%;\n",
|
|
||||||
name, name, index, name, name);
|
|
||||||
} else if type == type_info(float) || type == type_info(s32) {
|
|
||||||
if autoconf.kind == .SLIDER {
|
if autoconf.kind == .SLIDER {
|
||||||
print_to_builder(*builder, "GR.slider(r, *value.%, %, %, %, *theme.slider_theme);\n", name, autoconf.min, autoconf.max, autoconf.step);
|
print_to_builder(*builder, "GR.slider(r, *value.%, %, %, %, *theme.slider_theme);\n", name, autoconf.min, autoconf.max, autoconf.step);
|
||||||
} else {
|
} else {
|
||||||
print_to_builder(*builder, "GR.number_input(r, tprint(\"\%\", value.%), *value.%, %, %, *number_theme);\n", name, name, autoconf.min, autoconf.max);
|
print_to_builder(*builder, "GR.number_input(r, tprint(\"\%\", value.%), *value.%, %, %, *number_theme);\n", name, name, autoconf.min, autoconf.max);
|
||||||
}
|
}
|
||||||
} else if type == type_info(string) {
|
|
||||||
// Roomier than the other widgets: text fields hold a sentence, not a number.
|
|
||||||
print_to_builder(*builder, "r.h = ui_h(7,0);\n");
|
|
||||||
print_to_builder(*builder, "{\n");
|
|
||||||
print_to_builder(*builder, "action, _, text_state := GR.text_input(r, value.%, *theme.text_input_theme, autoedit_widget_id(identifier, %));\n", name, index);
|
|
||||||
print_to_builder(*builder, "if (action & .TEXT_MODIFIED) || (action & .ENTERED) value.% = autoedit_set_text(*value.%, text_state.text);\n", name, name);
|
|
||||||
print_to_builder(*builder, "}\n");
|
|
||||||
} else if type == type_info(Vector3) {
|
} else if type == type_info(Vector3) {
|
||||||
if autoconf.kind == .DEFAULT {
|
if autoconf.kind == .DEFAULT {
|
||||||
print_to_builder(*builder, "{\n");
|
print_to_builder(*builder, "{\n");
|
||||||
@ -124,9 +83,9 @@ input_code_from_type_and_notes :: (name: string, type: *Type_Info, notes: []stri
|
|||||||
print_to_builder(*builder, "r.w = orig_w; r.x = orig_x;\n");
|
print_to_builder(*builder, "r.w = orig_w; r.x = orig_x;\n");
|
||||||
print_to_builder(*builder, "}\n");
|
print_to_builder(*builder, "}\n");
|
||||||
} else if autoconf.kind == .COLOR {
|
} else if autoconf.kind == .COLOR {
|
||||||
print_to_builder(*builder, "if GR.button(r, \"Edit color\", *t_button_color(theme, .{value.%.x, value.%.y, value.%.z, 1.0}), autoedit_widget_id(identifier, %)) then cur_edit_color = *value.%;\n", name, name, name, index, name);
|
print_to_builder(*builder, "if GR.button(r, \"Edit color\", *t_button_color(theme, .{value.%.x, value.%.y, value.%.z, 1.0})) then cur_edit_color = *value.%;\n", name, name, name, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
print_to_builder(*builder, "r.y += r.h;\n");
|
print_to_builder(*builder, "r.y += r.h;\n");
|
||||||
return builder_to_string(*builder);
|
return builder_to_string(*builder);
|
||||||
@ -147,10 +106,7 @@ autoedit :: (rect: GR.Rect, value: *$T, theme: *GR.Overall_Theme, identifier: s3
|
|||||||
ti := type_info(T);
|
ti := type_info(T);
|
||||||
#assert #run type_info(T).type == .STRUCT "Autoedit only works for structs";
|
#assert #run type_info(T).type == .STRUCT "Autoedit only works for structs";
|
||||||
for ti.members {
|
for ti.members {
|
||||||
if it.flags & .CONSTANT continue;
|
print_to_builder(*builder, "%\n", input_code_from_type_and_notes(it.name, it.type, it.notes));
|
||||||
if it.flags & .USING continue; // e.g. an entity's Entity base
|
|
||||||
if !autoedit_supports_type(it.type) continue;
|
|
||||||
print_to_builder(*builder, "%\n", input_code_from_type_and_notes(it.name, it.type, it.notes, it_index));
|
|
||||||
}
|
}
|
||||||
return builder_to_string(*builder);
|
return builder_to_string(*builder);
|
||||||
}
|
}
|
||||||
|
|||||||
234
src/world.jai
234
src/world.jai
@ -52,7 +52,6 @@ Chunk_Trile_Group :: struct {
|
|||||||
average_pos : Vector3;
|
average_pos : Vector3;
|
||||||
|
|
||||||
instances : [..]Trile_Instance;
|
instances : [..]Trile_Instance;
|
||||||
is_buried : [..]bool; // Runtime-only, parallel to instances. Not serialized.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Chunk :: struct {
|
Chunk :: struct {
|
||||||
@ -91,8 +90,6 @@ World :: struct {
|
|||||||
chunks : Table(Chunk_Key, Chunk, chunk_key_hash, chunk_key_compare);
|
chunks : Table(Chunk_Key, Chunk, chunk_key_hash, chunk_key_compare);
|
||||||
emitter_instances : [..]Particle_Emitter_Instance;
|
emitter_instances : [..]Particle_Emitter_Instance;
|
||||||
notes : [..]Editor_Note;
|
notes : [..]Editor_Note;
|
||||||
entities : [..]*Entity;
|
|
||||||
next_entity_id : u32;
|
|
||||||
rdm_overrides : [..]Rdm_Instance_Override;
|
rdm_overrides : [..]Rdm_Instance_Override;
|
||||||
rdm_lookup : [..]Rdm_Atlas_Entry; // populated by bake (Step 5) and by loader (Step 6)
|
rdm_lookup : [..]Rdm_Atlas_Entry; // populated by bake (Step 5) and by loader (Step 6)
|
||||||
}
|
}
|
||||||
@ -209,33 +206,6 @@ lworld :: (name: string) {
|
|||||||
load_world(name);
|
load_world(name);
|
||||||
} @Command;
|
} @Command;
|
||||||
|
|
||||||
// Copy another world's config (sky, sun, water, ...) onto the loaded world, so a
|
|
||||||
// new level can start from an existing look instead of retyping the values.
|
|
||||||
copy_world_info :: (name: string) {
|
|
||||||
if !current_world.valid {
|
|
||||||
log_error("Cannot copy: no world loaded");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#if OS != .WASM {
|
|
||||||
file :: #import "File";
|
|
||||||
path := tprint("%/worlds/%/world.json", GAME_RESOURCES_DIR, name);
|
|
||||||
json_str, read_ok := file.read_entire_file(path,, allocator = temp);
|
|
||||||
if !read_ok {
|
|
||||||
log_error("Cannot copy: failed to read '%'", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_ok, wj := Jaison.json_parse_string(json_str, World_Json,, temp);
|
|
||||||
if !parse_ok {
|
|
||||||
log_error("Cannot copy: failed to parse world '%'", name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_world.world.conf = world_config_from_json(*wj.config);
|
|
||||||
log_info("Copied world config from '%'", name);
|
|
||||||
}
|
|
||||||
} @Command
|
|
||||||
|
|
||||||
init_world_system :: () {
|
init_world_system :: () {
|
||||||
Pool.set_allocators(*current_world.pool);
|
Pool.set_allocators(*current_world.pool);
|
||||||
}
|
}
|
||||||
@ -246,15 +216,10 @@ unload_current_world :: () {
|
|||||||
for *chunk: current_world.world.chunks {
|
for *chunk: current_world.world.chunks {
|
||||||
for *group: chunk.groups {
|
for *group: chunk.groups {
|
||||||
array_free(group.instances);
|
array_free(group.instances);
|
||||||
array_free(group.is_buried);
|
|
||||||
}
|
}
|
||||||
array_free(chunk.groups);
|
array_free(chunk.groups);
|
||||||
}
|
}
|
||||||
deinit(*current_world.world.chunks);
|
deinit(*current_world.world.chunks);
|
||||||
free_all_entities(*current_world.world);
|
|
||||||
// Entity ids restart per world, so a stale editor selection would latch onto
|
|
||||||
// an unrelated entity in the next one.
|
|
||||||
#if OS != .WASM { level_editor_clear_entity_selection(); }
|
|
||||||
array_free(current_world.world.rdm_lookup);
|
array_free(current_world.world.rdm_lookup);
|
||||||
Pool.reset(*current_world.pool);
|
Pool.reset(*current_world.pool);
|
||||||
current_world.valid = false;
|
current_world.valid = false;
|
||||||
@ -265,9 +230,6 @@ set_loaded_world :: (world: World) {
|
|||||||
current_world.world = world;
|
current_world.world = world;
|
||||||
current_world.valid = true;
|
current_world.valid = true;
|
||||||
resolve_emitter_definitions(*current_world.world);
|
resolve_emitter_definitions(*current_world.world);
|
||||||
for *chunk: current_world.world.chunks {
|
|
||||||
recompute_buried_for_chunk(*current_world.world, chunk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is_cell_opaque :: (name: string) -> bool {
|
is_cell_opaque :: (name: string) -> bool {
|
||||||
@ -292,105 +254,106 @@ trile_at_world :: (world: *World, wx: s32, wy: s32, wz: s32) -> string {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
BURIED_DIRS :: s32.[ 1,0,0, -1,0,0, 0,1,0, 0,-1,0, 0,0,1, 0,0,-1 ];
|
recompute_group_bounds :: (group: *Chunk_Trile_Group) {
|
||||||
|
if group.instances.count == 0 {
|
||||||
compute_buried_at_world :: (world: *World, wx: s32, wy: s32, wz: s32) -> bool {
|
group.bounding_min = .{};
|
||||||
for axis: 0..5 {
|
group.bounding_max = .{};
|
||||||
nwx := wx + BURIED_DIRS[axis*3+0];
|
group.average_pos = .{};
|
||||||
nwy := wy + BURIED_DIRS[axis*3+1];
|
return;
|
||||||
nwz := wz + BURIED_DIRS[axis*3+2];
|
|
||||||
if !is_cell_opaque(trile_at_world(world, nwx, nwy, nwz)) return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
gmin := Vector3.{31, 31, 31};
|
||||||
|
gmax := Vector3.{0, 0, 0};
|
||||||
|
gavg := Vector3.{0, 0, 0};
|
||||||
|
for i : group.instances {
|
||||||
|
gmin.x = min(gmin.x, xx i.x);
|
||||||
|
gmin.y = min(gmin.y, xx i.y);
|
||||||
|
gmin.z = min(gmin.z, xx i.z);
|
||||||
|
gmax.x = max(gmax.x, xx i.x);
|
||||||
|
gmax.y = max(gmax.y, xx i.y);
|
||||||
|
gmax.z = max(gmax.z, xx i.z);
|
||||||
|
gavg += Vector3.{xx i.x, xx i.y, xx i.z};
|
||||||
|
}
|
||||||
|
gavg /= cast(float) group.instances.count;
|
||||||
|
group.bounding_min = gmin;
|
||||||
|
group.bounding_max = gmax;
|
||||||
|
group.average_pos = gavg;
|
||||||
}
|
}
|
||||||
|
|
||||||
recompute_buried_for_chunk :: (world: *World, chunk: *Chunk) {
|
// Scans every chunk for triles whose 6 axis-aligned neighbors are all opaque,
|
||||||
// Build an O(1) local lookup grid (32^3 trile names) for this chunk so the
|
// and removes them. One-shot — operates against a snapshot of the original
|
||||||
// common in-chunk neighbor case is a single index lookup. Cross-chunk
|
// state so a fully-enclosed cluster is removed in a single pass.
|
||||||
// neighbors fall back to trile_at_world.
|
delete_buried_triles :: (world: *World) -> s32 {
|
||||||
|
NEIGHBOR_DIRS :: s32.[ 1,0,0, -1,0,0, 0,1,0, 0,-1,0, 0,0,1, 0,0,-1 ];
|
||||||
GRID :: CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
GRID :: CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
||||||
grid: [GRID] string;
|
|
||||||
grid_data := grid;
|
cell_idx :: (lx: u8, ly: u8, lz: u8) -> int {
|
||||||
for *group: chunk.groups {
|
return cast(int)lx + CHUNK_SIZE * (cast(int)ly + CHUNK_SIZE * cast(int)lz);
|
||||||
for inst: group.instances {
|
}
|
||||||
idx := cast(int)inst.x + CHUNK_SIZE * (cast(int)inst.y + CHUNK_SIZE * cast(int)inst.z);
|
|
||||||
grid_data[idx] = group.trile_name;
|
build_chunk_grid :: (chunk: *Chunk, grid: *[GRID]string) {
|
||||||
|
for *group : chunk.groups {
|
||||||
|
for inst : group.instances grid.*[cell_idx(inst.x, inst.y, inst.z)] = group.trile_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for *group: chunk.groups {
|
neighbor_trile_name :: (world: *World, chunk_coord: Chunk_Key, grid: *[GRID]string, wx: s32, wy: s32, wz: s32) -> string {
|
||||||
array_resize(*group.is_buried, group.instances.count);
|
if world_to_chunk_coord(wx, wy, wz) == chunk_coord {
|
||||||
for inst, i: group.instances {
|
lx, ly, lz := world_to_local(wx, wy, wz);
|
||||||
wx, wy, wz := chunk_local_to_world(chunk.coord, inst.x, inst.y, inst.z);
|
return grid.*[cell_idx(lx, ly, lz)];
|
||||||
buried := true;
|
}
|
||||||
for axis: 0..5 {
|
return trile_at_world(world, wx, wy, wz);
|
||||||
nwx := wx + BURIED_DIRS[axis*3+0];
|
}
|
||||||
nwy := wy + BURIED_DIRS[axis*3+1];
|
|
||||||
nwz := wz + BURIED_DIRS[axis*3+2];
|
is_buried :: (world: *World, chunk_coord: Chunk_Key, grid: *[GRID]string, lx: u8, ly: u8, lz: u8) -> bool {
|
||||||
neighbor_name: string;
|
wx0, wy0, wz0 := chunk_local_to_world(chunk_coord, lx, ly, lz);
|
||||||
nkey := world_to_chunk_coord(nwx, nwy, nwz);
|
for axis : 0..5 {
|
||||||
if nkey == chunk.coord {
|
name := neighbor_trile_name(world, chunk_coord, grid,
|
||||||
nlx, nly, nlz := world_to_local(nwx, nwy, nwz);
|
wx0 + NEIGHBOR_DIRS[axis*3+0],
|
||||||
nidx := cast(int)nlx + CHUNK_SIZE * (cast(int)nly + CHUNK_SIZE * cast(int)nlz);
|
wy0 + NEIGHBOR_DIRS[axis*3+1],
|
||||||
neighbor_name = grid_data[nidx];
|
wz0 + NEIGHBOR_DIRS[axis*3+2]);
|
||||||
} else {
|
if !is_cell_opaque(name) then return false;
|
||||||
neighbor_name = trile_at_world(world, nwx, nwy, nwz);
|
}
|
||||||
}
|
return true;
|
||||||
if !is_cell_opaque(neighbor_name) {
|
}
|
||||||
buried = false;
|
|
||||||
break;
|
Pending :: struct {
|
||||||
}
|
group : *Chunk_Trile_Group;
|
||||||
|
indices : [..]s32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending : [..]Pending;
|
||||||
|
pending.allocator = temp;
|
||||||
|
|
||||||
|
for *chunk : world.chunks {
|
||||||
|
grid : [GRID]string;
|
||||||
|
build_chunk_grid(chunk, *grid);
|
||||||
|
|
||||||
|
for *group : chunk.groups {
|
||||||
|
pr : Pending;
|
||||||
|
pr.group = group;
|
||||||
|
pr.indices.allocator = temp;
|
||||||
|
for inst, i : group.instances {
|
||||||
|
if is_buried(world, chunk.coord, *grid, inst.x, inst.y, inst.z) then array_add(*pr.indices, cast(s32) i);
|
||||||
}
|
}
|
||||||
group.is_buried[i] = buried;
|
if pr.indices.count > 0 then array_add(*pending, pr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
recompute_buried_at_cell :: (world: *World, wx: s32, wy: s32, wz: s32) {
|
total : s32 = 0;
|
||||||
key := world_to_chunk_coord(wx, wy, wz);
|
for pr : pending {
|
||||||
chunk := table_find_pointer(*world.chunks, key);
|
for < k : 0..pr.indices.count - 1 {
|
||||||
if !chunk return;
|
array_unordered_remove_by_index(*pr.group.instances, pr.indices[k]);
|
||||||
lx, ly, lz := world_to_local(wx, wy, wz);
|
total += 1;
|
||||||
for *group: chunk.groups {
|
|
||||||
for inst, i: group.instances {
|
|
||||||
if inst.x == lx && inst.y == ly && inst.z == lz {
|
|
||||||
if group.is_buried.count != group.instances.count array_resize(*group.is_buried, group.instances.count);
|
|
||||||
group.is_buried[i] = compute_buried_at_world(world, wx, wy, wz);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
recompute_group_bounds(pr.group);
|
||||||
}
|
}
|
||||||
}
|
return total;
|
||||||
|
|
||||||
get_note_locations :: (note : string) -> []Vector3 {
|
|
||||||
result: [..]Vector3;
|
|
||||||
print("Looking for notes %\n", current_world.world.name);
|
|
||||||
for n: current_world.world.notes {
|
|
||||||
print("COMP: % %\n", n.text, note);
|
|
||||||
if n.text == note {
|
|
||||||
v: Vector3 = .{cast(float)n.position.x, cast(float)n.position.y, cast(float)n.position.z};
|
|
||||||
array_add(*result, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidate_buried_around :: (world: *World, wx: s32, wy: s32, wz: s32) {
|
|
||||||
recompute_buried_at_cell(world, wx, wy, wz);
|
|
||||||
for axis: 0..5 {
|
|
||||||
recompute_buried_at_cell(world, wx + BURIED_DIRS[axis*3+0], wy + BURIED_DIRS[axis*3+1], wz + BURIED_DIRS[axis*3+2]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_emitter_definitions :: (world: *World) {
|
resolve_emitter_definitions :: (world: *World) {
|
||||||
for *inst: world.emitter_instances {
|
for *inst: world.emitter_instances {
|
||||||
inst.definition = get_emitter_def(inst.definition_name);
|
inst.definition = get_emitter_def(inst.definition_name);
|
||||||
}
|
}
|
||||||
for e: world.entities {
|
|
||||||
for *inst: e.emitters {
|
|
||||||
inst.definition = get_emitter_def(inst.definition_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_world :: () {
|
clear_world :: () {
|
||||||
@ -398,7 +361,6 @@ clear_world :: () {
|
|||||||
for *chunk: current_world.world.chunks {
|
for *chunk: current_world.world.chunks {
|
||||||
for *group: chunk.groups {
|
for *group: chunk.groups {
|
||||||
array_free(group.instances);
|
array_free(group.instances);
|
||||||
array_free(group.is_buried);
|
|
||||||
}
|
}
|
||||||
array_free(chunk.groups);
|
array_free(chunk.groups);
|
||||||
}
|
}
|
||||||
@ -422,7 +384,6 @@ World_Json :: struct {
|
|||||||
chunks : [..]World_Json_Chunk;
|
chunks : [..]World_Json_Chunk;
|
||||||
emitters : [..]World_Json_Emitter;
|
emitters : [..]World_Json_Emitter;
|
||||||
notes : [..]World_Json_Note;
|
notes : [..]World_Json_Note;
|
||||||
entities : [..]World_Json_Entity;
|
|
||||||
rdm_overrides : [..]World_Json_Rdm_Override;
|
rdm_overrides : [..]World_Json_Rdm_Override;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,17 +424,6 @@ World_Json_Note :: struct {
|
|||||||
position : [3]s32;
|
position : [3]s32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entity fields are stored as name/value string pairs rather than a typed
|
|
||||||
// object, so that adding, removing or renaming a field never breaks loading:
|
|
||||||
// unknown names are skipped and missing ones keep their struct defaults.
|
|
||||||
World_Json_Entity :: struct {
|
|
||||||
type : string; // entity type name, so reordering ENTITY_TYPES is safe
|
|
||||||
id : u32;
|
|
||||||
position : [3]float;
|
|
||||||
orientation : u8; // absent in worlds saved before entities could rotate
|
|
||||||
fields : [..]Entity_Field;
|
|
||||||
}
|
|
||||||
|
|
||||||
World_Json_Rdm_Override :: struct {
|
World_Json_Rdm_Override :: struct {
|
||||||
x : s32;
|
x : s32;
|
||||||
y : s32;
|
y : s32;
|
||||||
@ -669,7 +619,7 @@ save_world :: (world: *World) -> (json: string, chunks_bin: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wj: World_Json;
|
wj: World_Json;
|
||||||
wj.version = 5;
|
wj.version = 4;
|
||||||
wj.name = world.name;
|
wj.name = world.name;
|
||||||
wj.config = world_config_to_json(*world.conf);
|
wj.config = world_config_to_json(*world.conf);
|
||||||
|
|
||||||
@ -698,16 +648,6 @@ save_world :: (world: *World) -> (json: string, chunks_bin: string) {
|
|||||||
array_add(*wj.notes, jn);
|
array_add(*wj.notes, jn);
|
||||||
}
|
}
|
||||||
|
|
||||||
for e: world.entities {
|
|
||||||
je: World_Json_Entity;
|
|
||||||
je.type = entity_type_name(e.type);
|
|
||||||
je.id = e.id;
|
|
||||||
je.position = e.position.component;
|
|
||||||
je.orientation = e.orientation;
|
|
||||||
for f: entity_fields_to_strings(e) array_add(*je.fields, f);
|
|
||||||
array_add(*wj.entities, je);
|
|
||||||
}
|
|
||||||
|
|
||||||
for ov: world.rdm_overrides {
|
for ov: world.rdm_overrides {
|
||||||
size := ifx ov.rdm_size > 0 then ov.rdm_size else RDM_DEFAULT_SIZE;
|
size := ifx ov.rdm_size > 0 then ov.rdm_size else RDM_DEFAULT_SIZE;
|
||||||
array_add(*wj.rdm_overrides, .{x=ov.x, y=ov.y, z=ov.z, rdm_enabled=ov.rdm_enabled, rdm_size=size});
|
array_add(*wj.rdm_overrides, .{x=ov.x, y=ov.y, z=ov.z, rdm_enabled=ov.rdm_enabled, rdm_size=size});
|
||||||
@ -795,16 +735,6 @@ load_world_from_json :: (json_str: string, chunk_bin: []u8) -> (World, bool) {
|
|||||||
array_add(*world.notes, note);
|
array_add(*world.notes, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
for je: wj.entities {
|
|
||||||
e := spawn_entity(*world, je.type, .{je.position[0], je.position[1], je.position[2]}, je.orientation);
|
|
||||||
if e == null then continue; // unknown type; spawn_entity logged it
|
|
||||||
e.id = je.id;
|
|
||||||
if je.id >= world.next_entity_id then world.next_entity_id = je.id + 1;
|
|
||||||
for f: je.fields {
|
|
||||||
entity_apply_field(e, f.name, f.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for jov: wj.rdm_overrides {
|
for jov: wj.rdm_overrides {
|
||||||
size := ifx jov.rdm_size > 0 then jov.rdm_size else RDM_DEFAULT_SIZE;
|
size := ifx jov.rdm_size > 0 then jov.rdm_size else RDM_DEFAULT_SIZE;
|
||||||
array_add(*world.rdm_overrides, .{x=jov.x, y=jov.y, z=jov.z, rdm_enabled=jov.rdm_enabled, rdm_size=size});
|
array_add(*world.rdm_overrides, .{x=jov.x, y=jov.y, z=jov.z, rdm_enabled=jov.rdm_enabled, rdm_size=size});
|
||||||
|
|||||||
@ -1,11 +1,3 @@
|
|||||||
// @Entity is what registers a type with the engine; see src/entities.jai.
|
|
||||||
Test_Marker :: struct {
|
|
||||||
#as using base : Entity;
|
|
||||||
base.type = Test_Marker;
|
|
||||||
|
|
||||||
PARTS :: Entity_Part.[];
|
|
||||||
} @Entity
|
|
||||||
|
|
||||||
game_engine_config :: () {
|
game_engine_config :: () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
702
test_game/resources/game_core/sprites/anim.sheet.json
Normal file
702
test_game/resources/game_core/sprites/anim.sheet.json
Normal file
@ -0,0 +1,702 @@
|
|||||||
|
{ "frames": [
|
||||||
|
{
|
||||||
|
"filename": "animsheet 0.aseprite",
|
||||||
|
"frame": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 1.aseprite",
|
||||||
|
"frame": { "x": 50, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 2.aseprite",
|
||||||
|
"frame": { "x": 100, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 3.aseprite",
|
||||||
|
"frame": { "x": 150, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 4.aseprite",
|
||||||
|
"frame": { "x": 200, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 5.aseprite",
|
||||||
|
"frame": { "x": 250, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 6.aseprite",
|
||||||
|
"frame": { "x": 300, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 7.aseprite",
|
||||||
|
"frame": { "x": 350, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 8.aseprite",
|
||||||
|
"frame": { "x": 400, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 9.aseprite",
|
||||||
|
"frame": { "x": 450, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 10.aseprite",
|
||||||
|
"frame": { "x": 500, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 11.aseprite",
|
||||||
|
"frame": { "x": 550, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 12.aseprite",
|
||||||
|
"frame": { "x": 600, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 13.aseprite",
|
||||||
|
"frame": { "x": 650, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 14.aseprite",
|
||||||
|
"frame": { "x": 700, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 15.aseprite",
|
||||||
|
"frame": { "x": 750, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 16.aseprite",
|
||||||
|
"frame": { "x": 800, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 17.aseprite",
|
||||||
|
"frame": { "x": 850, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 18.aseprite",
|
||||||
|
"frame": { "x": 900, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 19.aseprite",
|
||||||
|
"frame": { "x": 950, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 20.aseprite",
|
||||||
|
"frame": { "x": 1000, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 21.aseprite",
|
||||||
|
"frame": { "x": 1050, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 22.aseprite",
|
||||||
|
"frame": { "x": 1100, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 23.aseprite",
|
||||||
|
"frame": { "x": 1150, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 24.aseprite",
|
||||||
|
"frame": { "x": 1200, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 25.aseprite",
|
||||||
|
"frame": { "x": 1250, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 26.aseprite",
|
||||||
|
"frame": { "x": 1300, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 27.aseprite",
|
||||||
|
"frame": { "x": 1350, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 28.aseprite",
|
||||||
|
"frame": { "x": 1400, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 29.aseprite",
|
||||||
|
"frame": { "x": 1450, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 30.aseprite",
|
||||||
|
"frame": { "x": 1500, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 31.aseprite",
|
||||||
|
"frame": { "x": 1550, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 32.aseprite",
|
||||||
|
"frame": { "x": 1600, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 33.aseprite",
|
||||||
|
"frame": { "x": 1650, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 34.aseprite",
|
||||||
|
"frame": { "x": 1700, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 35.aseprite",
|
||||||
|
"frame": { "x": 1750, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 36.aseprite",
|
||||||
|
"frame": { "x": 1800, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 37.aseprite",
|
||||||
|
"frame": { "x": 1850, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 38.aseprite",
|
||||||
|
"frame": { "x": 1900, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 39.aseprite",
|
||||||
|
"frame": { "x": 1950, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 40.aseprite",
|
||||||
|
"frame": { "x": 2000, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 41.aseprite",
|
||||||
|
"frame": { "x": 2050, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 42.aseprite",
|
||||||
|
"frame": { "x": 2100, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 43.aseprite",
|
||||||
|
"frame": { "x": 2150, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 44.aseprite",
|
||||||
|
"frame": { "x": 2200, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 45.aseprite",
|
||||||
|
"frame": { "x": 2250, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 46.aseprite",
|
||||||
|
"frame": { "x": 2300, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 47.aseprite",
|
||||||
|
"frame": { "x": 2350, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 48.aseprite",
|
||||||
|
"frame": { "x": 2400, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 49.aseprite",
|
||||||
|
"frame": { "x": 2450, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 50.aseprite",
|
||||||
|
"frame": { "x": 2500, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 51.aseprite",
|
||||||
|
"frame": { "x": 2550, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 52.aseprite",
|
||||||
|
"frame": { "x": 2600, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 53.aseprite",
|
||||||
|
"frame": { "x": 2650, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 54.aseprite",
|
||||||
|
"frame": { "x": 2700, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 55.aseprite",
|
||||||
|
"frame": { "x": 2750, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 56.aseprite",
|
||||||
|
"frame": { "x": 2800, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 57.aseprite",
|
||||||
|
"frame": { "x": 2850, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 58.aseprite",
|
||||||
|
"frame": { "x": 2900, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 59.aseprite",
|
||||||
|
"frame": { "x": 2950, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 60.aseprite",
|
||||||
|
"frame": { "x": 3000, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 61.aseprite",
|
||||||
|
"frame": { "x": 3050, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 62.aseprite",
|
||||||
|
"frame": { "x": 3100, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 63.aseprite",
|
||||||
|
"frame": { "x": 3150, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 64.aseprite",
|
||||||
|
"frame": { "x": 3200, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 65.aseprite",
|
||||||
|
"frame": { "x": 3250, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 66.aseprite",
|
||||||
|
"frame": { "x": 3300, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 67.aseprite",
|
||||||
|
"frame": { "x": 3350, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 68.aseprite",
|
||||||
|
"frame": { "x": 3400, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 69.aseprite",
|
||||||
|
"frame": { "x": 3450, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 70.aseprite",
|
||||||
|
"frame": { "x": 3500, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 71.aseprite",
|
||||||
|
"frame": { "x": 3550, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 72.aseprite",
|
||||||
|
"frame": { "x": 3600, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "animsheet 73.aseprite",
|
||||||
|
"frame": { "x": 3650, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 50, "h": 48 },
|
||||||
|
"sourceSize": { "w": 50, "h": 48 },
|
||||||
|
"duration": 75
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"app": "http://www.aseprite.org/",
|
||||||
|
"version": "1.3.15.5-x64",
|
||||||
|
"image": "animsheet.png",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": { "w": 3700, "h": 48 },
|
||||||
|
"scale": "1",
|
||||||
|
"frameTags": [
|
||||||
|
{ "name": "player_idle", "from": 0, "to": 6, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "player_walk", "from": 7, "to": 14, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "player_run", "from": 15, "to": 22, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "player_stop", "from": 23, "to": 24, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "ball", "from": 27, "to": 27, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "jump_up", "from": 28, "to": 28, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "jump_top", "from": 29, "to": 29, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "jump_down", "from": 30, "to": 30, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "jump_land", "from": 31, "to": 32, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "slide", "from": 33, "to": 36, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_idle", "from": 37, "to": 43, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_walk", "from": 44, "to": 51, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_run", "from": 52, "to": 59, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_jump_up", "from": 65, "to": 65, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_jump_top", "from": 66, "to": 66, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_jump_down", "from": 67, "to": 67, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_jump_land", "from": 68, "to": 69, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "red_slide", "from": 70, "to": 73, "direction": "forward", "color": "#000000ff" }
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||||
|
],
|
||||||
|
"slices": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
test_game/resources/game_core/sprites/anim.sheet.png
Normal file
BIN
test_game/resources/game_core/sprites/anim.sheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
267
test_game/resources/game_core/sprites/score.sheet.json
Normal file
267
test_game/resources/game_core/sprites/score.sheet.json
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
{ "frames": [
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 0.aseprite",
|
||||||
|
"frame": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 1.aseprite",
|
||||||
|
"frame": { "x": 96, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 2.aseprite",
|
||||||
|
"frame": { "x": 192, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 3.aseprite",
|
||||||
|
"frame": { "x": 288, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 4.aseprite",
|
||||||
|
"frame": { "x": 384, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 5.aseprite",
|
||||||
|
"frame": { "x": 480, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 6.aseprite",
|
||||||
|
"frame": { "x": 576, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 7.aseprite",
|
||||||
|
"frame": { "x": 672, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 8.aseprite",
|
||||||
|
"frame": { "x": 768, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 9.aseprite",
|
||||||
|
"frame": { "x": 864, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 10.aseprite",
|
||||||
|
"frame": { "x": 960, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 11.aseprite",
|
||||||
|
"frame": { "x": 1056, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 12.aseprite",
|
||||||
|
"frame": { "x": 1152, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 13.aseprite",
|
||||||
|
"frame": { "x": 1248, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 14.aseprite",
|
||||||
|
"frame": { "x": 1344, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 15.aseprite",
|
||||||
|
"frame": { "x": 1440, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 16.aseprite",
|
||||||
|
"frame": { "x": 1536, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 17.aseprite",
|
||||||
|
"frame": { "x": 1632, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 18.aseprite",
|
||||||
|
"frame": { "x": 1728, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 19.aseprite",
|
||||||
|
"frame": { "x": 1824, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 20.aseprite",
|
||||||
|
"frame": { "x": 1920, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 21.aseprite",
|
||||||
|
"frame": { "x": 2016, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 22.aseprite",
|
||||||
|
"frame": { "x": 2112, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 23.aseprite",
|
||||||
|
"frame": { "x": 2208, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "scoresheet 24.aseprite",
|
||||||
|
"frame": { "x": 2304, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"rotated": false,
|
||||||
|
"trimmed": false,
|
||||||
|
"spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 48 },
|
||||||
|
"sourceSize": { "w": 96, "h": 48 },
|
||||||
|
"duration": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"app": "http://www.aseprite.org/",
|
||||||
|
"version": "1.3.16.1-x64",
|
||||||
|
"format": "RGBA8888",
|
||||||
|
"size": { "w": 2400, "h": 48 },
|
||||||
|
"scale": "1",
|
||||||
|
"frameTags": [
|
||||||
|
{ "name": "lscore_0", "from": 0, "to": 0, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_1", "from": 1, "to": 1, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_2", "from": 2, "to": 2, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_3", "from": 3, "to": 3, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_4", "from": 4, "to": 4, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_5", "from": 5, "to": 5, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_6", "from": 6, "to": 6, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_7", "from": 7, "to": 7, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_8", "from": 8, "to": 8, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "lscore_adv", "from": 9, "to": 9, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_0", "from": 10, "to": 10, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_1", "from": 11, "to": 11, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_2", "from": 12, "to": 12, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_3", "from": 13, "to": 13, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_4", "from": 14, "to": 14, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_5", "from": 15, "to": 15, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_6", "from": 16, "to": 16, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_7", "from": 17, "to": 17, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_8", "from": 18, "to": 18, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "rscore_adv", "from": 19, "to": 19, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "score_middle", "from": 20, "to": 20, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "score_board", "from": 21, "to": 21, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "score_board_l", "from": 22, "to": 22, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "score_board_r", "from": 23, "to": 23, "direction": "forward", "color": "#000000ff" },
|
||||||
|
{ "name": "logo", "from": 24, "to": 24, "direction": "forward", "color": "#000000ff" }
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||||
|
],
|
||||||
|
"slices": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
test_game/resources/game_core/sprites/score.sheet.png
Normal file
BIN
test_game/resources/game_core/sprites/score.sheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 5,
|
"version": 4,
|
||||||
"name": "test_world",
|
"name": "test_world",
|
||||||
"config": {
|
"config": {
|
||||||
"skyBase": [
|
"skyBase": [
|
||||||
@ -23,8 +23,8 @@
|
|||||||
"sunPosition": [
|
"sunPosition": [
|
||||||
0.371391,0.557086,0.742781
|
0.371391,0.557086,0.742781
|
||||||
],
|
],
|
||||||
"sunIntensity": 1,
|
"sunIntensity": 2,
|
||||||
"skyIntensity": 0.3,
|
"skyIntensity": 1,
|
||||||
"hasClouds": 1,
|
"hasClouds": 1,
|
||||||
"planeHeight": 0,
|
"planeHeight": 0,
|
||||||
"animatePlaneHeight": 1,
|
"animatePlaneHeight": 1,
|
||||||
@ -33,17 +33,12 @@
|
|||||||
],
|
],
|
||||||
"deepColor": [
|
"deepColor": [
|
||||||
1,1,1
|
1,1,1
|
||||||
],
|
]
|
||||||
"waterShininess": 64
|
|
||||||
},
|
},
|
||||||
"chunks": [
|
"chunks": [
|
||||||
],
|
],
|
||||||
"emitters": [
|
"emitters": [
|
||||||
],
|
],
|
||||||
"notes": [
|
"notes": [
|
||||||
],
|
|
||||||
"entities": [
|
|
||||||
],
|
|
||||||
"rdm_overrides": [
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user