add entity code
This commit is contained in:
parent
69e3dd26dc
commit
cba645adcf
142
run_tests.sh
Executable file
142
run_tests.sh
Executable file
@ -0,0 +1,142 @@
|
|||||||
|
#!/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."
|
||||||
@ -2,6 +2,7 @@
|
|||||||
#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";
|
||||||
|
|||||||
455
src/editor/gizmo.jai
Normal file
455
src/editor/gizmo.jai
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
// Blender-style gizmos: a translate gizmo with three RGB axis arrows plus three
|
||||||
|
// plane handles for two-axis drags, and a rotate gizmo with three RGB rings.
|
||||||
|
// Drawn with depth-ignoring overlay lines so they stay visible through terrain,
|
||||||
|
// and picked in world space so the pick thresholds scale with the gizmo itself.
|
||||||
|
|
||||||
|
Gizmo_Handle :: enum {
|
||||||
|
NONE;
|
||||||
|
AXIS_X;
|
||||||
|
AXIS_Y;
|
||||||
|
AXIS_Z;
|
||||||
|
PLANE_YZ; // normal +X
|
||||||
|
PLANE_XZ; // normal +Y
|
||||||
|
PLANE_XY; // normal +Z
|
||||||
|
RING_X; // turns about +X
|
||||||
|
RING_Y;
|
||||||
|
RING_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gizmo_State :: struct {
|
||||||
|
hover : Gizmo_Handle;
|
||||||
|
active : Gizmo_Handle;
|
||||||
|
|
||||||
|
start_position : Vector3; // object position when the drag began
|
||||||
|
start_t : float; // axis drags: parameter along the axis at grab time
|
||||||
|
start_hit : Vector3; // plane drags: world point grabbed
|
||||||
|
start_angle : float; // ring drags: angle around the ring at grab time
|
||||||
|
applied_steps : int; // ring drags: quarter turns already handed to the caller
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gizmo length as a fraction of the camera distance, so it keeps a constant
|
||||||
|
// on-screen size. Everything else is expressed in units of that length.
|
||||||
|
GIZMO_SCALE :: 0.14;
|
||||||
|
GIZMO_PLANE_INNER :: 0.22;
|
||||||
|
GIZMO_PLANE_OUTER :: 0.52;
|
||||||
|
GIZMO_PICK_RADIUS :: 0.08;
|
||||||
|
// Below this |dot(plane normal, view dir)| the plane handle is edge-on: too thin
|
||||||
|
// to aim at, so it is neither drawn nor picked.
|
||||||
|
GIZMO_PLANE_MIN_FACING :: 0.15;
|
||||||
|
// Ring radius, and how far off it a click can land, both in units of gizmo length.
|
||||||
|
GIZMO_RING_RADIUS :: 1.0;
|
||||||
|
GIZMO_RING_PICK :: 0.14;
|
||||||
|
GIZMO_RING_SEGMENTS :: 48;
|
||||||
|
// Line widths, in pixels.
|
||||||
|
GIZMO_LINE_WIDTH :: 3.0;
|
||||||
|
GIZMO_FILL_WIDTH :: 2.0;
|
||||||
|
|
||||||
|
// Runs one frame of the gizmo: hover, click-to-grab, drag, release, and drawing.
|
||||||
|
// Returns the dragged-to position; the caller owns snapping and writing it back.
|
||||||
|
gizmo_translate :: (using state: *Gizmo_State, position: Vector3, cam: Camera, ray: Ray) -> (position: Vector3, changed: bool) {
|
||||||
|
len := max(length(cam.position - position) * GIZMO_SCALE, 0.001);
|
||||||
|
pos := position;
|
||||||
|
changed := false;
|
||||||
|
|
||||||
|
mouse := get_mouse_state(Key_Code.MOUSE_BUTTON_LEFT);
|
||||||
|
pressed := (mouse & .START) != .NONE;
|
||||||
|
held := (mouse & .DOWN) != .NONE;
|
||||||
|
|
||||||
|
if active == .NONE {
|
||||||
|
hover = gizmo_pick(position, len, cam, ray);
|
||||||
|
if hover != .NONE && pressed {
|
||||||
|
start_position = position;
|
||||||
|
if gizmo_is_plane(hover) {
|
||||||
|
ok, point := gizmo_plane_hit(position, hover, ray);
|
||||||
|
if ok {
|
||||||
|
start_hit = point;
|
||||||
|
active = hover;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t, _, _ := gizmo_closest_axis_t(ray, position, gizmo_axis(hover));
|
||||||
|
start_t = t;
|
||||||
|
active = hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !held {
|
||||||
|
active = .NONE;
|
||||||
|
} else {
|
||||||
|
if gizmo_is_plane(active) {
|
||||||
|
ok, point := gizmo_plane_hit(start_position, active, ray);
|
||||||
|
if ok {
|
||||||
|
pos = start_position + (point - start_hit);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
axis := gizmo_axis(active);
|
||||||
|
t, _, _ := gizmo_closest_axis_t(ray, start_position, axis);
|
||||||
|
pos = start_position + axis * (t - start_t);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drawn at the object's own position rather than the dragged-to one: the
|
||||||
|
// caller may snap what we hand back, and the gizmo has to sit on the object.
|
||||||
|
gizmo_draw(position, len, cam, ifx active != .NONE then active else hover);
|
||||||
|
return pos, changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One frame of the rotate gizmo. Entities only hold the 24 axis-aligned cube
|
||||||
|
// rotations, so a drag reports whole quarter turns: the returned basis is the
|
||||||
|
// (u, v, n) of the ring being dragged and 'steps' is how many turns of u toward
|
||||||
|
// v to apply since the last frame, which is usually zero.
|
||||||
|
gizmo_rotate :: (using state: *Gizmo_State, position: Vector3, cam: Camera, ray: Ray) -> (u: Vector3, v: Vector3, n: Vector3, steps: int) {
|
||||||
|
len := max(length(cam.position - position) * GIZMO_SCALE, 0.001);
|
||||||
|
|
||||||
|
mouse := get_mouse_state(Key_Code.MOUSE_BUTTON_LEFT);
|
||||||
|
pressed := (mouse & .START) != .NONE;
|
||||||
|
held := (mouse & .DOWN) != .NONE;
|
||||||
|
|
||||||
|
steps := 0;
|
||||||
|
|
||||||
|
if active == .NONE {
|
||||||
|
hover = gizmo_pick_ring(position, len, cam, ray);
|
||||||
|
if hover != .NONE && pressed {
|
||||||
|
ok, angle := gizmo_ring_angle(position, hover, ray);
|
||||||
|
if ok {
|
||||||
|
start_position = position;
|
||||||
|
start_angle = angle;
|
||||||
|
applied_steps = 0;
|
||||||
|
active = hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !held {
|
||||||
|
active = .NONE;
|
||||||
|
} else {
|
||||||
|
ok, angle := gizmo_ring_angle(start_position, active, ray);
|
||||||
|
if ok {
|
||||||
|
// Wrap into (-180, 180] so passing the seam doesn't spin the object.
|
||||||
|
delta := angle - start_angle;
|
||||||
|
while delta > PI delta -= 2 * PI;
|
||||||
|
while delta < -PI delta += 2 * PI;
|
||||||
|
total := cast(int) floor(delta / (PI * 0.5) + 0.5);
|
||||||
|
steps = total - applied_steps;
|
||||||
|
applied_steps = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_draw_rings(position, len, cam, ifx active != .NONE then active else hover);
|
||||||
|
|
||||||
|
handle := ifx active != .NONE then active else Gizmo_Handle.RING_Y;
|
||||||
|
u, v := gizmo_plane_axes(handle);
|
||||||
|
return u, v, gizmo_axis(handle), steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_axis :: (h: Gizmo_Handle) -> Vector3 {
|
||||||
|
if h == {
|
||||||
|
case .AXIS_X; #through;
|
||||||
|
case .PLANE_YZ; #through;
|
||||||
|
case .RING_X; return .{1, 0, 0};
|
||||||
|
case .AXIS_Y; #through;
|
||||||
|
case .PLANE_XZ; #through;
|
||||||
|
case .RING_Y; return .{0, 1, 0};
|
||||||
|
case; return .{0, 0, 1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_is_plane :: (h: Gizmo_Handle) -> bool {
|
||||||
|
return h == .PLANE_YZ || h == .PLANE_XZ || h == .PLANE_XY;
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_is_ring :: (h: Gizmo_Handle) -> bool {
|
||||||
|
return h == .RING_X || h == .RING_Y || h == .RING_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The two in-plane axes of a plane or ring handle; gizmo_axis gives the third
|
||||||
|
// (the plane normal, or the axis the ring turns about).
|
||||||
|
gizmo_plane_axes :: (h: Gizmo_Handle) -> (u: Vector3, v: Vector3) {
|
||||||
|
if h == {
|
||||||
|
case .PLANE_YZ; #through;
|
||||||
|
case .RING_X; return .{0, 1, 0}, .{0, 0, 1};
|
||||||
|
case .PLANE_XZ; #through;
|
||||||
|
case .RING_Y; return .{1, 0, 0}, .{0, 0, 1};
|
||||||
|
case; return .{1, 0, 0}, .{0, 1, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles are colored by their axis; plane handles by the axis they are
|
||||||
|
// perpendicular to, which is what gizmo_axis returns for them.
|
||||||
|
gizmo_color :: (h: Gizmo_Handle, highlighted: bool) -> Vector4 {
|
||||||
|
if highlighted then return .{1.0, 0.85, 0.2, 1.0};
|
||||||
|
axis := gizmo_axis(h);
|
||||||
|
if axis.x != 0 then return .{0.95, 0.28, 0.30, 1.0};
|
||||||
|
if axis.y != 0 then return .{0.40, 0.90, 0.32, 1.0};
|
||||||
|
return .{0.30, 0.50, 0.98, 1.0};
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file
|
||||||
|
|
||||||
|
// Parameter along the line (origin + axis*t) of the point closest to the ray,
|
||||||
|
// plus that point and the distance between the two lines at closest approach.
|
||||||
|
gizmo_closest_axis_t :: (ray: Ray, origin: Vector3, axis: Vector3) -> (t: float, point: Vector3, dist: float) {
|
||||||
|
r := origin - ray.origin;
|
||||||
|
a := dot(axis, axis);
|
||||||
|
b := dot(axis, ray.direction);
|
||||||
|
e := dot(ray.direction, ray.direction);
|
||||||
|
c := dot(axis, r);
|
||||||
|
f := dot(ray.direction, r);
|
||||||
|
|
||||||
|
denom := a*e - b*b;
|
||||||
|
if abs(denom) < 0.00001 then return 0, origin, 99999; // ray parallel to the axis
|
||||||
|
|
||||||
|
t := (b*f - c*e) / denom;
|
||||||
|
s := (a*f - c*b) / denom;
|
||||||
|
pa := origin + axis * t;
|
||||||
|
pb := ray.origin + ray.direction * s;
|
||||||
|
return t, pa, length(pa - pb);
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_plane_hit :: (origin: Vector3, h: Gizmo_Handle, ray: Ray) -> (bool, Vector3) {
|
||||||
|
n := gizmo_axis(h);
|
||||||
|
denom := dot(n, ray.direction);
|
||||||
|
if abs(denom) < 0.0001 then return false, .{};
|
||||||
|
t := dot(n, origin - ray.origin) / denom;
|
||||||
|
if t < 0 then return false, .{}; // plane is behind the camera
|
||||||
|
return true, ray.origin + ray.direction * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which way each plane handle is offset from the origin: toward the camera, so
|
||||||
|
// the handles always sit in the octant facing the viewer.
|
||||||
|
gizmo_plane_signs :: (origin: Vector3, h: Gizmo_Handle, cam: Camera) -> (su: float, sv: float) {
|
||||||
|
u, v := gizmo_plane_axes(h);
|
||||||
|
to_cam := cam.position - origin;
|
||||||
|
su := ifx dot(u, to_cam) >= 0 then cast(float)1 else cast(float)-1;
|
||||||
|
sv := ifx dot(v, to_cam) >= 0 then cast(float)1 else cast(float)-1;
|
||||||
|
return su, sv;
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_pick :: (origin: Vector3, len: float, cam: Camera, ray: Ray) -> Gizmo_Handle {
|
||||||
|
// Planes first: they sit closer to the origin and never overlap the arrows,
|
||||||
|
// but grabbing a plane is the more common intent when they are near.
|
||||||
|
for h: Gizmo_Handle.[.PLANE_YZ, .PLANE_XZ, .PLANE_XY] {
|
||||||
|
n := gizmo_axis(h);
|
||||||
|
if abs(dot(n, normalize(cam.position - origin))) < GIZMO_PLANE_MIN_FACING then continue;
|
||||||
|
ok, point := gizmo_plane_hit(origin, h, ray);
|
||||||
|
if !ok then continue;
|
||||||
|
u, v := gizmo_plane_axes(h);
|
||||||
|
su, sv := gizmo_plane_signs(origin, h, cam);
|
||||||
|
d := point - origin;
|
||||||
|
cu := dot(d, u) * su;
|
||||||
|
cv := dot(d, v) * sv;
|
||||||
|
if cu >= GIZMO_PLANE_INNER * len && cu <= GIZMO_PLANE_OUTER * len
|
||||||
|
&& cv >= GIZMO_PLANE_INNER * len && cv <= GIZMO_PLANE_OUTER * len {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
best := Gizmo_Handle.NONE;
|
||||||
|
best_dist := GIZMO_PICK_RADIUS * len;
|
||||||
|
for h: Gizmo_Handle.[.AXIS_X, .AXIS_Y, .AXIS_Z] {
|
||||||
|
t, _, dist := gizmo_closest_axis_t(ray, origin, gizmo_axis(h));
|
||||||
|
if t < 0 || t > len then continue;
|
||||||
|
if dist < best_dist {
|
||||||
|
best_dist = dist;
|
||||||
|
best = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_draw :: (origin: Vector3, len: float, cam: Camera, highlighted: Gizmo_Handle) {
|
||||||
|
for h: Gizmo_Handle.[.PLANE_YZ, .PLANE_XZ, .PLANE_XY] {
|
||||||
|
n := gizmo_axis(h);
|
||||||
|
if abs(dot(n, normalize(cam.position - origin))) < GIZMO_PLANE_MIN_FACING then continue;
|
||||||
|
u, v := gizmo_plane_axes(h);
|
||||||
|
su, sv := gizmo_plane_signs(origin, h, cam);
|
||||||
|
col := gizmo_color(h, h == highlighted);
|
||||||
|
gizmo_draw_plane(origin, u * su, v * sv, GIZMO_PLANE_INNER * len, GIZMO_PLANE_OUTER * len, col, h == highlighted);
|
||||||
|
}
|
||||||
|
|
||||||
|
for h: Gizmo_Handle.[.AXIS_X, .AXIS_Y, .AXIS_Z] {
|
||||||
|
gizmo_draw_arrow(origin, gizmo_axis(h) * len, gizmo_color(h, h == highlighted));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_draw_arrow :: (origin: Vector3, vec: Vector3, col: Vector4) {
|
||||||
|
tip := origin + vec;
|
||||||
|
shaft := normalize(vec);
|
||||||
|
debug_line_overlay(origin, tip, col, GIZMO_LINE_WIDTH);
|
||||||
|
|
||||||
|
head := length(vec) * 0.18;
|
||||||
|
back := tip - shaft * head;
|
||||||
|
perp : Vector3;
|
||||||
|
if abs(shaft.x) < 0.9 {
|
||||||
|
perp = normalize(cross(shaft, .{1, 0, 0}));
|
||||||
|
} else {
|
||||||
|
perp = normalize(cross(shaft, .{0, 1, 0}));
|
||||||
|
}
|
||||||
|
perp2 := cross(shaft, perp);
|
||||||
|
w1 := perp * (head * 0.4);
|
||||||
|
w2 := perp2 * (head * 0.4);
|
||||||
|
// Four fins plus a ring, so the head reads as a cone from any angle.
|
||||||
|
debug_line_overlay(back + w1, tip, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(back - w1, tip, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(back + w2, tip, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(back - w2, tip, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(back + w1, back + w2, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(back + w2, back - w1, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(back - w1, back - w2, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(back - w2, back + w1, col, GIZMO_LINE_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A square in the (u, v) plane between inner and outer, outlined at full alpha
|
||||||
|
// and hatched at low alpha so it reads as a translucent pane.
|
||||||
|
gizmo_draw_plane :: (origin: Vector3, u: Vector3, v: Vector3, inner: float, outer: float, col: Vector4, filled: bool) {
|
||||||
|
p00 := origin + u * inner + v * inner;
|
||||||
|
p10 := origin + u * outer + v * inner;
|
||||||
|
p11 := origin + u * outer + v * outer;
|
||||||
|
p01 := origin + u * inner + v * outer;
|
||||||
|
debug_line_overlay(p00, p10, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(p10, p11, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(p11, p01, col, GIZMO_LINE_WIDTH);
|
||||||
|
debug_line_overlay(p01, p00, col, GIZMO_LINE_WIDTH);
|
||||||
|
|
||||||
|
fill := col;
|
||||||
|
fill.w = ifx filled then 0.55 else 0.18;
|
||||||
|
// Enough hatch lines that the pane reads as filled rather than striped.
|
||||||
|
HATCH :: 14;
|
||||||
|
for i: 1..HATCH {
|
||||||
|
f := cast(float)i / cast(float)(HATCH + 1);
|
||||||
|
c := inner + (outer - inner) * f;
|
||||||
|
debug_line_overlay(origin + u * c + v * inner, origin + u * c + v * outer, fill, GIZMO_FILL_WIDTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle of the mouse ray's hit on the ring's plane, measured from u toward v.
|
||||||
|
gizmo_ring_angle :: (origin: Vector3, h: Gizmo_Handle, ray: Ray) -> (ok: bool, angle: float) {
|
||||||
|
ok, point := gizmo_plane_hit(origin, h, ray);
|
||||||
|
if !ok then return false, 0;
|
||||||
|
u, v := gizmo_plane_axes(h);
|
||||||
|
d := point - origin;
|
||||||
|
return true, atan2(dot(d, v), dot(d, u));
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_pick_ring :: (origin: Vector3, len: float, cam: Camera, ray: Ray) -> Gizmo_Handle {
|
||||||
|
best := Gizmo_Handle.NONE;
|
||||||
|
best_dist := GIZMO_RING_PICK * len;
|
||||||
|
for h: Gizmo_Handle.[.RING_X, .RING_Y, .RING_Z] {
|
||||||
|
if !gizmo_ring_faces_camera(origin, h, cam) then continue;
|
||||||
|
ok, point := gizmo_plane_hit(origin, h, ray);
|
||||||
|
if !ok then continue;
|
||||||
|
// Distance from the ring itself, not from its centre.
|
||||||
|
off := abs(length(point - origin) - GIZMO_RING_RADIUS * len);
|
||||||
|
if off < best_dist {
|
||||||
|
best_dist = off;
|
||||||
|
best = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An edge-on ring is a line on screen: impossible to aim at, and its plane
|
||||||
|
// intersection shoots off to infinity, so skip it.
|
||||||
|
gizmo_ring_faces_camera :: (origin: Vector3, h: Gizmo_Handle, cam: Camera) -> bool {
|
||||||
|
return abs(dot(gizmo_axis(h), normalize(cam.position - origin))) >= GIZMO_PLANE_MIN_FACING;
|
||||||
|
}
|
||||||
|
|
||||||
|
gizmo_draw_rings :: (origin: Vector3, len: float, cam: Camera, highlighted: Gizmo_Handle) {
|
||||||
|
for h: Gizmo_Handle.[.RING_X, .RING_Y, .RING_Z] {
|
||||||
|
is_hot := h == highlighted;
|
||||||
|
if !gizmo_ring_faces_camera(origin, h, cam) && !is_hot then continue;
|
||||||
|
u, v := gizmo_plane_axes(h);
|
||||||
|
col := gizmo_color(h, is_hot);
|
||||||
|
r := GIZMO_RING_RADIUS * len;
|
||||||
|
prev := origin + u * r;
|
||||||
|
for i: 1..GIZMO_RING_SEGMENTS {
|
||||||
|
a := (cast(float)i / cast(float)GIZMO_RING_SEGMENTS) * 2 * PI;
|
||||||
|
next := origin + u * (cos(a) * r) + v * (sin(a) * r);
|
||||||
|
debug_line_overlay(prev, next, col, GIZMO_LINE_WIDTH);
|
||||||
|
prev = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FLAG_TEST_ENGINE {
|
||||||
|
gizmo_test_cam :: () -> Camera {
|
||||||
|
// Off the +X+Y+Z octant, so every plane handle faces the viewer and its
|
||||||
|
// signs are all positive.
|
||||||
|
cam : Camera;
|
||||||
|
cam.position = .{5, 5, 5};
|
||||||
|
cam.target = .{0, 0, 0};
|
||||||
|
return cam;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_gizmo_closest_axis :: () {
|
||||||
|
s := begin_suite("gizmo closest point on axis");
|
||||||
|
// Vertical ray dropping onto the +X axis at x = 0.5.
|
||||||
|
ray := Ray.{ origin = .{0.5, 3, 0}, direction = .{0, -1, 0} };
|
||||||
|
t, point, dist := gizmo_closest_axis_t(ray, .{0, 0, 0}, .{1, 0, 0});
|
||||||
|
check(*s, "t is the distance along the axis", abs(t - 0.5) < 0.001);
|
||||||
|
check(*s, "closest point sits on the axis", abs(point.x - 0.5) < 0.001 && abs(point.y) < 0.001 && abs(point.z) < 0.001);
|
||||||
|
check(*s, "intersecting lines have 0 distance", dist < 0.001);
|
||||||
|
|
||||||
|
// A ray offset in Z never meets the axis; the gap is that offset.
|
||||||
|
off := Ray.{ origin = .{0.5, 3, 0.25}, direction = .{0, -1, 0} };
|
||||||
|
_, _, off_dist := gizmo_closest_axis_t(off, .{0, 0, 0}, .{1, 0, 0});
|
||||||
|
check(*s, "offset ray reports its offset as distance", abs(off_dist - 0.25) < 0.001);
|
||||||
|
|
||||||
|
// Parallel lines have no unique closest point.
|
||||||
|
par := Ray.{ origin = .{0, 3, 0}, direction = .{1, 0, 0} };
|
||||||
|
_, _, par_dist := gizmo_closest_axis_t(par, .{0, 0, 0}, .{1, 0, 0});
|
||||||
|
check(*s, "parallel ray is rejected", par_dist > 1000);
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_gizmo_pick :: () {
|
||||||
|
s := begin_suite("gizmo handle picking");
|
||||||
|
cam := gizmo_test_cam();
|
||||||
|
len : float : 1.0;
|
||||||
|
|
||||||
|
// Straight down onto the middle of the +X arrow.
|
||||||
|
axis_ray := Ray.{ origin = .{0.5, 3, 0}, direction = .{0, -1, 0} };
|
||||||
|
check(*s, "aiming at the X arrow picks AXIS_X", gizmo_pick(.{0,0,0}, len, cam, axis_ray) == .AXIS_X);
|
||||||
|
|
||||||
|
// Down onto the XZ pane, which spans 0.22..0.52 on both of its axes.
|
||||||
|
plane_ray := Ray.{ origin = .{0.35, 3, 0.35}, direction = .{0, -1, 0} };
|
||||||
|
check(*s, "aiming at the XZ pane picks PLANE_XZ", gizmo_pick(.{0,0,0}, len, cam, plane_ray) == .PLANE_XZ);
|
||||||
|
|
||||||
|
// Inside the pane's plane but short of its inner edge: no handle there.
|
||||||
|
gap_ray := Ray.{ origin = .{0.1, 3, 0.1}, direction = .{0, -1, 0} };
|
||||||
|
check(*s, "the gap inside the panes picks nothing", gizmo_pick(.{0,0,0}, len, cam, gap_ray) == .NONE);
|
||||||
|
|
||||||
|
// Past the end of every arrow.
|
||||||
|
miss_ray := Ray.{ origin = .{2, 3, 2}, direction = .{0, -1, 0} };
|
||||||
|
check(*s, "aiming past the gizmo picks nothing", gizmo_pick(.{0,0,0}, len, cam, miss_ray) == .NONE);
|
||||||
|
|
||||||
|
// Picking follows the gizmo, not the world origin.
|
||||||
|
moved := Vector3.{10, 4, -7};
|
||||||
|
moved_ray := Ray.{ origin = moved + Vector3.{0.5, 3, 0}, direction = .{0, -1, 0} };
|
||||||
|
check(*s, "picking is relative to the gizmo origin", gizmo_pick(moved, len, cam, moved_ray) == .AXIS_X);
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_gizmo_plane_hit :: () {
|
||||||
|
s := begin_suite("gizmo plane intersection");
|
||||||
|
// PLANE_XZ has a +Y normal, so it is the horizontal plane through the origin.
|
||||||
|
ray := Ray.{ origin = .{1, 4, 2}, direction = .{0, -1, 0} };
|
||||||
|
ok, point := gizmo_plane_hit(.{0, 0, 0}, .PLANE_XZ, ray);
|
||||||
|
check(*s, "downward ray hits the XZ pane's plane", ok);
|
||||||
|
check(*s, "hit lands at the ray's XZ", abs(point.x - 1) < 0.001 && abs(point.z - 2) < 0.001);
|
||||||
|
check(*s, "hit lands on the plane", abs(point.y) < 0.001);
|
||||||
|
|
||||||
|
// The plane passes through the gizmo origin, not the world origin.
|
||||||
|
shifted, sp := gizmo_plane_hit(.{0, 2.5, 0}, .PLANE_XZ, ray);
|
||||||
|
check(*s, "plane follows the gizmo origin", shifted && abs(sp.y - 2.5) < 0.001);
|
||||||
|
|
||||||
|
away := Ray.{ origin = .{1, 4, 2}, direction = .{0, 1, 0} };
|
||||||
|
no_hit, _ := gizmo_plane_hit(.{0, 0, 0}, .PLANE_XZ, away);
|
||||||
|
check(*s, "ray pointing away misses", !no_hit);
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#run {
|
||||||
|
test_gizmo_closest_axis();
|
||||||
|
test_gizmo_pick();
|
||||||
|
test_gizmo_plane_hit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ 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;
|
||||||
@ -59,6 +60,21 @@ 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 {
|
||||||
@ -333,6 +349,8 @@ 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;
|
||||||
|
|
||||||
@ -370,7 +388,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 {
|
} else if current_tool_mode == .INSPECTOR || current_tool_mode == .ENTITY {
|
||||||
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;
|
||||||
@ -510,6 +528,121 @@ remove_trile :: (x: s32, y: s32, z: s32) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
@ -525,6 +658,9 @@ 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;
|
||||||
@ -538,9 +674,14 @@ tick_level_editor :: () {
|
|||||||
current_orientation_face = (current_orientation_face + 1) % 6;
|
current_orientation_face = (current_orientation_face + 1) % 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
ray := get_mouse_ray(*get_level_editor_camera());
|
cam := 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;
|
||||||
@ -632,6 +773,12 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -642,6 +789,9 @@ 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 "");
|
||||||
@ -980,11 +1130,131 @@ 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;
|
||||||
|
|
||||||
@ -1012,6 +1282,7 @@ 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();
|
||||||
}
|
}
|
||||||
@ -1043,12 +1314,14 @@ 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 {
|
if current_tool_mode == .INSPECTOR || current_tool_mode == .ENTITY {
|
||||||
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 inspector_selected {
|
if current_tool_mode == .ENTITY {
|
||||||
|
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);
|
||||||
|
|||||||
408
src/entities.jai
Normal file
408
src/entities.jai
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
// Entity types are plain structs declared in game code, registered with an
|
||||||
|
// @Entity note the way console commands register with @Command. The metaprogram
|
||||||
|
// (src/meta/entity_types.jai) collects them into ENTITY_TYPES, and spawning,
|
||||||
|
// dispatch, editor UI and saving all come from that. Marking a struct @Entity
|
||||||
|
// without embedding Entity, or embedding Entity without the note, is an error.
|
||||||
|
//
|
||||||
|
// Foo :: struct {
|
||||||
|
// #as using base : Entity;
|
||||||
|
// base.type = Foo;
|
||||||
|
// PARTS :: Entity_Part.[...]; // visuals, may be empty
|
||||||
|
// EDITOR_MARKER :: "pack.animation"; // optional, editor-only billboard
|
||||||
|
// } @Entity
|
||||||
|
// entity_tick :: (using foo: *Foo, dt: float) {} // optional
|
||||||
|
// entity_draw :: (foo: *Foo) {} // optional
|
||||||
|
//
|
||||||
|
// Fields of type float, s32, bool, string or Vector3 are editable and saved;
|
||||||
|
// @Slider / @Color notes pick the editor widget.
|
||||||
|
//
|
||||||
|
// Each frame the game calls tick_entities, then tick_entity_emitters, then
|
||||||
|
// add_entity_render_tasks. The level editor calls the last two only: it shows
|
||||||
|
// entities and their particles without running their game logic.
|
||||||
|
|
||||||
|
#scope_export
|
||||||
|
|
||||||
|
Entity :: struct {
|
||||||
|
type : Type;
|
||||||
|
id : u32;
|
||||||
|
position : Vector3;
|
||||||
|
orientation : u8; // 0..23, see orientation.jai
|
||||||
|
emitters : [..]Particle_Emitter_Instance; // built from PARTS, not saved
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity_Part_Kind :: enum {
|
||||||
|
TRILE;
|
||||||
|
BILLBOARD;
|
||||||
|
EMITTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity_Part :: struct {
|
||||||
|
kind : Entity_Part_Kind;
|
||||||
|
trile : string; // TRILE
|
||||||
|
orientation : u8; // TRILE, 0..23
|
||||||
|
animation : string; // BILLBOARD, e.g. "game_core.ball"
|
||||||
|
emitter : string; // EMITTER, a particle definition name
|
||||||
|
offset : Vector3;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity_Type_Info :: struct {
|
||||||
|
name : string;
|
||||||
|
type : Type;
|
||||||
|
parts : []Entity_Part;
|
||||||
|
editor_marker : string;
|
||||||
|
create : () -> *Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
#insert #run,stallable gen_entity_type_table();
|
||||||
|
|
||||||
|
get_entity_type_info :: (type: Type) -> *Entity_Type_Info {
|
||||||
|
for * ENTITY_TYPE_TABLE if it.type == type return it;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_entity_parts :: (type: Type) -> []Entity_Part {
|
||||||
|
info := get_entity_type_info(type);
|
||||||
|
return ifx info then info.parts else .[];
|
||||||
|
}
|
||||||
|
|
||||||
|
get_entity_editor_marker :: (type: Type) -> string {
|
||||||
|
info := get_entity_type_info(type);
|
||||||
|
return ifx info then info.editor_marker else "";
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_type_name :: (type: Type) -> string {
|
||||||
|
return (cast(*Type_Info_Struct) type).name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entities turn about the middle of their own cell, so a rotated entity stays
|
||||||
|
// on the block it was placed on.
|
||||||
|
ENTITY_PIVOT :: Vector3.{0.5, 0.5, 0.5};
|
||||||
|
|
||||||
|
entity_point :: (e: *Entity, offset: Vector3) -> Vector3 {
|
||||||
|
return e.position + ENTITY_PIVOT + rotate_by_orientation(e.orientation, offset - ENTITY_PIVOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A trile part fills a cell rather than being a point, so the pivot cancels out.
|
||||||
|
entity_trile_point :: (e: *Entity, offset: Vector3) -> Vector3 {
|
||||||
|
return e.position + rotate_by_orientation(e.orientation, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orientation is taken up front rather than assigned afterwards because the
|
||||||
|
// emitters are placed relative to it.
|
||||||
|
spawn_entity :: (world: *World, type_name: string, position: Vector3, orientation: u8 = 0) -> *Entity {
|
||||||
|
for ENTITY_TYPE_TABLE {
|
||||||
|
if it.name != type_name continue;
|
||||||
|
e := it.create();
|
||||||
|
e.position = position;
|
||||||
|
e.orientation = orientation;
|
||||||
|
e.id = world.next_entity_id;
|
||||||
|
world.next_entity_id += 1;
|
||||||
|
init_entity_emitters(e);
|
||||||
|
array_add(*world.entities, e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
log_error("Unknown entity type: %", type_name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_first_entity :: (world: *World, type: Type) -> *Entity {
|
||||||
|
for e: world.entities if e.type == type return e;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_entity :: (world: *World, e: *Entity) {
|
||||||
|
for other, i: world.entities {
|
||||||
|
if other != e continue;
|
||||||
|
array_ordered_remove_by_index(*world.entities, i);
|
||||||
|
free_entity(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free_entity :: (e: *Entity) {
|
||||||
|
array_free(e.emitters);
|
||||||
|
free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_all_entities :: (world: *World) {
|
||||||
|
for e: world.entities free_entity(e);
|
||||||
|
array_reset(*world.entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concrete per-type overloads in game code take precedence over these.
|
||||||
|
entity_tick :: (e: *$T/Entity, dt: float) {}
|
||||||
|
entity_draw :: (e: *$T/Entity) { draw_entity_parts(e); }
|
||||||
|
|
||||||
|
tick_entities :: (world: *World, dt: float) {
|
||||||
|
for e: world.entities entity_dispatch_tick(e, dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_dispatch_tick :: (e: *Entity, dt: float) {
|
||||||
|
#insert #run,stallable gen_entity_dispatch("entity_tick(cast(*%)e, dt)");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_dispatch_draw :: (e: *Entity) {
|
||||||
|
#insert #run,stallable gen_entity_dispatch("entity_draw(cast(*%)e)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatches a call on the entity's concrete type. '%' in 'call' becomes the type
|
||||||
|
// name. Also used by the editor to run autoedit on an entity's own fields.
|
||||||
|
gen_entity_dispatch :: (call: string) -> string {
|
||||||
|
builder : String_Builder;
|
||||||
|
for ENTITY_TYPES {
|
||||||
|
name := (cast(*Type_Info_Struct) it).name;
|
||||||
|
print_to_builder(*builder, "if e.type == % { %; return; }\n", name, tprint(call, name));
|
||||||
|
}
|
||||||
|
return builder_to_string(*builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
init_entity_emitters :: (e: *Entity) {
|
||||||
|
for part: get_entity_parts(e.type) {
|
||||||
|
if part.kind != .EMITTER continue;
|
||||||
|
inst : Particle_Emitter_Instance;
|
||||||
|
inst.definition_name = part.emitter;
|
||||||
|
inst.definition = get_emitter_def(part.emitter);
|
||||||
|
inst.offset = part.offset;
|
||||||
|
inst.position = entity_point(e, part.offset);
|
||||||
|
inst.active = true;
|
||||||
|
array_add(*e.emitters, inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tick_entity_emitters :: (world: *World, dt: float) {
|
||||||
|
for e: world.entities {
|
||||||
|
for *inst: e.emitters {
|
||||||
|
if inst.definition == null {
|
||||||
|
inst.definition = get_emitter_def(inst.definition_name);
|
||||||
|
if inst.definition == null continue;
|
||||||
|
}
|
||||||
|
inst.position = entity_point(e, inst.offset);
|
||||||
|
tick_emitter_instance(inst, dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once per frame, from the game and from the editor. Trile draws are batched per
|
||||||
|
// (trile, chunk) and flushed as TRILE_DYNAMIC tasks at the end.
|
||||||
|
add_entity_render_tasks :: (world: *World) {
|
||||||
|
array_reset_keeping_memory(*entity_trile_batches);
|
||||||
|
for e: world.entities entity_dispatch_draw(e);
|
||||||
|
for *batch: entity_trile_batches {
|
||||||
|
task : Rendering_Task_Trile_Dynamic;
|
||||||
|
task.trile = batch.trile;
|
||||||
|
task.positions = batch.positions;
|
||||||
|
task.chunk_key = batch.chunk_key;
|
||||||
|
task.worldConf = *world.conf;
|
||||||
|
add_rendering_task(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_draw_trile :: (e: *Entity, trile: string, offset: Vector3, orientation: u8 = 0) {
|
||||||
|
pos := entity_trile_point(e, offset);
|
||||||
|
ori := compose_orientations(e.orientation, orientation);
|
||||||
|
key := world_to_chunk_coord(cast(s32) floor(pos.x), cast(s32) floor(pos.y), cast(s32) floor(pos.z));
|
||||||
|
instance := Vector4.{pos.x, pos.y, pos.z, cast(float) ori};
|
||||||
|
|
||||||
|
for *batch: entity_trile_batches {
|
||||||
|
if batch.trile == trile && batch.chunk_key == key {
|
||||||
|
array_add(*batch.positions, instance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch : Entity_Trile_Batch;
|
||||||
|
batch.trile = trile;
|
||||||
|
batch.chunk_key = key;
|
||||||
|
batch.positions.allocator = temp;
|
||||||
|
array_add(*batch.positions, instance);
|
||||||
|
array_add(*entity_trile_batches, batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_draw_billboard :: (e: *Entity, animation: string, offset: Vector3, frame: s32 = 0) {
|
||||||
|
anim := get_animation_from_string(animation);
|
||||||
|
if anim == null return;
|
||||||
|
task : Rendering_Task_Billboard;
|
||||||
|
task.position = entity_point(e, offset);
|
||||||
|
task.animation = anim;
|
||||||
|
task.frame = frame;
|
||||||
|
add_rendering_task(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_entity_parts :: (e: *Entity) {
|
||||||
|
for part: get_entity_parts(e.type) {
|
||||||
|
if part.kind == {
|
||||||
|
case .TRILE; entity_draw_trile(e, part.trile, part.offset, part.orientation);
|
||||||
|
case .BILLBOARD; entity_draw_billboard(e, part.animation, part.offset);
|
||||||
|
case .EMITTER; // tick_entity_emitters handles these
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields are saved as name/value string pairs rather than typed JSON, so adding,
|
||||||
|
// removing or renaming one never breaks loading an older world.
|
||||||
|
Entity_Field :: struct {
|
||||||
|
name : string;
|
||||||
|
value : string;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_fields_to_strings :: (e: *Entity) -> []Entity_Field {
|
||||||
|
result : [..]Entity_Field;
|
||||||
|
result.allocator = temp;
|
||||||
|
for member: entity_saved_members(e.type) {
|
||||||
|
ptr := (cast(*u8) e) + member.offset_in_bytes;
|
||||||
|
value : string;
|
||||||
|
if member.type == type_info(float) value = tprint("%", (cast(*float) ptr).*);
|
||||||
|
else if member.type == type_info(s32) value = tprint("%", (cast(*s32) ptr).*);
|
||||||
|
else if member.type == type_info(bool) value = tprint("%", (cast(*bool) ptr).*);
|
||||||
|
else if member.type == type_info(string) value = (cast(*string) ptr).*;
|
||||||
|
else if member.type == type_info(Vector3) {
|
||||||
|
v := (cast(*Vector3) ptr).*;
|
||||||
|
value = tprint("% % %", v.x, v.y, v.z);
|
||||||
|
} else continue;
|
||||||
|
array_add(*result, .{member.name, value});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_apply_field :: (e: *Entity, name: string, value: string) {
|
||||||
|
for member: entity_saved_members(e.type) {
|
||||||
|
if member.name != name continue;
|
||||||
|
ptr := (cast(*u8) e) + member.offset_in_bytes;
|
||||||
|
if member.type == type_info(float) {
|
||||||
|
v, ok := string_to_float(value);
|
||||||
|
if ok { (cast(*float) ptr).* = v; }
|
||||||
|
} else if member.type == type_info(s32) {
|
||||||
|
v, ok := string_to_int(value);
|
||||||
|
if ok { (cast(*s32) ptr).* = cast(s32) v; }
|
||||||
|
} else if member.type == type_info(bool) {
|
||||||
|
(cast(*bool) ptr).* = (value == "true");
|
||||||
|
} else if member.type == type_info(string) {
|
||||||
|
(cast(*string) ptr).* = copy_string(value);
|
||||||
|
} else if member.type == type_info(Vector3) {
|
||||||
|
parts := split(value, " ",, temp);
|
||||||
|
if parts.count != 3 return;
|
||||||
|
v : Vector3;
|
||||||
|
x, okx := string_to_float(parts[0]);
|
||||||
|
y, oky := string_to_float(parts[1]);
|
||||||
|
z, okz := string_to_float(parts[2]);
|
||||||
|
if okx && oky && okz { (cast(*Vector3) ptr).* = .{x, y, z}; }
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log_warn("Entity type % has no field '%', skipping", entity_type_name(e.type), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file
|
||||||
|
|
||||||
|
#import "String";
|
||||||
|
|
||||||
|
// The members an entity type declares itself: constants and the Entity base are
|
||||||
|
// neither editable nor saved.
|
||||||
|
entity_saved_members :: (type: Type) -> []Type_Info_Struct_Member {
|
||||||
|
result : [..]Type_Info_Struct_Member;
|
||||||
|
result.allocator = temp;
|
||||||
|
for (cast(*Type_Info_Struct) type).members {
|
||||||
|
if it.flags & (.CONSTANT | .USING) continue;
|
||||||
|
array_add(*result, it);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity_Trile_Batch :: struct {
|
||||||
|
trile : string;
|
||||||
|
chunk_key : Chunk_Key;
|
||||||
|
positions : [..]Vector4;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_trile_batches : [..]Entity_Trile_Batch;
|
||||||
|
|
||||||
|
#if FLAG_TEST_ENGINE {
|
||||||
|
// The two placement helpers use different pivots, and getting them the wrong
|
||||||
|
// way round only shows up as parts drifting off a rotated entity, so both are
|
||||||
|
// pinned to the invariant they exist for.
|
||||||
|
test_entity_part_placement :: () {
|
||||||
|
s := begin_suite("entity part placement");
|
||||||
|
|
||||||
|
e : Entity;
|
||||||
|
e.position = .{10, 3, -5};
|
||||||
|
|
||||||
|
// A trile part on the entity's own cell has to stay on that cell however
|
||||||
|
// the entity is turned, or rotating would walk it off the block it was
|
||||||
|
// placed on.
|
||||||
|
on_its_block := true;
|
||||||
|
for ori: 0..ORIENTATION_COUNT-1 {
|
||||||
|
e.orientation = cast(u8) ori;
|
||||||
|
if entity_trile_point(*e, .{0, 0, 0}) != e.position on_its_block = false;
|
||||||
|
}
|
||||||
|
check(*s, "a trile part on the entity's own cell never moves", on_its_block);
|
||||||
|
|
||||||
|
// Point parts turn about the middle of that same cell, so a part sitting
|
||||||
|
// at the middle is the fixed point of all 24 rotations.
|
||||||
|
at_the_middle := true;
|
||||||
|
for ori: 0..ORIENTATION_COUNT-1 {
|
||||||
|
e.orientation = cast(u8) ori;
|
||||||
|
if entity_point(*e, ENTITY_PIVOT) != e.position + ENTITY_PIVOT at_the_middle = false;
|
||||||
|
}
|
||||||
|
check(*s, "a point part at the middle never moves", at_the_middle);
|
||||||
|
|
||||||
|
e.orientation = 0;
|
||||||
|
check(*s, "unturned point parts sit at their authored offset",
|
||||||
|
entity_point(*e, .{1, 2, 3}) == e.position + Vector3.{1, 2, 3});
|
||||||
|
check(*s, "unturned trile parts sit at their authored offset",
|
||||||
|
entity_trile_point(*e, .{1, 2, 3}) == e.position + Vector3.{1, 2, 3});
|
||||||
|
|
||||||
|
// Orientation 1 is the quarter twist about Y, which sends X to Z.
|
||||||
|
e.orientation = 1;
|
||||||
|
check(*s, "a turned entity carries its parts around with it",
|
||||||
|
entity_trile_point(*e, .{1, 0, 0}) == e.position + Vector3.{0, 0, 1});
|
||||||
|
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every declared type must reach the table, or it would be unspawnable and
|
||||||
|
// unsaveable with no diagnostic anywhere.
|
||||||
|
test_entity_type_table :: () {
|
||||||
|
s := begin_suite("entity type table");
|
||||||
|
check(*s, "the table covers every declared type", ENTITY_TYPE_TABLE.count == ENTITY_TYPES.count);
|
||||||
|
|
||||||
|
found_all := true;
|
||||||
|
for type: ENTITY_TYPES if get_entity_type_info(type) == null found_all = false;
|
||||||
|
check(*s, "every declared type is findable by type", found_all);
|
||||||
|
|
||||||
|
names_match := true;
|
||||||
|
for info: ENTITY_TYPE_TABLE if info.name != entity_type_name(info.type) names_match = false;
|
||||||
|
check(*s, "table names match the type names saves use", names_match);
|
||||||
|
|
||||||
|
// Entity itself is never a declared type, so it stands in for one that
|
||||||
|
// this build does not have.
|
||||||
|
check(*s, "an unknown type has no info", get_entity_type_info(Entity) == null);
|
||||||
|
check(*s, "an unknown type has no parts", get_entity_parts(Entity).count == 0);
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stallable because the type table is: ENTITY_TYPES only exists once the
|
||||||
|
// metaprogram has seen every @Entity in the program.
|
||||||
|
#run,stallable {
|
||||||
|
test_entity_part_placement();
|
||||||
|
test_entity_type_table();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_entity_type_table :: () -> string {
|
||||||
|
builder : String_Builder;
|
||||||
|
append(*builder, "ENTITY_TYPE_TABLE :: Entity_Type_Info.[\n");
|
||||||
|
for ENTITY_TYPES {
|
||||||
|
ti := cast(*Type_Info_Struct) it;
|
||||||
|
marker := "\"\"";
|
||||||
|
for member: ti.members {
|
||||||
|
if member.name == "EDITOR_MARKER" && (member.flags & .CONSTANT) {
|
||||||
|
marker = tprint("%.EDITOR_MARKER", ti.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print_to_builder(*builder,
|
||||||
|
" .{name = \"%\", type = %, parts = %.PARTS, editor_marker = %, create = () -> *Entity { return New(%); }},\n",
|
||||||
|
ti.name, ti.name, ti.name, marker, ti.name);
|
||||||
|
}
|
||||||
|
append(*builder, "];\n");
|
||||||
|
return builder_to_string(*builder);
|
||||||
|
}
|
||||||
@ -28,6 +28,7 @@ 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;
|
||||||
@ -180,6 +181,7 @@ set_default_bindings :: () {
|
|||||||
set(.LEVEL_TOOL_LINE, cast(Key_Code) #char "4");
|
set(.LEVEL_TOOL_LINE, cast(Key_Code) #char "4");
|
||||||
set(.LEVEL_TOOL_INSPECTOR, cast(Key_Code) #char "5");
|
set(.LEVEL_TOOL_INSPECTOR, cast(Key_Code) #char "5");
|
||||||
set(.LEVEL_TOOL_VIEWER, cast(Key_Code) #char "6");
|
set(.LEVEL_TOOL_VIEWER, cast(Key_Code) #char "6");
|
||||||
|
set(.LEVEL_TOOL_ENTITY, cast(Key_Code) #char "7");
|
||||||
set(.LEVEL_TAB_TOOLS, cast(Key_Code) #char "T");
|
set(.LEVEL_TAB_TOOLS, cast(Key_Code) #char "T");
|
||||||
set(.LEVEL_TAB_INFO, cast(Key_Code) #char "I");
|
set(.LEVEL_TAB_INFO, cast(Key_Code) #char "I");
|
||||||
set(.LEVEL_TAB_TACOMA, cast(Key_Code) #char "X");
|
set(.LEVEL_TAB_TACOMA, cast(Key_Code) #char "X");
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#if FLAG_TEST_EXE_ENGINE {
|
#if FLAG_USE_TEST_GAME {
|
||||||
PACK_DIR :: "./test_packs";
|
PACK_DIR :: "./test_packs";
|
||||||
GAME_RESOURCES_DIR :: "./test_game/resources";
|
GAME_RESOURCES_DIR :: "./test_game/resources";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -18,8 +18,10 @@ _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!
|
// want those meddling with our log row allocation! Before the engine has
|
||||||
push_allocator(default_allocator);
|
// set one up — at compile time, in a #run — there is nothing to protect
|
||||||
|
// against and nothing to allocate from, so the current one has to do.
|
||||||
|
push_allocator(ifx default_allocator.proc then default_allocator else context.allocator);
|
||||||
line := copy_string(tprint("%1%2", prefix, message));
|
line := copy_string(tprint("%1%2", prefix, message));
|
||||||
print("%\n", line);
|
print("%\n", line);
|
||||||
console_add_output_line(line);
|
console_add_output_line(line);
|
||||||
|
|||||||
25
src/main.jai
25
src/main.jai
@ -15,6 +15,17 @@ 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";
|
||||||
@ -32,6 +43,8 @@ stbi :: #import "stb_image";
|
|||||||
#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";
|
||||||
@ -40,10 +53,10 @@ stbi :: #import "stb_image";
|
|||||||
#load "loading_screen.jai";
|
#load "loading_screen.jai";
|
||||||
#load "ui/demo.jai";
|
#load "ui/demo.jai";
|
||||||
|
|
||||||
#if !FLAG_TEST_EXE_ENGINE {
|
#if FLAG_USE_TEST_GAME {
|
||||||
#load "../game/game.jai";
|
|
||||||
} else {
|
|
||||||
#load "../test_game/game.jai";
|
#load "../test_game/game.jai";
|
||||||
|
} else {
|
||||||
|
#load "../game/game.jai";
|
||||||
}
|
}
|
||||||
|
|
||||||
last_frame_time : float64; // timestamp of the last frame
|
last_frame_time : float64; // timestamp of the last frame
|
||||||
@ -139,6 +152,10 @@ 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 :: () {
|
||||||
@ -225,7 +242,7 @@ frame :: () {
|
|||||||
|
|
||||||
add_frame_profiling_point("After loading logic");
|
add_frame_profiling_point("After loading logic");
|
||||||
|
|
||||||
#if FLAG_TEST_EXE_ENGINE {
|
#if FLAG_ANY_EXE_TEST {
|
||||||
run_exe_tests();
|
run_exe_tests();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
98
src/meta/entity_types.jai
Normal file
98
src/meta/entity_types.jai
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
Entity types register themselves with an @Entity note on the struct, the same
|
||||||
|
way console commands register with @Command. The generators in entities.jai
|
||||||
|
all read one list, ENTITY_TYPES, which is built here and injected once the
|
||||||
|
rest of the program has been typechecked — that is why those generators are
|
||||||
|
written as #run,stallable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "Sort";
|
||||||
|
|
||||||
|
entity_type_names : [..]string;
|
||||||
|
entity_types_emitted := false;
|
||||||
|
|
||||||
|
add_entity_types :: (message: *Message, w: *Workspace) {
|
||||||
|
if message.kind == .PHASE {
|
||||||
|
phase := cast(*Message_Phase) message;
|
||||||
|
if phase.phase == .TYPECHECKED_ALL_WE_CAN && !entity_types_emitted {
|
||||||
|
entity_types_emitted = true;
|
||||||
|
emit_entity_types(w);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if message.kind != .TYPECHECKED then return;
|
||||||
|
code := cast(*Message_Typechecked) message;
|
||||||
|
|
||||||
|
for code.declarations {
|
||||||
|
decl := it.expression;
|
||||||
|
if !decl.expression || decl.expression.kind != .STRUCT then continue;
|
||||||
|
if !is_main_program(decl) then continue;
|
||||||
|
|
||||||
|
noted := false;
|
||||||
|
for note: decl.notes if note.text == "Entity" noted = true;
|
||||||
|
embeds := struct_embeds_entity(cast(*Code_Struct) decl.expression);
|
||||||
|
|
||||||
|
// Without this the note is as easy to forget as the hand-written list it
|
||||||
|
// replaced, and forgetting it is silent: the type compiles fine and is
|
||||||
|
// simply invisible to spawning, the editor and saving.
|
||||||
|
if embeds && !noted {
|
||||||
|
report(decl, sprint("Struct % embeds Entity but is not marked @Entity, so nothing can spawn, edit or save it.", decl.name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !noted then continue;
|
||||||
|
|
||||||
|
if !embeds {
|
||||||
|
report(decl, sprint("Struct % is marked @Entity but has no '#as using base : Entity;'.", decl.name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if decl.flags & .SCOPE_FILE {
|
||||||
|
report(decl, sprint("Entity type % is file scoped, that is not okay. The generated type table has to name it.", decl.name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
array_add(*entity_type_names, sprint("%", decl.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file
|
||||||
|
|
||||||
|
// Emitted even when the game declares no entity types at all: the generators in
|
||||||
|
// entities.jai are stalled waiting for this name and would never come unstuck.
|
||||||
|
emit_entity_types :: (w: *Workspace) {
|
||||||
|
// Sorted by name so the generated table — and with it the order of the
|
||||||
|
// editor's spawn list — does not shuffle between builds just because
|
||||||
|
// typechecking happened to finish in a different order.
|
||||||
|
quick_sort(entity_type_names, compare_strings);
|
||||||
|
|
||||||
|
builder : String_Builder;
|
||||||
|
append(*builder, "ENTITY_TYPES :: Type.[");
|
||||||
|
for entity_type_names {
|
||||||
|
if it_index > 0 then append(*builder, ", ");
|
||||||
|
append(*builder, it);
|
||||||
|
}
|
||||||
|
append(*builder, "];\n");
|
||||||
|
add_build_string(builder_to_string(*builder), w.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct_embeds_entity :: (s: *Code_Struct) -> bool {
|
||||||
|
if !s.defined_type then return false;
|
||||||
|
for s.defined_type.members {
|
||||||
|
if !(it.flags & .USING) then continue;
|
||||||
|
if it.type.type != .STRUCT then continue;
|
||||||
|
if (cast(*Type_Info_Struct) it.type).name == "Entity" then return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game and engine code only. Module code never declares entity types, and
|
||||||
|
// build strings have no enclosing load to ask about.
|
||||||
|
is_main_program :: (decl: *Code_Declaration) -> bool {
|
||||||
|
if !decl.enclosing_load then return false;
|
||||||
|
if !decl.enclosing_load.enclosing_import then return false;
|
||||||
|
return decl.enclosing_load.enclosing_import.module_type == .MAIN_PROGRAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
report :: (decl: *Code_Declaration, error: string) {
|
||||||
|
compiler_report(error, make_location(cast(*Code_Node) decl), .ERROR);
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
#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;
|
||||||
@ -14,4 +16,6 @@ custom_message_handler :: (message: *Message, w: *Workspace) {
|
|||||||
remove_getrect_custom_cursor(message, w);
|
remove_getrect_custom_cursor(message, w);
|
||||||
|
|
||||||
add_console_commands(message, w);
|
add_console_commands(message, w);
|
||||||
|
|
||||||
|
add_entity_types(message, w);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,9 @@
|
|||||||
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;
|
||||||
|
|
||||||
ok, left, right := split_from_right(name, #char ".");
|
// Aseprite sources are inputs (see resource_mirror.jai), not shippable
|
||||||
if right == "aseprite" {
|
// assets; only the .sheet.png/.sheet.json they export go into the pack.
|
||||||
print("Ignoring % as aseprite file...\n", name);
|
if is_aseprite_source(name) return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -53,11 +51,10 @@ create_pack :: (include_test_resources: bool = false, pack_dir: string = "./pack
|
|||||||
#import "Simple_Package";
|
#import "Simple_Package";
|
||||||
#import "File";
|
#import "File";
|
||||||
|
|
||||||
util.visit_files("./resources", true, true, file_visit_handler);
|
// Packs come from the processed copy of the resource trees, never the trees
|
||||||
if include_test_resources {
|
// themselves. See resource_mirror.jai.
|
||||||
util.visit_files("./test_game/resources", true, true, file_visit_handler);
|
for prepare_resource_mirror(include_test_resources) {
|
||||||
} else {
|
util.visit_files(it, true, true, file_visit_handler);
|
||||||
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);
|
||||||
|
|||||||
291
src/orientation.jai
Normal file
291
src/orientation.jai
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
// The 24 axis-aligned cube rotations, encoded as 'face * 4 + twist' exactly as
|
||||||
|
// the trile shaders decode them (see get_orientation_matrix in
|
||||||
|
// shader_trile_shadow.glsl). Everything here mirrors that decode, so a rotation
|
||||||
|
// applied to a position on the CPU lines up with the geometry the GPU draws.
|
||||||
|
//
|
||||||
|
// Note the shaders build their mat3s from columns, which makes each of their
|
||||||
|
// rot_* helpers the transpose of the usual form. The matrices below are the
|
||||||
|
// effective row-major operators (v' = M * v), so they match what the shader
|
||||||
|
// actually does rather than what its helper names suggest.
|
||||||
|
|
||||||
|
ORIENTATION_COUNT :: 24;
|
||||||
|
|
||||||
|
Orientation_Matrix :: struct {
|
||||||
|
m : [3][3]s8;
|
||||||
|
}
|
||||||
|
|
||||||
|
orientation_matrix :: (ori: u8) -> Orientation_Matrix {
|
||||||
|
return ORIENTATION_MATRICES[ori % ORIENTATION_COUNT];
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate_by_orientation :: (ori: u8, v: Vector3) -> Vector3 {
|
||||||
|
m := orientation_matrix(ori).m;
|
||||||
|
return .{
|
||||||
|
cast(float)m[0][0] * v.x + cast(float)m[0][1] * v.y + cast(float)m[0][2] * v.z,
|
||||||
|
cast(float)m[1][0] * v.x + cast(float)m[1][1] * v.y + cast(float)m[1][2] * v.z,
|
||||||
|
cast(float)m[2][0] * v.x + cast(float)m[2][1] * v.y + cast(float)m[2][2] * v.z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The orientation equal to applying 'inner' first and then 'outer'. Used to fold
|
||||||
|
// an entity's rotation into the orientation its parts were authored with.
|
||||||
|
compose_orientations :: (outer: u8, inner: u8) -> u8 {
|
||||||
|
return ORIENTATION_COMPOSE[outer % ORIENTATION_COUNT][inner % ORIENTATION_COUNT];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The quarter turn that carries 'u' onto 'v' and leaves 'n' alone, where u, v, n
|
||||||
|
// are a set of perpendicular unit axes. Taking the basis from the caller keeps
|
||||||
|
// the result consistent with whatever handedness the caller draws with, instead
|
||||||
|
// of relying on a sign convention agreed at a distance.
|
||||||
|
orientation_quarter_turn :: (u: Vector3, v: Vector3, n: Vector3) -> u8 {
|
||||||
|
// M = v*uT - u*vT + n*nT sends u -> v, v -> -u, n -> n.
|
||||||
|
m : Orientation_Matrix;
|
||||||
|
ui := to_axis_ints(u);
|
||||||
|
vi := to_axis_ints(v);
|
||||||
|
ni := to_axis_ints(n);
|
||||||
|
for i: 0..2 {
|
||||||
|
for j: 0..2 {
|
||||||
|
m.m[i][j] = vi[i]*ui[j] - ui[i]*vi[j] + ni[i]*ni[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ori, ok := orientation_from_matrix(m);
|
||||||
|
if !ok {
|
||||||
|
log_error("Rotate gizmo basis is not axis-aligned; ignoring the turn.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ori;
|
||||||
|
}
|
||||||
|
|
||||||
|
orientation_from_matrix :: (m: Orientation_Matrix) -> (ori: u8, ok: bool) {
|
||||||
|
for i: 0..ORIENTATION_COUNT-1 {
|
||||||
|
if mat3_equal(m, ORIENTATION_MATRICES[i]) return cast(u8) i, true;
|
||||||
|
}
|
||||||
|
return 0, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file
|
||||||
|
|
||||||
|
ORIENTATION_MATRICES :: #run generate_orientation_matrices();
|
||||||
|
ORIENTATION_COMPOSE :: #run generate_orientation_compose_table();
|
||||||
|
|
||||||
|
// Axis vectors only ever hold 0 or +-1 here, but they arrive as floats.
|
||||||
|
to_axis_ints :: (v: Vector3) -> [3]s8 {
|
||||||
|
round_axis :: (f: float) -> s8 {
|
||||||
|
if f > 0.5 then return 1;
|
||||||
|
if f < -0.5 then return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return .[round_axis(v.x), round_axis(v.y), round_axis(v.z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
mat3_of :: (a: s8, b: s8, c: s8, d: s8, e: s8, f: s8, g: s8, h: s8, i: s8) -> Orientation_Matrix {
|
||||||
|
r : Orientation_Matrix;
|
||||||
|
r.m[0][0] = a; r.m[0][1] = b; r.m[0][2] = c;
|
||||||
|
r.m[1][0] = d; r.m[1][1] = e; r.m[1][2] = f;
|
||||||
|
r.m[2][0] = g; r.m[2][1] = h; r.m[2][2] = i;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (c, s) are cos and sin of the angle, always in {-1, 0, 1} here.
|
||||||
|
rot_x_effective :: (s: s8, c: s8) -> Orientation_Matrix { return mat3_of(1, 0, 0, 0, c, s, 0, -s, c); }
|
||||||
|
rot_y_effective :: (s: s8, c: s8) -> Orientation_Matrix { return mat3_of(c, 0, -s, 0, 1, 0, s, 0, c); }
|
||||||
|
rot_z_effective :: (s: s8, c: s8) -> Orientation_Matrix { return mat3_of(c, s, 0, -s, c, 0, 0, 0, 1); }
|
||||||
|
|
||||||
|
mat3_mul :: (a: Orientation_Matrix, b: Orientation_Matrix) -> Orientation_Matrix {
|
||||||
|
r : Orientation_Matrix;
|
||||||
|
for i: 0..2 {
|
||||||
|
for j: 0..2 {
|
||||||
|
sum : s8 = 0;
|
||||||
|
for k: 0..2 sum += a.m[i][k] * b.m[k][j];
|
||||||
|
r.m[i][j] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat3_equal :: (a: Orientation_Matrix, b: Orientation_Matrix) -> bool {
|
||||||
|
for i: 0..2 for j: 0..2 if a.m[i][j] != b.m[i][j] return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors get_orientation_matrix: a face rotation followed by a twist about Y.
|
||||||
|
generate_orientation_matrices :: () -> [ORIENTATION_COUNT]Orientation_Matrix {
|
||||||
|
identity :: #run mat3_of(1, 0, 0, 0, 1, 0, 0, 0, 1);
|
||||||
|
// sin/cos of face and twist angles, written out to keep this exact.
|
||||||
|
faces : [6]Orientation_Matrix;
|
||||||
|
faces[0] = identity;
|
||||||
|
faces[1] = rot_x_effective( 0, -1); // PI
|
||||||
|
faces[2] = rot_z_effective(-1, 0); // -PI/2
|
||||||
|
faces[3] = rot_z_effective( 1, 0); // PI/2
|
||||||
|
faces[4] = rot_x_effective( 1, 0); // PI/2
|
||||||
|
faces[5] = rot_x_effective(-1, 0); // -PI/2
|
||||||
|
|
||||||
|
twists : [4]Orientation_Matrix;
|
||||||
|
twists[0] = identity;
|
||||||
|
twists[1] = rot_y_effective( 1, 0);
|
||||||
|
twists[2] = rot_y_effective( 0, -1);
|
||||||
|
twists[3] = rot_y_effective(-1, 0);
|
||||||
|
|
||||||
|
result : [ORIENTATION_COUNT]Orientation_Matrix;
|
||||||
|
for face: 0..5 {
|
||||||
|
for twist: 0..3 {
|
||||||
|
result[face * 4 + twist] = mat3_mul(faces[face], twists[twist]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_orientation_compose_table :: () -> [ORIENTATION_COUNT][ORIENTATION_COUNT]u8 {
|
||||||
|
mats := generate_orientation_matrices();
|
||||||
|
table : [ORIENTATION_COUNT][ORIENTATION_COUNT]u8;
|
||||||
|
for a: 0..ORIENTATION_COUNT-1 {
|
||||||
|
for b: 0..ORIENTATION_COUNT-1 {
|
||||||
|
product := mat3_mul(mats[a], mats[b]);
|
||||||
|
found := false;
|
||||||
|
for c: 0..ORIENTATION_COUNT-1 {
|
||||||
|
if mat3_equal(product, mats[c]) {
|
||||||
|
table[a][b] = cast(u8) c;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found, "Cube rotation % * % is not one of the 24 orientations", a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if FLAG_TEST_ENGINE {
|
||||||
|
v3_eq :: (a: Vector3, b: Vector3) -> bool {
|
||||||
|
return abs(a.x - b.x) < 0.001 && abs(a.y - b.y) < 0.001 && abs(a.z - b.z) < 0.001;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_orientation_set :: () {
|
||||||
|
s := begin_suite("cube orientations");
|
||||||
|
|
||||||
|
distinct := true;
|
||||||
|
for i: 0..ORIENTATION_COUNT-1 {
|
||||||
|
for j: i+1..ORIENTATION_COUNT-1 {
|
||||||
|
if mat3_equal(ORIENTATION_MATRICES[i], ORIENTATION_MATRICES[j]) distinct = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(*s, "all 24 orientations are distinct", distinct);
|
||||||
|
|
||||||
|
// Rotations preserve handedness, so every determinant must be +1. A -1
|
||||||
|
// would mean the set had picked up a reflection.
|
||||||
|
proper := true;
|
||||||
|
for i: 0..ORIENTATION_COUNT-1 {
|
||||||
|
m := ORIENTATION_MATRICES[i].m;
|
||||||
|
det := cast(int)m[0][0] * (cast(int)m[1][1]*m[2][2] - cast(int)m[1][2]*m[2][1])
|
||||||
|
- cast(int)m[0][1] * (cast(int)m[1][0]*m[2][2] - cast(int)m[1][2]*m[2][0])
|
||||||
|
+ cast(int)m[0][2] * (cast(int)m[1][0]*m[2][1] - cast(int)m[1][1]*m[2][0]);
|
||||||
|
if det != 1 proper = false;
|
||||||
|
}
|
||||||
|
check(*s, "every orientation is a proper rotation", proper);
|
||||||
|
|
||||||
|
check(*s, "orientation 0 is the identity",
|
||||||
|
v3_eq(rotate_by_orientation(0, .{1, 2, 3}), .{1, 2, 3}));
|
||||||
|
|
||||||
|
// Orientations 1..3 are the twists: rotations about Y, so Y is fixed.
|
||||||
|
twists_about_y := true;
|
||||||
|
for ori: 1..3 {
|
||||||
|
if !v3_eq(rotate_by_orientation(cast(u8)ori, .{0, 1, 0}), .{0, 1, 0}) twists_about_y = false;
|
||||||
|
}
|
||||||
|
check(*s, "the twist orientations turn about Y", twists_about_y);
|
||||||
|
|
||||||
|
// Rotations are rigid: lengths survive.
|
||||||
|
lengths_kept := true;
|
||||||
|
for ori: 0..ORIENTATION_COUNT-1 {
|
||||||
|
r := rotate_by_orientation(cast(u8)ori, .{1, 2, 3});
|
||||||
|
if abs(length(r) - length(Vector3.{1, 2, 3})) > 0.001 lengths_kept = false;
|
||||||
|
}
|
||||||
|
check(*s, "rotating preserves length", lengths_kept);
|
||||||
|
|
||||||
|
// Pinned against the shader's decode. If these drift, entity part offsets
|
||||||
|
// will rotate the opposite way from the trile geometry they belong to.
|
||||||
|
// Orientation 1 is face 0 twist 1, the shader's rot_y(PI/2).
|
||||||
|
check(*s, "orientation 1 (twist 90) sends X to Z",
|
||||||
|
v3_eq(rotate_by_orientation(1, .{1, 0, 0}), .{0, 0, 1}));
|
||||||
|
// Orientation 4 is face 1 twist 0, the shader's rot_x(PI).
|
||||||
|
check(*s, "orientation 4 (face 1) flips Y and Z",
|
||||||
|
v3_eq(rotate_by_orientation(4, .{0, 1, 1}), .{0, -1, -1}));
|
||||||
|
// Orientation 16 is face 4 twist 0, the shader's rot_x(PI/2).
|
||||||
|
check(*s, "orientation 16 (face 4) sends Y to -Z",
|
||||||
|
v3_eq(rotate_by_orientation(16, .{0, 1, 0}), .{0, 0, -1}));
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_orientation_compose :: () {
|
||||||
|
s := begin_suite("orientation composition");
|
||||||
|
|
||||||
|
identity_ok := true;
|
||||||
|
for ori: 0..ORIENTATION_COUNT-1 {
|
||||||
|
if compose_orientations(0, cast(u8)ori) != cast(u8)ori identity_ok = false;
|
||||||
|
if compose_orientations(cast(u8)ori, 0) != cast(u8)ori identity_ok = false;
|
||||||
|
}
|
||||||
|
check(*s, "composing with orientation 0 changes nothing", identity_ok);
|
||||||
|
|
||||||
|
// Composing a fixed orientation with each of the 24 must hit each exactly
|
||||||
|
// once, or the table is not a group and some rotations are unreachable.
|
||||||
|
permutation := true;
|
||||||
|
for a: 0..ORIENTATION_COUNT-1 {
|
||||||
|
seen : [ORIENTATION_COUNT]bool;
|
||||||
|
for b: 0..ORIENTATION_COUNT-1 {
|
||||||
|
r := compose_orientations(cast(u8)a, cast(u8)b);
|
||||||
|
if seen[r] permutation = false;
|
||||||
|
seen[r] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(*s, "every row of the compose table is a permutation", permutation);
|
||||||
|
|
||||||
|
// compose(outer, inner) must mean "inner first, then outer".
|
||||||
|
order_ok := true;
|
||||||
|
v := Vector3.{1, 2, 3};
|
||||||
|
for a: 0..ORIENTATION_COUNT-1 {
|
||||||
|
for b: 0..ORIENTATION_COUNT-1 {
|
||||||
|
combined := rotate_by_orientation(compose_orientations(cast(u8)a, cast(u8)b), v);
|
||||||
|
stepwise := rotate_by_orientation(cast(u8)a, rotate_by_orientation(cast(u8)b, v));
|
||||||
|
if !v3_eq(combined, stepwise) order_ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(*s, "composing matches applying inner then outer", order_ok);
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_orientation_quarter_turn :: () {
|
||||||
|
s := begin_suite("orientation quarter turns");
|
||||||
|
X :: Vector3.{1, 0, 0};
|
||||||
|
Y :: Vector3.{0, 1, 0};
|
||||||
|
Z :: Vector3.{0, 0, 1};
|
||||||
|
|
||||||
|
q := orientation_quarter_turn(X, Z, Y); // turn X toward Z about Y
|
||||||
|
check(*s, "a quarter turn carries u onto v", v3_eq(rotate_by_orientation(q, X), Z));
|
||||||
|
check(*s, "a quarter turn carries v onto -u", v3_eq(rotate_by_orientation(q, Z), .{-1, 0, 0}));
|
||||||
|
check(*s, "a quarter turn leaves its axis alone", v3_eq(rotate_by_orientation(q, Y), Y));
|
||||||
|
|
||||||
|
four := compose_orientations(q, compose_orientations(q, compose_orientations(q, q)));
|
||||||
|
check(*s, "four quarter turns return to the start", four == 0);
|
||||||
|
|
||||||
|
// Dragging the ring back the other way must undo the turn exactly.
|
||||||
|
back := orientation_quarter_turn(Z, X, Y);
|
||||||
|
check(*s, "the reverse turn cancels the forward one", compose_orientations(q, back) == 0);
|
||||||
|
|
||||||
|
all_valid := true;
|
||||||
|
for pair: Vector3.[X, Y, Z] {
|
||||||
|
u := pair;
|
||||||
|
v := ifx v3_eq(u, X) then Y else ifx v3_eq(u, Y) then Z else X;
|
||||||
|
n := cross(u, v);
|
||||||
|
t := orientation_quarter_turn(u, v, n);
|
||||||
|
if !v3_eq(rotate_by_orientation(t, u), v) all_valid = false;
|
||||||
|
}
|
||||||
|
check(*s, "quarter turns work on every axis pair", all_valid);
|
||||||
|
end_suite(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#run {
|
||||||
|
test_orientation_set();
|
||||||
|
test_orientation_compose();
|
||||||
|
test_orientation_quarter_turn();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
#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";
|
||||||
@ -36,8 +38,11 @@ _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);
|
||||||
File_Util.visit_files("./resources", true, *packs, _hotreload_visitor);
|
// Same mirror-and-process pipeline the build uses, so a hot reload picks up
|
||||||
File_Util.visit_files(GAME_RESOURCES_DIR, true, *packs, _hotreload_visitor);
|
// edited .aseprite files too, not just plain assets.
|
||||||
|
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);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ 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;
|
||||||
@ -51,6 +52,15 @@ 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;
|
||||||
|
|||||||
@ -22,6 +22,9 @@ 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);
|
||||||
@ -157,6 +160,14 @@ 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);
|
||||||
|
|||||||
@ -1,8 +1,26 @@
|
|||||||
DEBUG_LINE_MAX :: 65536;
|
DEBUG_LINE_MAX :: 65536;
|
||||||
|
DEBUG_OVERLAY_LINE_MAX :: 8192;
|
||||||
|
|
||||||
g_debug_line_verts : [DEBUG_LINE_MAX * 2 * 7]float;
|
// One record per line, uploaded as instance data: the line pipeline expands each
|
||||||
|
// 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;
|
||||||
@ -24,27 +42,35 @@ 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) {
|
debug_line :: (a: Vector3, b: Vector3, col: Vector4, width: float = DEBUG_LINE_DEFAULT_WIDTH) {
|
||||||
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;
|
||||||
base := g_debug_line_count * 14;
|
g_debug_lines[g_debug_line_count] = .{a, b, col, width};
|
||||||
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;
|
||||||
@ -126,21 +152,34 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_draw_flush_gpu :: () {
|
flush_debug_lines :: (pipe: *Pipeline_Binding, lines: []Debug_Line, count: *int, params: *Debugline_Vs_Params) {
|
||||||
if g_debug_line_count == 0 then return;
|
if count.* == 0 then return;
|
||||||
|
// Buffer 0 is the shared static quad; buffer 1 is this pipeline's instances.
|
||||||
sg_update_buffer(
|
sg_update_buffer(
|
||||||
gPipelines.debugline.bind.vertex_buffers[0],
|
pipe.bind.vertex_buffers[1],
|
||||||
*(sg_range.{
|
*(sg_range.{
|
||||||
ptr = g_debug_line_verts.data,
|
ptr = lines.data,
|
||||||
size = cast(u64)(g_debug_line_count * 14 * size_of(float)),
|
size = cast(u64)(count.* * size_of(Debug_Line)),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
mvp := create_viewproj(*camera);
|
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);
|
||||||
params : Debugline_Vs_Params;
|
params : Debugline_Vs_Params;
|
||||||
params.mvp = mvp.floats;
|
params.mvp = mvp.floats;
|
||||||
sg_apply_pipeline(gPipelines.debugline.pipeline);
|
params.viewport = .[cast(float)w, cast(float)h, 0, 0];
|
||||||
sg_apply_bindings(*gPipelines.debugline.bind);
|
|
||||||
sg_apply_uniforms(UB_debugline_vs_params, *(sg_range.{ ptr = *params, size = size_of(Debugline_Vs_Params) }));
|
flush_debug_lines(*gPipelines.debugline, g_debug_lines, *g_debug_line_count, *params);
|
||||||
sg_draw(0, xx (g_debug_line_count * 2), 1);
|
flush_debug_lines(*gPipelines.debugline_overlay, g_overlay_lines, *g_overlay_line_count, *params);
|
||||||
g_debug_line_count = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#assert size_of(Debug_Line) == 44 "Debug_Line must stay tightly packed: the pipeline layout hardcodes its field offsets.";
|
||||||
|
|||||||
@ -113,6 +113,8 @@ 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 :: () {
|
||||||
@ -1694,20 +1696,45 @@ create_particle_pipeline :: () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
create_debugline_pipeline :: () {
|
create_debugline_pipeline :: () {
|
||||||
buf_desc := sg_buffer_desc.{
|
// The two corner coordinates of a quad: x picks the side of the line, y picks
|
||||||
size = DEBUG_LINE_MAX * 2 * 7 * size_of(float),
|
// the end. The vertex shader turns those into a screen-space widened line.
|
||||||
usage = .DYNAMIC,
|
corners := float.[
|
||||||
label = "debug_line_verts",
|
-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",
|
||||||
};
|
};
|
||||||
gPipelines.debugline.bind.vertex_buffers[0] = sg_make_buffer(*buf_desc);
|
quad_buffer := sg_make_buffer(*quad_desc);
|
||||||
|
|
||||||
|
buf_desc := sg_buffer_desc.{
|
||||||
|
size = DEBUG_LINE_MAX * size_of(Debug_Line),
|
||||||
|
usage = .DYNAMIC,
|
||||||
|
label = "debug_line_instances",
|
||||||
|
};
|
||||||
|
gPipelines.debugline.bind.vertex_buffers[0] = quad_buffer;
|
||||||
|
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.primitive_type = .LINES;
|
pipeline.layout.buffers[0].stride = 2 * size_of(float);
|
||||||
pipeline.layout.buffers[0].stride = 28;
|
pipeline.layout.buffers[1].stride = size_of(Debug_Line);
|
||||||
pipeline.layout.attrs[ATTR_debugline_a_pos] = .{ format = .FLOAT3, buffer_index = 0, offset = 0 };
|
pipeline.layout.buffers[1].step_func = .PER_INSTANCE;
|
||||||
pipeline.layout.attrs[ATTR_debugline_a_col] = .{ format = .FLOAT4, buffer_index = 0, offset = 12 };
|
pipeline.layout.attrs[ATTR_debugline_a_corner] = .{ format = .FLOAT2, buffer_index = 0, offset = 0 };
|
||||||
|
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,
|
||||||
@ -1720,4 +1747,13 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ 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;
|
||||||
@ -65,6 +66,19 @@ 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;
|
||||||
@ -177,6 +191,22 @@ 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);
|
||||||
|
|||||||
162
src/resource_mirror.jai
Normal file
162
src/resource_mirror.jai
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Packs are built from a throwaway copy of the resource trees, not from the
|
||||||
|
// trees themselves:
|
||||||
|
//
|
||||||
|
// wipe .build/generated -> copy the trees in -> process the copy -> pack it
|
||||||
|
//
|
||||||
|
// Processing (currently just the Aseprite export) writes its output into the
|
||||||
|
// copy, so the real resource trees only ever hold sources. Rebuilding the copy
|
||||||
|
// from scratch every time is what keeps this simple: there is no stale output to
|
||||||
|
// detect and nothing to prune, because a deleted source is simply not there.
|
||||||
|
//
|
||||||
|
// Used by both the build (meta/pack.jai) and the runtime hot-reloader
|
||||||
|
// (pack_hotreload.jai), so they process resources identically.
|
||||||
|
|
||||||
|
#import "Basic";
|
||||||
|
#import "String";
|
||||||
|
#import "Process";
|
||||||
|
#import "File";
|
||||||
|
#import "File_Utilities";
|
||||||
|
|
||||||
|
// Not ".build/generated/resources": pack.jai derives the pack name by splitting
|
||||||
|
// on the *first* "/resources/", so a "resources" segment here would shadow the
|
||||||
|
// real one and yield pack "game" instead of "game_core".
|
||||||
|
MIRROR_ROOT :: ".build/generated";
|
||||||
|
|
||||||
|
ASEPRITE_SOURCE_EXTENSIONS :: string.["aseprite", "ase"];
|
||||||
|
|
||||||
|
is_aseprite_source :: (short_name: string) -> bool {
|
||||||
|
ok, base, extension := split_from_right(short_name, #char ".");
|
||||||
|
if !ok return false;
|
||||||
|
for ASEPRITE_SOURCE_EXTENSIONS if extension == it return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipes and rebuilds the mirror, runs the processing steps over it, and returns
|
||||||
|
// the mirrored resource roots to pack in place of the real ones.
|
||||||
|
prepare_resource_mirror :: (use_test_game: bool) -> [] string {
|
||||||
|
run_command("rm", "-rf", MIRROR_ROOT);
|
||||||
|
|
||||||
|
game_tree := ifx use_test_game then "test_game" else "game";
|
||||||
|
|
||||||
|
roots: [..] string;
|
||||||
|
roots.allocator = temp;
|
||||||
|
array_add(*roots, copy_tree_into("./resources", MIRROR_ROOT));
|
||||||
|
array_add(*roots, copy_tree_into(tprint("./%/resources", game_tree), tprint("%/%", MIRROR_ROOT, game_tree)));
|
||||||
|
|
||||||
|
export_aseprite_sheets(roots);
|
||||||
|
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "cp -r ./game/resources .build/generated/game" -> ".build/generated/game/resources"
|
||||||
|
copy_tree_into :: (source: string, destination_parent: string) -> string {
|
||||||
|
make_directory_if_it_does_not_exist(destination_parent, recursive = true);
|
||||||
|
run_command("cp", "-r", source, destination_parent);
|
||||||
|
|
||||||
|
ok, parent, name := split_from_right(source, #char "/");
|
||||||
|
return tprint("%/%", destination_parent, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aseprite's CLI, reproducing byte for byte what its "Export Sprite Sheet"
|
||||||
|
// dialog was producing for the sheets that used to be checked in.
|
||||||
|
// --format json-array and --list-tags are load-bearing: asset_manager.jai parses
|
||||||
|
// `frames` as an array and drives Animation off `meta.frameTags`.
|
||||||
|
ASEPRITE_EXPORT_FLAGS :: string.[
|
||||||
|
"--format", "json-array",
|
||||||
|
"--list-tags",
|
||||||
|
"--list-layers",
|
||||||
|
"--list-slices",
|
||||||
|
];
|
||||||
|
|
||||||
|
// "player.aseprite" -> player.sheet.png / player.sheet.json
|
||||||
|
// "anim.sheet.aseprite" -> anim.sheet.png / anim.sheet.json
|
||||||
|
//
|
||||||
|
// The doubled-up ".sheet" is dropped because asset_manager.jai keys sheets on
|
||||||
|
// everything before the *first* dot, so "anim.sheet.sheet.png" would load as a
|
||||||
|
// sheet named "anim" with extension "sheet.sheet.png" and be rejected.
|
||||||
|
sheet_output_paths :: (source: string) -> (png: string, json: string) {
|
||||||
|
ok, stem := split_from_right(source, #char ".");
|
||||||
|
if ends_with(stem, ".sheet") {
|
||||||
|
stem.count -= ".sheet".count;
|
||||||
|
}
|
||||||
|
return tprint("%.sheet.png", stem), tprint("%.sheet.json", stem);
|
||||||
|
}
|
||||||
|
|
||||||
|
find_aseprite_binary :: () -> (path: string, found: bool) {
|
||||||
|
candidates: [..] string;
|
||||||
|
candidates.allocator = temp;
|
||||||
|
|
||||||
|
#if OS == .LINUX || OS == .MACOS {
|
||||||
|
POSIX :: #import "POSIX";
|
||||||
|
read_env :: (name: string) -> string {
|
||||||
|
value := POSIX.getenv(name.data);
|
||||||
|
if !value return "";
|
||||||
|
return to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASEPRITE points the build at a different install without editing this.
|
||||||
|
override := read_env("ASEPRITE");
|
||||||
|
if override array_add(*candidates, override);
|
||||||
|
|
||||||
|
home := read_env("HOME");
|
||||||
|
if home array_add(*candidates, tprint("%/bin/aseprite", home));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OS == .MACOS {
|
||||||
|
array_add(*candidates, "/Applications/Aseprite.app/Contents/MacOS/aseprite");
|
||||||
|
}
|
||||||
|
|
||||||
|
array_add(*candidates, "aseprite"); // whatever is on PATH
|
||||||
|
|
||||||
|
for candidates {
|
||||||
|
// Probing by running it also catches a path that exists but isn't executable.
|
||||||
|
result := run_command(it, "--version", capture_and_return_output = true);
|
||||||
|
if result.type == .EXITED && result.exit_code == 0 return it, true;
|
||||||
|
}
|
||||||
|
return "", false;
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_aseprite_sources :: (root: string, sources: *[..] string) {
|
||||||
|
if !is_directory(root) return;
|
||||||
|
|
||||||
|
visit_files(root, true, sources, (info: *File_Visit_Info, sources: *[..] string) {
|
||||||
|
if info.is_directory return;
|
||||||
|
if !is_aseprite_source(info.short_name) return;
|
||||||
|
array_add(sources, copy_string(info.full_name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exports every Aseprite file in the mirror to a sheet pair beside it. pack.jai
|
||||||
|
// skips the sources themselves, so only the exports end up in the pack.
|
||||||
|
export_aseprite_sheets :: (roots: [] string) {
|
||||||
|
sources: [..] string;
|
||||||
|
sources.allocator = temp;
|
||||||
|
for roots collect_aseprite_sources(it, *sources);
|
||||||
|
if !sources return;
|
||||||
|
|
||||||
|
aseprite, found := find_aseprite_binary();
|
||||||
|
if !found {
|
||||||
|
log_error("Aseprite export: % source file(s) to export but no aseprite binary was found.", sources.count);
|
||||||
|
log_error("Looked for $ASEPRITE, $HOME/bin/aseprite, and 'aseprite' on PATH.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for source: sources {
|
||||||
|
png, json := sheet_output_paths(source);
|
||||||
|
|
||||||
|
args: [..] string;
|
||||||
|
args.allocator = temp;
|
||||||
|
array_add(*args, aseprite, "-b", source, "--sheet", png, "--data", json);
|
||||||
|
for flag: ASEPRITE_EXPORT_FLAGS array_add(*args, flag);
|
||||||
|
|
||||||
|
result, output, error := run_command(..args, capture_and_return_output = true);
|
||||||
|
if result.type != .EXITED || result.exit_code != 0 {
|
||||||
|
log_error("Aseprite export failed for %", source);
|
||||||
|
if output log_error(output,, logger = runtime_support_default_logger);
|
||||||
|
if error log_error(error,, logger = runtime_support_default_logger);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Aseprite: exported % sheet(s)\n", sources.count);
|
||||||
|
}
|
||||||
@ -13,30 +13,55 @@
|
|||||||
Vertex Shader: vs_debugline
|
Vertex Shader: vs_debugline
|
||||||
Fragment Shader: fs_debugline
|
Fragment Shader: fs_debugline
|
||||||
Attributes:
|
Attributes:
|
||||||
ATTR_debugline_a_pos => 0
|
ATTR_debugline_a_corner => 0
|
||||||
ATTR_debugline_a_col => 1
|
ATTR_debugline_a_pos_a => 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_pos :: 0;
|
ATTR_debugline_a_corner :: 0;
|
||||||
ATTR_debugline_a_col :: 1;
|
ATTR_debugline_a_pos_a :: 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[4];
|
uniform vec4 debugline_vs_params[5];
|
||||||
layout(location = 0) in vec3 a_pos;
|
layout(location = 1) in vec3 a_pos_a;
|
||||||
|
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 = 1) in vec4 a_col;
|
layout(location = 3) in vec4 a_col;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]) * vec4(a_pos, 1.0);
|
mat4 _18 = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,26 +69,74 @@ 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,0x34,
|
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,0x61,0x74,0x69,
|
0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,
|
||||||
0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,
|
0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,
|
||||||
0x61,0x5f,0x70,0x6f,0x73,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,
|
0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,
|
||||||
0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,
|
0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,
|
||||||
0x76,0x65,0x63,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x6c,0x61,0x79,0x6f,
|
0x20,0x76,0x65,0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x62,0x3b,0x0a,0x6c,
|
||||||
0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,
|
0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,
|
||||||
0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,
|
0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x61,0x5f,0x63,0x6f,
|
||||||
0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,
|
0x72,0x6e,0x65,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,
|
||||||
|
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,0x6d,0x61,0x74,0x34,0x28,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,
|
0x20,0x5f,0x31,0x31,0x35,0x20,0x2b,0x20,0x76,0x65,0x63,0x34,0x28,0x28,0x28,0x28,
|
||||||
0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x64,0x65,
|
0x76,0x65,0x63,0x32,0x28,0x2d,0x5f,0x38,0x36,0x2e,0x79,0x2c,0x20,0x5f,0x38,0x36,
|
||||||
0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,
|
0x2e,0x78,0x29,0x20,0x2a,0x20,0x61,0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x2e,0x78,
|
||||||
0x73,0x5b,0x31,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,
|
0x29,0x20,0x2a,0x20,0x28,0x28,0x61,0x5f,0x77,0x69,0x64,0x74,0x68,0x20,0x2a,0x20,
|
||||||
0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x64,0x65,
|
0x30,0x2e,0x35,0x29,0x20,0x2b,0x20,0x30,0x2e,0x35,0x29,0x29,0x20,0x2f,0x20,0x64,
|
||||||
0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,
|
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
||||||
0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x61,0x5f,0x70,
|
0x6d,0x73,0x5b,0x34,0x5d,0x2e,0x78,0x79,0x29,0x20,0x2a,0x20,0x5f,0x31,0x31,0x35,
|
||||||
0x6f,0x73,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x5f,
|
0x2e,0x77,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,0x0a,0x20,
|
||||||
0x63,0x6f,0x6c,0x20,0x3d,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x7d,0x0a,0x0a,
|
0x20,0x20,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x20,0x3d,0x20,0x61,0x5f,0x63,0x6f,0x6c,
|
||||||
0x00,
|
0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
||||||
];
|
];
|
||||||
/*
|
/*
|
||||||
#version 430
|
#version 430
|
||||||
@ -91,14 +164,32 @@ fs_debugline_source_glsl430 := u8.[
|
|||||||
/*
|
/*
|
||||||
#version 300 es
|
#version 300 es
|
||||||
|
|
||||||
uniform vec4 debugline_vs_params[4];
|
uniform vec4 debugline_vs_params[5];
|
||||||
layout(location = 0) in vec3 a_pos;
|
layout(location = 1) in vec3 a_pos_a;
|
||||||
|
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 = 1) in vec4 a_col;
|
layout(location = 3) in vec4 a_col;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
gl_Position = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]) * vec4(a_pos, 1.0);
|
mat4 _18 = mat4(debugline_vs_params[0], debugline_vs_params[1], debugline_vs_params[2], debugline_vs_params[3]);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,23 +198,72 @@ 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,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,
|
0x73,0x5b,0x35,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,
|
||||||
0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,
|
0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,
|
||||||
0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,
|
0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x3b,0x0a,0x6c,0x61,0x79,0x6f,
|
||||||
0x63,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,
|
0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,
|
||||||
0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,
|
0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x62,
|
||||||
0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x0a,0x76,
|
0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,
|
||||||
0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,
|
0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x61,
|
||||||
0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,
|
0x5f,0x63,0x6f,0x72,0x6e,0x65,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,
|
||||||
0x61,0x74,0x34,0x28,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,
|
0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x34,0x29,0x20,0x69,0x6e,
|
||||||
0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,
|
0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x61,0x5f,0x77,0x69,0x64,0x74,0x68,0x3b,0x0a,
|
||||||
0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,
|
0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,
|
||||||
0x31,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,
|
0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,
|
||||||
0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x64,0x65,0x62,0x75,
|
0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x61,0x5f,0x63,
|
||||||
0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,
|
0x6f,0x6c,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,
|
||||||
0x33,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x61,0x5f,0x70,0x6f,0x73,
|
0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x74,0x34,0x20,0x5f,0x31,0x38,0x20,
|
||||||
0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x76,0x5f,0x63,0x6f,
|
0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,
|
||||||
0x6c,0x20,0x3d,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
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,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
|
||||||
@ -161,6 +301,7 @@ 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
|
||||||
@ -171,14 +312,31 @@ fs_debugline_source_glsl300es := u8.[
|
|||||||
|
|
||||||
struct main0_in
|
struct main0_in
|
||||||
{
|
{
|
||||||
float3 a_pos [[attribute(0)]];
|
float2 a_corner [[attribute(0)]];
|
||||||
float4 a_col [[attribute(1)]];
|
float3 a_pos_a [[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& _19 [[buffer(0)]])
|
vertex main0_out main0(main0_in in [[stage_in]], constant debugline_vs_params& _13 [[buffer(0)]])
|
||||||
{
|
{
|
||||||
main0_out out = {};
|
main0_out out = {};
|
||||||
out.gl_Position = _19.mvp * float4(in.a_pos, 1.0);
|
float4 _28 = _13.mvp * float4(in.a_pos_a, 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;
|
||||||
}
|
}
|
||||||
@ -192,33 +350,82 @@ 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,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,
|
0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,
|
||||||
0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,
|
0x34,0x20,0x76,0x69,0x65,0x77,0x70,0x6f,0x72,0x74,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,
|
||||||
0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x76,0x5f,0x63,0x6f,0x6c,0x20,0x5b,
|
0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,
|
||||||
0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,
|
0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x76,0x5f,
|
||||||
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x67,0x6c,0x5f,0x50,0x6f,
|
0x63,0x6f,0x6c,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,
|
||||||
0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,
|
0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,
|
||||||
0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,
|
0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f,
|
||||||
0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,
|
0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,
|
||||||
0x6c,0x6f,0x61,0x74,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x20,0x5b,0x5b,0x61,0x74,
|
0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,
|
||||||
0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,
|
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x61,0x5f,0x63,0x6f,0x72,
|
||||||
0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,0x20,0x5b,
|
0x6e,0x65,0x72,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,
|
||||||
0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,
|
0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x33,
|
||||||
0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,
|
0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x61,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,
|
||||||
0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,
|
0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,
|
||||||
0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,
|
0x6c,0x6f,0x61,0x74,0x33,0x20,0x61,0x5f,0x70,0x6f,0x73,0x5f,0x62,0x20,0x5b,0x5b,
|
||||||
0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x64,
|
0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,
|
||||||
0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,
|
0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x61,0x5f,0x63,0x6f,0x6c,
|
||||||
0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,
|
0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x33,0x29,0x5d,
|
||||||
0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,
|
0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x61,0x5f,0x77,
|
||||||
0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,
|
0x69,0x64,0x74,0x68,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,
|
||||||
0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,
|
0x28,0x34,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76,0x65,0x72,0x74,0x65,
|
||||||
0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a,
|
0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,
|
||||||
0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x61,0x5f,0x70,0x6f,0x73,
|
0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,
|
||||||
0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,
|
0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,
|
||||||
0x76,0x5f,0x63,0x6f,0x6c,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x61,0x5f,0x63,0x6f,0x6c,
|
0x74,0x61,0x6e,0x74,0x20,0x64,0x65,0x62,0x75,0x67,0x6c,0x69,0x6e,0x65,0x5f,0x76,
|
||||||
0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,
|
0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x31,0x33,0x20,0x5b,0x5b,
|
||||||
0x3b,0x0a,0x7d,0x0a,0x0a,0x00,
|
0x62,0x75,0x66,0x66,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,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>
|
||||||
@ -276,14 +483,20 @@ 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_pos";
|
desc.attrs[0].glsl_name = "a_corner";
|
||||||
desc.attrs[1].base_type = .FLOAT;
|
desc.attrs[1].base_type = .FLOAT;
|
||||||
desc.attrs[1].glsl_name = "a_col";
|
desc.attrs[1].glsl_name = "a_pos_a";
|
||||||
|
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 = 64;
|
desc.uniform_blocks[0].size = 80;
|
||||||
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 = 4;
|
desc.uniform_blocks[0].glsl_uniforms[0].array_count = 5;
|
||||||
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;
|
||||||
@ -291,14 +504,20 @@ 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_pos";
|
desc.attrs[0].glsl_name = "a_corner";
|
||||||
desc.attrs[1].base_type = .FLOAT;
|
desc.attrs[1].base_type = .FLOAT;
|
||||||
desc.attrs[1].glsl_name = "a_col";
|
desc.attrs[1].glsl_name = "a_pos_a";
|
||||||
|
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 = 64;
|
desc.uniform_blocks[0].size = 80;
|
||||||
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 = 4;
|
desc.uniform_blocks[0].glsl_uniforms[0].array_count = 5;
|
||||||
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;
|
||||||
@ -307,9 +526,12 @@ 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 = 64;
|
desc.uniform_blocks[0].size = 80;
|
||||||
desc.uniform_blocks[0].msl_buffer_n = 0;
|
desc.uniform_blocks[0].msl_buffer_n = 0;
|
||||||
}
|
}
|
||||||
return desc;
|
return desc;
|
||||||
|
|||||||
@ -1,16 +1,44 @@
|
|||||||
@vs vs_debugline
|
@vs vs_debugline
|
||||||
|
|
||||||
in vec3 a_pos;
|
// Lines are drawn as camera-facing quads rather than GL lines, so they can have
|
||||||
|
// 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() {
|
||||||
gl_Position = mvp * vec4(a_pos, 1.0);
|
vec4 clip_a = mvp * vec4(a_pos_a, 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,39 @@
|
|||||||
|
// A single test gets this long to run its commands before it is declared hung
|
||||||
|
// and the run moves on. This only fires while frames are still being produced;
|
||||||
|
// a genuine freeze has to be caught by a timeout around the process itself,
|
||||||
|
// which is what run_tests.sh does.
|
||||||
|
EXE_TEST_TIMEOUT_SECONDS :: 30.0;
|
||||||
|
|
||||||
Exe_Runner :: struct {
|
Exe_Runner :: struct {
|
||||||
suite_idx : int;
|
suite_idx : int;
|
||||||
test_idx : int;
|
test_idx : int;
|
||||||
cmd_idx : int;
|
cmd_idx : int;
|
||||||
wait_until : float64;
|
wait_until : float64;
|
||||||
test_failed : bool;
|
test_failed : bool;
|
||||||
test_started : bool;
|
test_started : bool;
|
||||||
done : bool;
|
done : bool;
|
||||||
|
passed : int;
|
||||||
|
failed : int;
|
||||||
|
test_deadline : float64;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_exe_runner : Exe_Runner;
|
g_exe_runner : Exe_Runner;
|
||||||
|
|
||||||
|
// Ends the process with a status that reflects the run, so a failing exe test
|
||||||
|
// actually fails the build instead of quitting quietly with 0.
|
||||||
|
finish_exe_tests :: (r: *Exe_Runner) {
|
||||||
|
total := r.passed + r.failed;
|
||||||
|
if r.failed == 0 {
|
||||||
|
print("[exe tests] All % test(s) passed.\n", total);
|
||||||
|
} else {
|
||||||
|
print("[exe tests] %/% passed, % FAILED.\n", r.passed, total, r.failed);
|
||||||
|
}
|
||||||
|
r.done = true;
|
||||||
|
sapp_request_quit();
|
||||||
|
status : s32 = ifx r.failed > 0 then cast(s32) 1 else cast(s32) 0;
|
||||||
|
exit(status);
|
||||||
|
}
|
||||||
|
|
||||||
run_exe_tests :: () {
|
run_exe_tests :: () {
|
||||||
r := *g_exe_runner;
|
r := *g_exe_runner;
|
||||||
if r.done then return;
|
if r.done then return;
|
||||||
@ -20,8 +44,7 @@ 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");
|
||||||
r.done = true;
|
finish_exe_tests(r);
|
||||||
sapp_request_quit();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,15 +63,30 @@ 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;
|
||||||
|
|||||||
12
src/tests/framework.jai
Normal file
12
src/tests/framework.jai
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Test framework only — no test cases. This is loaded under every test flag so
|
||||||
|
// that the game side can write suites and exe tests exactly like the engine does.
|
||||||
|
//
|
||||||
|
// Unit tests need nothing beyond this: put them in a #if FLAG_TEST_GAME block
|
||||||
|
// next to the code they cover, with a local #run to call them, and a failing
|
||||||
|
// check turns into a compile error.
|
||||||
|
//
|
||||||
|
// Exe tests are registered from game_exe_tests_add(), which the engine calls
|
||||||
|
// during init when built with test_exe_game.
|
||||||
|
|
||||||
|
#load "utils.jai";
|
||||||
|
#load "exe_tests/index.jai";
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#load "utils.jai";
|
// Engine test cases. The framework itself lives in framework.jai, which is
|
||||||
|
// loaded separately so the game side gets it too.
|
||||||
|
|
||||||
#load "world_test.jai";
|
#load "world_test.jai";
|
||||||
#load "../editor/rdm_disk_test.jai";
|
#load "../editor/rdm_disk_test.jai";
|
||||||
|
|
||||||
#load "engine_exe_tests/index.jai";
|
#load "engine_exe_tests/index.jai";
|
||||||
#load "exe_tests/index.jai";
|
|
||||||
|
|||||||
@ -268,11 +268,106 @@ test_legacy_load_cursor_fix :: () {
|
|||||||
end_suite(s);
|
end_suite(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
#run {
|
// Entity types come from the game, so these run against whatever the game
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,18 +59,59 @@ note_to_autoedit_conf :: (notes: []string) -> Autoedit_Conf {
|
|||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
input_code_from_type_and_notes :: (name: string, type: *Type_Info, notes: []string) -> string {
|
// Types we can generate an editor widget for. Anything else (nested structs,
|
||||||
|
// 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(float) || type == type_info(s32) {
|
if type == type_info(bool) {
|
||||||
|
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");
|
||||||
@ -83,9 +124,9 @@ input_code_from_type_and_notes :: (name: string, type: *Type_Info, notes: []stri
|
|||||||
print_to_builder(*builder, "r.w = orig_w; r.x = orig_x;\n");
|
print_to_builder(*builder, "r.w = orig_w; r.x = orig_x;\n");
|
||||||
print_to_builder(*builder, "}\n");
|
print_to_builder(*builder, "}\n");
|
||||||
} else if autoconf.kind == .COLOR {
|
} else if autoconf.kind == .COLOR {
|
||||||
print_to_builder(*builder, "if GR.button(r, \"Edit color\", *t_button_color(theme, .{value.%.x, value.%.y, value.%.z, 1.0})) then cur_edit_color = *value.%;\n", name, name, name, name);
|
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, "r.y += r.h;\n");
|
print_to_builder(*builder, "r.y += r.h;\n");
|
||||||
return builder_to_string(*builder);
|
return builder_to_string(*builder);
|
||||||
@ -106,7 +147,10 @@ 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 {
|
||||||
print_to_builder(*builder, "%\n", input_code_from_type_and_notes(it.name, it.type, it.notes));
|
if it.flags & .CONSTANT continue;
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,8 @@ 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)
|
||||||
}
|
}
|
||||||
@ -207,6 +209,33 @@ 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);
|
||||||
}
|
}
|
||||||
@ -222,6 +251,10 @@ unload_current_world :: () {
|
|||||||
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;
|
||||||
@ -353,6 +386,11 @@ 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 :: () {
|
||||||
@ -384,6 +422,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,6 +463,17 @@ 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;
|
||||||
@ -619,7 +669,7 @@ save_world :: (world: *World) -> (json: string, chunks_bin: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wj: World_Json;
|
wj: World_Json;
|
||||||
wj.version = 4;
|
wj.version = 5;
|
||||||
wj.name = world.name;
|
wj.name = world.name;
|
||||||
wj.config = world_config_to_json(*world.conf);
|
wj.config = world_config_to_json(*world.conf);
|
||||||
|
|
||||||
@ -648,6 +698,16 @@ 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});
|
||||||
@ -735,6 +795,16 @@ load_world_from_json :: (json_str: string, chunk_bin: []u8) -> (World, bool) {
|
|||||||
array_add(*world.notes, note);
|
array_add(*world.notes, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for je: wj.entities {
|
||||||
|
e := spawn_entity(*world, je.type, .{je.position[0], je.position[1], je.position[2]}, je.orientation);
|
||||||
|
if e == null then continue; // unknown type; spawn_entity logged it
|
||||||
|
e.id = je.id;
|
||||||
|
if je.id >= world.next_entity_id then world.next_entity_id = je.id + 1;
|
||||||
|
for f: je.fields {
|
||||||
|
entity_apply_field(e, f.name, f.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for jov: wj.rdm_overrides {
|
for jov: wj.rdm_overrides {
|
||||||
size := ifx jov.rdm_size > 0 then jov.rdm_size else RDM_DEFAULT_SIZE;
|
size := ifx jov.rdm_size > 0 then jov.rdm_size else RDM_DEFAULT_SIZE;
|
||||||
array_add(*world.rdm_overrides, .{x=jov.x, y=jov.y, z=jov.z, rdm_enabled=jov.rdm_enabled, rdm_size=size});
|
array_add(*world.rdm_overrides, .{x=jov.x, y=jov.y, z=jov.z, rdm_enabled=jov.rdm_enabled, rdm_size=size});
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
|
// @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 :: () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,702 +0,0 @@
|
|||||||
{ "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.
|
Before Width: | Height: | Size: 16 KiB |
@ -1,267 +0,0 @@
|
|||||||
{ "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.
|
Before Width: | Height: | Size: 6.4 KiB |
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 4,
|
"version": 5,
|
||||||
"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": 2,
|
"sunIntensity": 1,
|
||||||
"skyIntensity": 1,
|
"skyIntensity": 0.3,
|
||||||
"hasClouds": 1,
|
"hasClouds": 1,
|
||||||
"planeHeight": 0,
|
"planeHeight": 0,
|
||||||
"animatePlaneHeight": 1,
|
"animatePlaneHeight": 1,
|
||||||
@ -33,12 +33,17 @@
|
|||||||
],
|
],
|
||||||
"deepColor": [
|
"deepColor": [
|
||||||
1,1,1
|
1,1,1
|
||||||
]
|
],
|
||||||
|
"waterShininess": 64
|
||||||
},
|
},
|
||||||
"chunks": [
|
"chunks": [
|
||||||
],
|
],
|
||||||
"emitters": [
|
"emitters": [
|
||||||
],
|
],
|
||||||
"notes": [
|
"notes": [
|
||||||
|
],
|
||||||
|
"entities": [
|
||||||
|
],
|
||||||
|
"rdm_overrides": [
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user