Documentation: engine/docs assembles one registry from the script
bindings, the schema, module docs and module-owned script paths; problems
are reported as warnings at build time. Module registries record doc
sources and script module owners; diag.Codes lists every diagnostic code.
Project bootstrap: project.defaultSpace ("2d"/"3d"), EnsureDirs creates
the default folders when the editor opens a project (reported in the
console), prefabs/ is a pipeline root. Native builds drop -trimpath so
binding locations resolve inside native/.
Editor files: create scene/prefab/script from menus, move/rename/duplicate
/delete with undo, script package retargeting on move, open scenes and
script panels follow renames, Save As / Rename Scene prompts, scene
picker, startup scene. Scoped shortcuts fall back to the global command
when disabled; Save As is Ctrl+Shift+S, Save All Ctrl+Alt+S.
Script editor: Go to Implementation (stub → native/ source), host
definitions always resolve to stubs, bracket match/mismatch states,
rainbow brackets (persisted), mono code font (ui-mono atlases, per-command
fonts in draw.List, PushFont/PopFont restored by Mark/Restore).
Tests: langsvc definition/implementation, bracket states, dispatch
fallback, script renames, font stack, editor file-op integration, golden
mono glyph and bracket references; depcheck rule for engine/docs.
Docs pipeline: engine, render2d and script/engine ship their own res/docs;
keyword hover text comes from the registry through manifest.Keywords;
project script symbols join the registry; aego docs export|check|build|
serve, --tool docs in native binaries, aego check --explain; app/docstool
and the tools/gendocs module (goldmark) build the static site.
Playground: app/playground + cmd/aego-playground (js/wasm, 3.3 MB gzip)
compile, highlight and run scripts in the browser; run blocks in the
language docs become examples. engine/builtin/headless is the module set
without render3d; workspace.Config.FS; StubFor/StubPaths; backends and the
script importer drop out of js builds.
VM: one frame pointer in the interpreter loop, direct host calls, panic
recovery once per run instead of per host call (Update2k -19%,
Schedule10k -24% against the previous head). bench-build/compare/profile
run pre-built, pinned, interleaved binaries.
Editor: structural bracket levels and current-block highlight, indent
guides, occurrence highlighting with write marks, multi-format clipboard
(copy/cut/paste assets, paste images as sprites or textures). gl33 probes
a renderable depth format and reports framebuffer failures by stage.
7 E:
- project templates empty/2d/2.5d/3d, app/projects as the single creation path (D148)
- launcher and recent projects (D149)
- orthographic Camera3D and Scene View (D150)
- reference states in the Inspector, ids.Short (D151)
- sceneio remaps node references when loading without KeepIDs (D152)
- exported ComponentRef<T> (D153)
- aego icons via module.Audit (D146)
108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
package view
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"aego/core/mathx"
|
|
"aego/gpu"
|
|
)
|
|
|
|
func perspectiveTestView() (View, mathx.Mat4) {
|
|
pos := mathx.Vec3{X: 1, Y: 2, Z: 5}
|
|
eye := mathx.Mat4TRS(pos, mathx.QuatLookAt(pos.Neg(), mathx.Vec3{Y: 1}), mathx.Vec3{X: 1, Y: 1, Z: 1})
|
|
v := PerspectiveView(eye, mathx.Deg2Rad(60), 0.1, 100, mathx.Rect{W: 800, H: 600})
|
|
v.Normalize(800, 600, 1)
|
|
return v, eye
|
|
}
|
|
|
|
func TestScreenToRayCenterIsForward(t *testing.T) {
|
|
v, eye := perspectiveTestView()
|
|
ray := v.ScreenToRay(mathx.Vec2{X: 400, Y: 300})
|
|
want := eye.TransformDir(mathx.Vec3{Z: -1}).Normalized()
|
|
if !ray.Dir.NearlyEqual(want, 1e-3) {
|
|
t.Fatalf("dir = %v, want %v", ray.Dir, want)
|
|
}
|
|
if ray.Origin.Sub(eye.Translation()).Len() > 0.2 {
|
|
t.Fatalf("ray must start at the near plane: %v", ray.Origin)
|
|
}
|
|
}
|
|
|
|
func TestWorldToScreen3RoundTrip(t *testing.T) {
|
|
v, _ := perspectiveTestView()
|
|
for _, p := range []mathx.Vec2{{X: 400, Y: 300}, {X: 10, Y: 20}, {X: 790, Y: 580}, {X: 120, Y: 450}} {
|
|
w := v.ScreenToRay(p).At(7)
|
|
back, ok := v.WorldToScreen3(w)
|
|
if !ok || !back.NearlyEqual(p, 0.05) {
|
|
t.Fatalf("%v -> %v (%v)", p, back, ok)
|
|
}
|
|
}
|
|
if _, ok := v.WorldToScreen3(mathx.Vec3{X: 2, Y: 4, Z: 10}); ok {
|
|
t.Fatal("a point behind the camera must not project")
|
|
}
|
|
}
|
|
|
|
func TestDepthAndWinding(t *testing.T) {
|
|
v, _ := perspectiveTestView()
|
|
caps := gpu.Capabilities{OriginBottomLeft: true}
|
|
if !v.UsesDepth() || v.Flipped(caps) {
|
|
t.Fatal("window perspective view must use the window depth and stay unflipped")
|
|
}
|
|
v.Target = gpu.TextureHandle{Index: 1, Gen: 1}
|
|
if v.UsesDepth() || !v.Flipped(caps) {
|
|
t.Fatal("offscreen view without a depth target must not use depth and must flip")
|
|
}
|
|
v.DepthTarget = gpu.TextureHandle{Index: 2, Gen: 1}
|
|
if !v.UsesDepth() {
|
|
t.Fatal("offscreen view with a depth target must use depth")
|
|
}
|
|
if ScreenView(64, 64, 1).UsesDepth() {
|
|
t.Fatal("2D views never use depth")
|
|
}
|
|
}
|
|
|
|
func TestFrustumFollowsEye(t *testing.T) {
|
|
v, _ := perspectiveTestView()
|
|
f := v.Frustum()
|
|
if !f.ContainsPoint(mathx.Vec3{}) {
|
|
t.Fatal("origin is in front of the camera")
|
|
}
|
|
if f.ContainsPoint(mathx.Vec3{X: 2, Y: 4, Z: 10}) {
|
|
t.Fatal("point behind the camera inside the frustum")
|
|
}
|
|
}
|
|
|
|
func TestOrthoViewMatrix4MatchesMat3(t *testing.T) {
|
|
v := testView()
|
|
p := mathx.Vec2{X: 3, Y: -4}
|
|
a := v.ViewMatrix().Apply(p)
|
|
b := v.ViewMatrix4().TransformPoint(mathx.Vec3{X: p.X, Y: p.Y})
|
|
if !b.NearlyEqual(mathx.Vec3{X: a.X, Y: a.Y}, 1e-4) {
|
|
t.Fatalf("ortho ViewMatrix4 diverges from ViewMatrix: %v vs %v", b, a)
|
|
}
|
|
}
|
|
|
|
func TestOrthoViewIgnoresDepth(t *testing.T) {
|
|
eye := mathx.Mat4Translate(mathx.Vec3{Z: 10})
|
|
v := OrthoView(eye, 5, 0.1, 100, mathx.Rect{W: 200, H: 100})
|
|
v.Normalize(200, 100, 1)
|
|
if !v.Is3D() || !v.UsesDepth() {
|
|
t.Fatal("ortho view must be a 3D view with depth")
|
|
}
|
|
a, okA := v.WorldToScreen3(mathx.Vec3{X: 2, Y: 1})
|
|
b, okB := v.WorldToScreen3(mathx.Vec3{X: 2, Y: 1, Z: -50})
|
|
if !okA || !okB {
|
|
t.Fatal("points in front of the camera must project")
|
|
}
|
|
if a.Sub(b).Len() > 1e-3 {
|
|
t.Fatalf("depth changed the pixel: %v vs %v", a, b)
|
|
}
|
|
want := mathx.Vec2{X: 120, Y: 40}
|
|
if a.Sub(want).Len() > 1e-3 {
|
|
t.Fatalf("pixel = %v, want %v", a, want)
|
|
}
|
|
ray := v.ScreenToRay(a)
|
|
if ray.Dir.Sub(mathx.Vec3{Z: -1}).Len() > 1e-4 {
|
|
t.Fatalf("ray dir = %v", ray.Dir)
|
|
}
|
|
}
|