Move Odin renderer into gui/ with per-project Makefiles
Each sub-project (gui, api, infra) now owns its own Makefile, and the root Makefile is a lean delegator for the base commands (run, build, test, clean, fmt). Odin source, deps, and web assets live under gui/ (gui/src, gui/lib, gui/www), with scripts kept under gui/scripts for build tooling only.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# gui/Makefile — the Odin + raylib renderer (the interactive 3D universe map).
|
||||
#
|
||||
# This is the Odin sub-project. It is normally invoked from the repo root via
|
||||
# `make run`, `make build`, etc. (which delegate here through the root
|
||||
# Makefile), but it can also be driven directly with `make -C gui <target>`.
|
||||
#
|
||||
# `lib/` (third-party Odin deps) lives under `gui/lib`; the `bin/` / `build/`
|
||||
# output dirs live at the repo root and are referenced through `$(ROOT)`.
|
||||
|
||||
ODIN ?= odin
|
||||
ROOT := ..
|
||||
BIN := $(ROOT)/bin
|
||||
BINARY := $(BIN)/desi_explorer
|
||||
ODIN_FLAGS := -collection:lib=lib/local
|
||||
WASM_DEFINE := RAYLIB_WASM_LIB=env.o
|
||||
|
||||
.PHONY: help run build build-debug build-web test clean fmt
|
||||
|
||||
help: ## List available targets
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
run: ## Run the native app
|
||||
@mkdir -p lib/local
|
||||
$(ODIN) run src $(ODIN_FLAGS)
|
||||
|
||||
build: ## Release build -> bin/desi_explorer
|
||||
@mkdir -p lib/local $(BIN)
|
||||
$(ODIN) build src $(ODIN_FLAGS) -o:speed -out:$(BINARY)
|
||||
|
||||
build-debug: ## Debug build -> bin/desi_explorer
|
||||
@mkdir -p lib/local $(BIN)
|
||||
$(ODIN) build src $(ODIN_FLAGS) -o:none -debug -out:$(BINARY)
|
||||
|
||||
build-web: ## WebAssembly build -> build/web (needs emscripten)
|
||||
@scripts/build_web.sh
|
||||
|
||||
test: ## Run Odin unit tests
|
||||
@mkdir -p lib/local
|
||||
$(ODIN) test src $(ODIN_FLAGS)
|
||||
|
||||
clean: ## Remove build artifacts
|
||||
rm -rf $(BIN) build
|
||||
|
||||
fmt: ## Format Odin sources with odinfmt
|
||||
odinfmt src
|
||||
@@ -0,0 +1,11 @@
|
||||
# Libraries
|
||||
|
||||
Third-party libraries for the DESI Explorer renderer live here, either as git
|
||||
submodules or vendored directly and wired in via `-collection:lib=lib/local`
|
||||
(as resolved from the `gui/` directory).
|
||||
|
||||
- `raylib` is bundled with Odin and imported as `vendor:raylib` — it does **not**
|
||||
live in this directory.
|
||||
|
||||
No external dependencies yet. `local/` is the default `-collection:lib` target
|
||||
and is currently empty.
|
||||
@@ -0,0 +1 @@
|
||||
# Intentionally empty; third-party Odin deps are wired in via -collection:lib=lib/local.
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"character_width": 80,
|
||||
"tabs": true,
|
||||
"tabs_width": 2
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"collections": [
|
||||
{ "name": "app", "path": "./src" },
|
||||
{ "name": "lib", "path": "./lib" }
|
||||
],
|
||||
"enable_semantic_tokens": false,
|
||||
"enable_document_symbols": true,
|
||||
"enable_hover": true,
|
||||
"enable_snippets": true,
|
||||
"profiles": [
|
||||
{
|
||||
"checker_path": "src/main.odin"
|
||||
}
|
||||
]
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the WebAssembly ("web") build of DESI Explorer with Odin
|
||||
# (js_wasm32) + emscripten. Output: build/web/index.html (+ index.wasm,
|
||||
# odin.js) at the repo root. Run via `make -C gui build-web`.
|
||||
#
|
||||
# Requires:
|
||||
# - `odin` on PATH with the js_wasm32 target
|
||||
# - emcc / emscripten active in PATH
|
||||
# Optional:
|
||||
# - ODIN_ROOT (defaults to `odin root`)
|
||||
# - OUT_DIR (defaults to <repo>/build/web)
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve the gui/ project root so this script works regardless of CWD.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
GUI_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
ROOT_DIR="$(cd "${GUI_DIR}/.." && pwd)"
|
||||
|
||||
OUT_DIR="${OUT_DIR:-${ROOT_DIR}/build/web}"
|
||||
ODIN_ROOT="${ODIN_ROOT:-$(odin root)}"
|
||||
|
||||
mkdir -p "${GUI_DIR}/lib/local"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# The Odin compiler emits a wasm object; raylib's C code is linked later by
|
||||
# emcc. `-define:RAYLIB_WASM_LIB=env.o` stops the compiler from trying to
|
||||
# link the raylib lib itself (emcc does that; it ends up in the final module).
|
||||
odin build "${GUI_DIR}/src" \
|
||||
-target:js_wasm32 \
|
||||
-build-mode:obj \
|
||||
-define:RAYLIB_WASM_LIB=env.o \
|
||||
-o:speed \
|
||||
-collection:lib="${GUI_DIR}/lib/local" \
|
||||
-out:"${OUT_DIR}/game"
|
||||
|
||||
# Odin's JS runtime glue; the generated module imports functions from it.
|
||||
cp "${ODIN_ROOT}/core/sys/wasm/js/odin.js" "${OUT_DIR}/odin.js"
|
||||
|
||||
# Link with emscripten against raylib's prebuilt web library. Flags mirror the
|
||||
# known-good karl-zylinski/odin-raylib-web template.
|
||||
emcc \
|
||||
-o "${OUT_DIR}/index.html" \
|
||||
"${OUT_DIR}/game.obj" \
|
||||
"${ODIN_ROOT}/vendor/raylib/wasm/libraylib.web.a" \
|
||||
--shell-file "${GUI_DIR}/www/index_template.html" \
|
||||
-sEXPORTED_RUNTIME_METHODS="['HEAPF32']" \
|
||||
-sUSE_GLFW=3 \
|
||||
-sWASM_BIGINT \
|
||||
-sWARN_ON_UNDEFINED_SYMBOLS=0 \
|
||||
-sASSERTIONS
|
||||
|
||||
rm -f "${OUT_DIR}/game.obj"
|
||||
|
||||
echo "Web build created in ${OUT_DIR}"
|
||||
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import "core:math"
|
||||
import "core:math/rand"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
WIDTH :: 1280
|
||||
HEIGHT :: 720
|
||||
|
||||
// Number of procedurally generated points standing in for the DESI catalog.
|
||||
// This is replaced by real survey data once ingestion lands.
|
||||
POINT_COUNT :: 4_000
|
||||
WORLD_RADIUS :: f32(500.0)
|
||||
|
||||
Galaxy :: struct {
|
||||
position: rl.Vector3,
|
||||
color: rl.Color,
|
||||
}
|
||||
|
||||
camera: rl.Camera3D
|
||||
universe: [dynamic]Galaxy
|
||||
|
||||
// Deterministic generator so the placeholder sky is stable between runs.
|
||||
rng: rand.Default_Random_State
|
||||
|
||||
main :: proc() {
|
||||
rng = rand.create(0xDE51_0000)
|
||||
context.random_generator = rand.default_random_generator(&rng)
|
||||
|
||||
rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT, .MSAA_4X_HINT})
|
||||
rl.InitWindow(WIDTH, HEIGHT, "DESI Explorer")
|
||||
defer rl.CloseWindow()
|
||||
rl.SetTargetFPS(60)
|
||||
|
||||
camera = make_camera()
|
||||
make_universe(&universe, POINT_COUNT)
|
||||
defer delete(universe)
|
||||
|
||||
for !rl.WindowShouldClose() {
|
||||
update()
|
||||
draw()
|
||||
}
|
||||
}
|
||||
|
||||
make_camera :: proc() -> rl.Camera3D {
|
||||
return {
|
||||
position = {0, 220, 220},
|
||||
target = {0, 0, 0},
|
||||
up = {0, 1, 0},
|
||||
fovy = 60,
|
||||
projection = .PERSPECTIVE,
|
||||
}
|
||||
}
|
||||
|
||||
make_universe :: proc(u: ^[dynamic]Galaxy, count: int) {
|
||||
clear(u)
|
||||
reserve(u, count)
|
||||
for _ in 0 ..< count {
|
||||
pos := random_sphere_point(WORLD_RADIUS)
|
||||
append(u, Galaxy{position = pos, color = color_for_position(pos)})
|
||||
}
|
||||
}
|
||||
|
||||
// Uniformly distributed random point inside the scene's bounding sphere.
|
||||
random_sphere_point :: proc(radius: f32) -> rl.Vector3 {
|
||||
for {
|
||||
x := rand.float32_range(-1, 1)
|
||||
y := rand.float32_range(-1, 1)
|
||||
z := rand.float32_range(-1, 1)
|
||||
len_sq := x * x + y * y + z * z
|
||||
if len_sq > 0.0001 && len_sq < 1.0 {
|
||||
return {x * radius, y * radius, z * radius}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maps distance from the origin to a blue (near) -> red (far) gradient, a
|
||||
// stand-in for redshift until real colors are computed from survey metadata.
|
||||
color_for_position :: proc(p: rl.Vector3) -> rl.Color {
|
||||
d := math.sqrt(p.x * p.x + p.y * p.y + p.z * p.z)
|
||||
t := math.clamp(d / WORLD_RADIUS, 0, 1)
|
||||
return rl.Color{u8(40 + 180 * t), 120, u8(255 - 180 * t), 255}
|
||||
}
|
||||
|
||||
update :: proc() {
|
||||
// Orbital camera: drag to rotate, scroll to zoom, right-drag / shift to pan.
|
||||
rl.UpdateCamera(&camera, .ORBITAL)
|
||||
}
|
||||
|
||||
draw :: proc() {
|
||||
rl.BeginDrawing()
|
||||
defer rl.EndDrawing()
|
||||
|
||||
rl.ClearBackground({5, 5, 15, 255})
|
||||
|
||||
rl.BeginMode3D(camera)
|
||||
defer rl.EndMode3D()
|
||||
|
||||
rl.DrawGrid(20, 50.0)
|
||||
|
||||
for galaxy in universe {
|
||||
rl.DrawPoint3D(galaxy.position, galaxy.color)
|
||||
}
|
||||
|
||||
rl.DrawFPS(10, 10)
|
||||
rl.DrawText(
|
||||
"DESI Explorer — drag to rotate, scroll to zoom",
|
||||
10,
|
||||
34,
|
||||
18,
|
||||
rl.RAYWHITE,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>DESI Explorer</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #05050f;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" tabindex="-1" oncontextmenu="event.preventDefault()"></canvas>
|
||||
|
||||
<!-- Odin's JS runtime glue; required by the generated wasm module. -->
|
||||
<script src="odin.js"></script>
|
||||
|
||||
<script>
|
||||
var odinMemoryInterface = new odin.WasmMemoryInterface();
|
||||
odinMemoryInterface.setIntSize(4);
|
||||
var odinImports = odin.setupDefaultImports(odinMemoryInterface);
|
||||
|
||||
var Module = {
|
||||
instantiateWasm: function(imports, successCallback) {
|
||||
var newImports = Object.assign({}, odinImports, imports);
|
||||
return WebAssembly.instantiateStreaming(fetch("index.wasm"), newImports).then(
|
||||
function(output) {
|
||||
var e = output.instance.exports;
|
||||
odinMemoryInterface.setExports(e);
|
||||
odinMemoryInterface.setMemory(e.memory);
|
||||
return successCallback(output.instance);
|
||||
}
|
||||
);
|
||||
},
|
||||
onRuntimeInitialized: function() {
|
||||
var e = wasmExports;
|
||||
if (e._start) { e._start(); }
|
||||
},
|
||||
canvas: document.getElementById("canvas")
|
||||
};
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user