Compare commits

..

1 Commits

Author SHA1 Message Date
3bff0f9501 upload wip work from desktop to take with me on laptop 2026-05-07 15:11:38 +03:00
53 changed files with 2430 additions and 4036 deletions

View File

@ -1,4 +1,4 @@
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:

View File

@ -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.

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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."

View File

@ -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

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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,38 +720,13 @@ 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; };
chunk_groups.allocator = temp;
for pos : positions {
wx := cast(s32) pos.x; wy := cast(s32) pos.y; wz := cast(s32) pos.z;
ck := world_to_chunk_coord(wx, wy, wz);
lx, ly, lz := world_to_local(wx, wy, wz);
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: Rendering_Task_Trile;
task.trile = editor_current_trile.name; task.trile = editor_current_trile.name;
task.positions = grp.instances; // task.positions = positions;
task.worldConf = *curworld.world.conf; task.worldConf = *curworld.world.conf;
task.chunk_key = grp.key;
task.preview_mode = ifx is_delete then cast(s32)2 else cast(s32)1; task.preview_mode = ifx is_delete then cast(s32)2 else cast(s32)1;
add_rendering_task(task); 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) {
key := world_to_chunk_coord(wx, wy, wz); key := world_to_chunk_coord(wx, wy, wz);
@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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);
}

View File

@ -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");

View File

@ -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 {

View File

@ -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);

View File

@ -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();
} }

View File

@ -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);
}

View File

@ -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);
} }

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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);

View File

@ -41,17 +41,52 @@ 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 {
for *p: bucket.particles {
if !p.alive then continue; if !p.alive then continue;
p.age += dt; p.age += dt;
if p.age >= p.lifetime { if p.age >= p.lifetime {
@ -62,6 +97,7 @@ tick_particles_physics :: (dt: float) {
p.position += p.velocity * dt; p.position += p.velocity * dt;
} }
} }
}
tick_world_emitters :: (dt: float) { tick_world_emitters :: (dt: float) {
curworld := get_current_world(); curworld := get_current_world();
@ -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; total_count : s32 = 0;
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; for *bucket: g_particle_buckets {
batches.allocator = temp; if !bucket.in_use then continue;
for *p: g_particles { def := get_emitter_def(bucket.name);
if !p.alive then continue; if def == null then continue;
if p.definition == null then continue;
def := p.definition;
if def.animation_name.count == 0 then continue; 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); anim := get_animation_from_string(def.animation_name);
if anim == null then continue; if anim == null then continue;
if anim.frames.count == 0 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; instance_offset := total_count;
bucket_count : s32 = 0;
for *p: bucket.particles {
if !p.alive then continue;
if total_count + bucket_count >= MAX_PARTICLES then break;
t := p.age / p.lifetime; t := p.age / p.lifetime;
size := def.size_start + (def.size_end - def.size_start) * t; size := def.size_start + (def.size_end - def.size_start) * t;
col := lerp_color(def.color_start, def.color_end, t); col := lerp_color(def.color_start, def.color_end, t);
idx := batch.count; idx := instance_offset + bucket_count;
batch.pos_size.*[idx] = .{p.position.x, p.position.y, p.position.z, size}; buf.pos_size[idx] = .{p.position.x, p.position.y, p.position.z, size};
anim := batch.anim;
frame_count := anim.frames.count; frame_count := anim.frames.count;
frame := cast(s32)(t * cast(float)frame_count); frame := cast(s32)(t * cast(float)frame_count);
if frame < 0 then frame = 0; if frame < 0 then frame = 0;
if frame >= frame_count then frame = cast(s32)(frame_count - 1); if frame >= frame_count then frame = cast(s32)(frame_count - 1);
f := anim.frames[frame]; f := anim.frames[frame];
batch.uv_rects.*[idx] = .{ buf.uv_rects[idx] = .{
cast(float)f.x / cast(float)anim.sheet_w, cast(float)f.x / cast(float)anim.sheet_w,
cast(float)f.y / cast(float)anim.sheet_h, cast(float)f.y / cast(float)anim.sheet_h,
cast(float)f.w / cast(float)anim.sheet_w, cast(float)f.w / cast(float)anim.sheet_w,
cast(float)f.h / cast(float)anim.sheet_h, cast(float)f.h / cast(float)anim.sheet_h,
}; };
buf.colors[idx] = col;
batch.colors.*[idx] = col; bucket_count += 1;
batch.count += 1;
} }
buf_task := New(Rendering_Task_Particles_Buffer,, temp); if bucket_count > 0 {
total_count : s32 = 0;
for *b: batches {
if b.count <= 0 then continue;
instance_offset := total_count;
memcpy(*buf_task.pos_size[instance_offset], cast(*void)b.pos_size, b.count * size_of(Vector4));
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; draw_task : Rendering_Task_Particles;
draw_task.count = b.count; draw_task.count = bucket_count;
draw_task.instance_offset = instance_offset; draw_task.instance_offset = instance_offset;
draw_task.blend_mode = b.blend_mode; draw_task.blend_mode = def.blend_mode;
draw_task.sheet = b.sheet; draw_task.sheet = anim.sheet;
add_rendering_task(draw_task); 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;

View File

@ -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 {

View File

@ -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)) }));

View File

@ -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};
} }

View File

@ -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);
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); 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.";

View File

@ -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 {

View File

@ -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);
} }

View File

@ -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;

View File

@ -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;
@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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;
} }

View File

