Files
aego/scene/space.go
T
sanspie 17cb5e4291 Stage 2-5: assets, scene/ECS, editor, 3D, and Aego Script
Stage 2: render pipeline and UI toolkit over gpu/platform.
Stage 3: schema registry, asset pipeline + runtime loader, scene graph and ECS.
Stage 4: editor — docking, inspector, scene/game views, play mode, asset browser, console, profiler; 4.5 adds the 3D foundation (mixed 2D/3D spaces, meshes, depth).
Stage 5: Aego Script — compiler (lexer/parser/types/resolver/checker/asm/compiler), bytecode with verifier and register VM, instance runtime, stdlib, native bindings, and editor/CLI integration (aego run/edit/check/test/dump).
2026-10-08 13:17:28 +03:00

200 lines
4.2 KiB
Go

package scene
import (
"aego/core/errs"
"aego/core/log"
"aego/core/mathx"
"aego/schema"
)
type Space uint8
const (
Space2D Space = iota
Space3D
)
func (sp Space) String() string {
if sp == Space3D {
return "3d"
}
return "2d"
}
func (sp Space) Kind() schema.SpaceKind {
if sp == Space3D {
return schema.Space3D
}
return schema.Space2D
}
func ParseSpace(s string) (Space, bool) {
switch s {
case "2d":
return Space2D, true
case "3d":
return Space3D, true
}
return Space2D, false
}
type Transform3D struct {
Position mathx.Vec3
Rotation mathx.Quat
Scale mathx.Vec3
}
func IdentityTransform3D() Transform3D {
return Transform3D{Rotation: mathx.QuatIdentity(), Scale: mathx.Vec3{X: 1, Y: 1, Z: 1}}
}
func (t Transform3D) Matrix() mathx.Mat4 {
rot := t.Rotation
if rot.IsZero() {
rot = mathx.QuatIdentity()
}
sc := t.Scale
if sc == (mathx.Vec3{}) {
sc = mathx.Vec3{X: 1, Y: 1, Z: 1}
}
return mathx.Mat4TRS(t.Position, rot, sc)
}
func Lift(t Transform2D) Transform3D {
sc := t.Scale
if sc.X == 0 && sc.Y == 0 {
sc = mathx.Vec2{X: 1, Y: 1}
}
return Transform3D{
Position: mathx.Vec3{X: t.Position.X, Y: -t.Position.Y},
Rotation: mathx.QuatAxisAngle(mathx.Vec3{Z: 1}, -t.Rotation),
Scale: mathx.Vec3{X: sc.X, Y: sc.Y, Z: 1},
}
}
func Flatten(t Transform3D) Transform2D {
_, _, z := t.Rotation.Euler()
return Transform2D{
Position: mathx.Vec2{X: t.Position.X, Y: -t.Position.Y},
Rotation: -z,
Scale: mathx.Vec2{X: t.Scale.X, Y: t.Scale.Y},
}
}
type Pose struct {
Space Space
T2 Transform2D
T3 Transform3D
}
type node3 struct {
owner NodeHandle
local Transform3D
world mathx.Mat4
}
func (s *Scene) alloc3(h NodeHandle, t Transform3D) int32 {
e := node3{owner: h, local: t, world: mathx.Mat4Identity()}
if n := len(s.free3); n > 0 {
slot := s.free3[n-1]
s.free3 = s.free3[:n-1]
s.three[slot] = e
return slot
}
s.three = append(s.three, e)
return int32(len(s.three) - 1)
}
func (s *Scene) release3(slot int32) {
s.three[slot] = node3{}
s.free3 = append(s.free3, slot)
}
func (s *Scene) DefaultSpace() Space {
return s.defaultSpace
}
func (s *Scene) SetDefaultSpace(sp Space) {
if s.defaultSpace != sp {
s.defaultSpace = sp
s.touch()
}
}
func (s *Scene) Space(h NodeHandle) Space {
n, ok := s.nodes.Get(h)
if !ok {
return Space2D
}
return n.space
}
func (s *Scene) Under3D(h NodeHandle) bool {
n, ok := s.nodes.Get(h)
if !ok || n.space == Space3D {
return false
}
if n.has(flagWorldDirty) {
s.UpdateTransforms()
n, _ = s.nodes.Get(h)
}
return n.has(flagUnder3D)
}
func (s *Scene) AllowsSpace(parent NodeHandle, sp Space) bool {
if sp == Space2D || parent.IsZero() || parent == s.root {
return true
}
p, ok := s.nodes.Get(parent)
return ok && p.space == Space3D
}
func (s *Scene) spaceError(node string, parent NodeHandle) error {
return errs.New(errs.SceneSpaceMismatch, "a 3D node needs a 3D parent or the scene root",
log.F("node", node), log.F("parent", s.Name(parent)))
}
func (s *Scene) Accepts(h NodeHandle, t schema.TypeID) bool {
td, ok := s.registry.ByID(t)
return ok && td.Space.Allows(s.Space(h).Kind())
}
func (s *Scene) SetSpace(h NodeHandle, sp Space) error {
n, ok := s.node(h)
if !ok || n.has(flagPendingDestroy) {
return errs.New(errs.SceneDeadNode, "node is not alive")
}
if h == s.root {
return errs.New(errs.SceneSpaceMismatch, "the scene root has no space of its own")
}
if n.space == sp {
return nil
}
if n.childCount > 0 {
return errs.New(errs.SceneSpaceMismatch, "a node with children cannot change its space", log.F("node", s.Name(h)))
}
if !s.AllowsSpace(n.parent, sp) {
return s.spaceError(s.Name(h), n.parent)
}
for _, st := range s.storeList {
if st.hasNode(h) && !st.descriptor().Space.Allows(sp.Kind()) {
return errs.New(errs.SceneSpaceMismatch, "a component on the node is tied to its current space",
log.F("node", s.Name(h)), log.F("component", st.descriptor().FullName))
}
}
if sp == Space3D {
n.slot = s.alloc3(h, Lift(n.local))
n.local = IdentityTransform()
} else {
n.local = Flatten(s.three[n.slot].local)
s.release3(n.slot)
n.slot = -1
}
n.space = sp
n.set(flagWorldDirty, false)
s.markWorldDirty(h)
s.touch()
s.emit(Event{Kind: EvSpaceChanged, Node: h, ID: n.id})
return nil
}