@ -47,7 +47,9 @@ layout(location=1) out vec4 out_normal;
void main() { void main() {
vec2 uv = uv_in; vec2 uv = uv_in;
#if SOKOL_GLSL
uv.y = 1.0 - uv.y; 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);

View File

@ -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;

View File

@ -1,9 +1,3 @@
// 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;
@ -12,28 +6,10 @@ Exe_Runner :: struct {
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;
} }
@ -64,29 +41,14 @@ 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;

View File

@ -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";

View File

@ -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";

View File

@ -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();
} }

View File

@ -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 {

View File

@ -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,7 +83,7 @@ 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);
} }
} }
@ -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);
} }

View File

@ -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 {
group.bounding_min = .{};
group.bounding_max = .{};
group.average_pos = .{};
return;
}
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;
}
compute_buried_at_world :: (world: *World, wx: s32, wy: s32, wz: s32) -> bool { // Scans every chunk for triles whose 6 axis-aligned neighbors are all opaque,
// and removes them. One-shot — operates against a snapshot of the original
// state so a fully-enclosed cluster is removed in a single pass.
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;
cell_idx :: (lx: u8, ly: u8, lz: u8) -> int {
return cast(int)lx + CHUNK_SIZE * (cast(int)ly + CHUNK_SIZE * cast(int)lz);
}
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;
}
}
neighbor_trile_name :: (world: *World, chunk_coord: Chunk_Key, grid: *[GRID]string, wx: s32, wy: s32, wz: s32) -> string {
if world_to_chunk_coord(wx, wy, wz) == chunk_coord {
lx, ly, lz := world_to_local(wx, wy, wz);
return grid.*[cell_idx(lx, ly, lz)];
}
return trile_at_world(world, wx, wy, wz);
}
is_buried :: (world: *World, chunk_coord: Chunk_Key, grid: *[GRID]string, lx: u8, ly: u8, lz: u8) -> bool {
wx0, wy0, wz0 := chunk_local_to_world(chunk_coord, lx, ly, lz);
for axis : 0..5 { for axis : 0..5 {
nwx := wx + BURIED_DIRS[axis*3+0]; name := neighbor_trile_name(world, chunk_coord, grid,
nwy := wy + BURIED_DIRS[axis*3+1]; wx0 + NEIGHBOR_DIRS[axis*3+0],
nwz := wz + BURIED_DIRS[axis*3+2]; wy0 + NEIGHBOR_DIRS[axis*3+1],
if !is_cell_opaque(trile_at_world(world, nwx, nwy, nwz)) return false; wz0 + NEIGHBOR_DIRS[axis*3+2]);
if !is_cell_opaque(name) then return false;
} }
return true; return true;
} }
recompute_buried_for_chunk :: (world: *World, chunk: *Chunk) { Pending :: struct {
// Build an O(1) local lookup grid (32^3 trile names) for this chunk so the group : *Chunk_Trile_Group;
// common in-chunk neighbor case is a single index lookup. Cross-chunk indices : [..]s32;
// neighbors fall back to trile_at_world. }
GRID :: CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
pending : [..]Pending;
pending.allocator = temp;
for *chunk : world.chunks {
grid : [GRID]string; grid : [GRID]string;
grid_data := grid; build_chunk_grid(chunk, *grid);
for *group: chunk.groups {
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;
}
}
for *group : chunk.groups { for *group : chunk.groups {
array_resize(*group.is_buried, group.instances.count); pr : Pending;
pr.group = group;
pr.indices.allocator = temp;
for inst, i : group.instances { for inst, i : group.instances {
wx, wy, wz := chunk_local_to_world(chunk.coord, inst.x, inst.y, inst.z); if is_buried(world, chunk.coord, *grid, inst.x, inst.y, inst.z) then array_add(*pr.indices, cast(s32) i);
buried := true;
for axis: 0..5 {
nwx := wx + BURIED_DIRS[axis*3+0];
nwy := wy + BURIED_DIRS[axis*3+1];
nwz := wz + BURIED_DIRS[axis*3+2];
neighbor_name: string;
nkey := world_to_chunk_coord(nwx, nwy, nwz);
if nkey == chunk.coord {
nlx, nly, nlz := world_to_local(nwx, nwy, nwz);
nidx := cast(int)nlx + CHUNK_SIZE * (cast(int)nly + CHUNK_SIZE * cast(int)nlz);
neighbor_name = grid_data[nidx];
} else {
neighbor_name = trile_at_world(world, nwx, nwy, nwz);
}
if !is_cell_opaque(neighbor_name) {
buried = false;
break;
}
}
group.is_buried[i] = buried;
}
}
}
recompute_buried_at_cell :: (world: *World, wx: s32, wy: s32, wz: s32) {
key := world_to_chunk_coord(wx, wy, wz);
chunk := table_find_pointer(*world.chunks, key);
if !chunk return;
lx, ly, lz := world_to_local(wx, wy, wz);
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;
}
} }
if pr.indices.count > 0 then array_add(*pending, pr);
} }
} }
get_note_locations :: (note : string) -> []Vector3 { total : s32 = 0;
result: [..]Vector3; for pr : pending {
print("Looking for notes %\n", current_world.world.name); for < k : 0..pr.indices.count - 1 {
for n: current_world.world.notes { array_unordered_remove_by_index(*pr.group.instances, pr.indices[k]);
print("COMP: % %\n", n.text, note); total += 1;
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);
} }
recompute_group_bounds(pr.group);
} }
return result; return total;
}
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});

View File

@ -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 :: () {
} }

View 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": [
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View 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": [
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -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.