Files

392 lines
9.1 KiB
Go

package templates
import (
"errors"
"fmt"
"sort"
"aego/asset"
"aego/core/errs"
"aego/core/ids"
"aego/core/log"
"aego/core/mathx"
"aego/editor/core/context"
"aego/editor/core/history/commands"
"aego/scene"
"aego/scene/sceneio"
"aego/schema"
)
const (
Category3D = "3D"
Moved3DNote = "a 3D node needs a 3D parent; created at the scene root"
)
type Template struct {
ID string
Name string
Module string
Category string
Menu string
Icon string
Builtin bool
NoOnAdd bool
Space scene.Space
Build func(s *scene.Scene, parent scene.NodeHandle) scene.NodeHandle
}
type Drop struct {
Owner string
Kind string
Space scene.Space
Setup func(s *scene.Scene, h scene.NodeHandle, id ids.AssetID)
}
type Place struct {
At2 *mathx.Vec2
At3 *mathx.Vec3
}
func (p Place) Fits(t Template) bool {
switch {
case p.At3 != nil:
return t.Space == scene.Space3D
case p.At2 != nil:
return t.Space == scene.Space2D
}
return true
}
type PrefabSource interface {
Space(id ids.AssetID) (scene.Space, bool)
Build(s *scene.Scene, parent scene.NodeHandle, id ids.AssetID) (scene.NodeHandle, error)
}
type Registry struct {
list []Template
drops []Drop
prefabs PrefabSource
}
func (r *Registry) SetPrefabs(p PrefabSource) {
r.prefabs = p
}
func New() *Registry {
return &Registry{}
}
func SpaceOf(k schema.SpaceKind) scene.Space {
if k == schema.Space3D {
return scene.Space3D
}
return scene.Space2D
}
func create(s *scene.Scene, parent scene.NodeHandle, sp scene.Space, name string) scene.NodeHandle {
if sp == scene.Space3D {
return s.Create3D(parent, name)
}
return s.Create(parent, name)
}
func FromDesc(d schema.TemplateDesc) (Template, error) {
setup, ok := d.Setup.(func(*scene.Scene, scene.NodeHandle))
if !ok {
return Template{}, errs.New(errs.EditorPanel, "template setup has an unsupported type",
log.F("template", d.ID), log.F("module", d.Owner))
}
node := d.Node
if node == "" {
node = d.Name
}
sp := SpaceOf(d.Space)
return Template{
ID: d.ID, Name: d.Name, Module: d.Owner, Category: d.Category, Menu: d.Menu, Icon: d.Icon, Builtin: d.Builtin, Space: sp,
Build: func(s *scene.Scene, parent scene.NodeHandle) scene.NodeHandle {
h := create(s, parent, sp, node)
if !h.IsZero() {
setup(s, h)
}
return h
},
}, nil
}
func (r *Registry) Load(descs []schema.TemplateDesc) error {
var all []error
for _, d := range descs {
t, err := FromDesc(d)
if err == nil {
err = r.Register(t)
}
if err != nil {
all = append(all, err)
}
}
return errors.Join(all...)
}
func (r *Registry) Register(t Template) error {
if t.ID == "" || t.Build == nil {
return errs.New(errs.EditorPanel, "template needs an id and a build function", log.F("template", t.ID))
}
for _, o := range r.list {
if o.ID == t.ID {
return errs.New(errs.EditorPanel, "duplicate template id", log.F("template", t.ID))
}
}
if t.Name == "" {
t.Name = t.ID
}
r.list = append(r.list, t)
return nil
}
func (r *Registry) Unregister(id string) {
for i, t := range r.list {
if t.ID == id {
r.list = append(r.list[:i], r.list[i+1:]...)
return
}
}
}
func (r *Registry) All() []Template {
return r.list
}
func (r *Registry) Get(id string) (Template, bool) {
for _, t := range r.list {
if t.ID == id {
return t, true
}
}
return Template{}, false
}
func (r *Registry) Spawn(s *scene.Scene, parent scene.NodeHandle, id string) scene.NodeHandle {
t, ok := r.Get(id)
if !ok {
return scene.NodeHandle{}
}
return t.Build(s, parent)
}
func (r *Registry) Root() []Template {
var out []Template
for _, t := range r.list {
if t.Builtin {
out = append(out, t)
}
}
return out
}
func (r *Registry) Modules() []string {
seen := map[string]bool{}
var out []string
for _, t := range r.list {
if !t.Builtin && !seen[t.Module] {
seen[t.Module] = true
out = append(out, t.Module)
}
}
sort.Strings(out)
return out
}
func (r *Registry) InModule(m string) []Template {
var out []Template
for _, t := range r.list {
if !t.Builtin && t.Module == m {
out = append(out, t)
}
}
sort.SliceStable(out, func(i, j int) bool { return out[i].Category < out[j].Category })
return out
}
func (r *Registry) AddDrop(d Drop) error {
if d.Kind == "" || d.Setup == nil {
return errs.New(errs.EditorPanel, "asset drop needs a kind and a setup", log.F("module", d.Owner))
}
r.drops = append(r.drops, d)
return nil
}
func (r *Registry) RemoveDrops(owner string) {
out := r.drops[:0]
for _, d := range r.drops {
if d.Owner != owner {
out = append(out, d)
}
}
r.drops = out
}
func (r *Registry) drop(kind asset.ArtifactKind, space *scene.Space) (Drop, bool) {
for _, d := range r.drops {
if context.AssetMatches(d.Kind, kind) && (space == nil || d.Space == *space) {
return d, true
}
}
return Drop{}, false
}
func (r *Registry) Accepts(kind asset.ArtifactKind, space *scene.Space) bool {
if kind == asset.ArtPrefab {
return r.prefabs != nil
}
_, ok := r.drop(kind, space)
return ok
}
func (r *Registry) ForAsset(kind asset.ArtifactKind, space *scene.Space, id ids.AssetID, name string) (Template, bool) {
if name == "" {
name = string(kind)
}
if kind == asset.ArtPrefab {
if r.prefabs == nil {
return Template{}, false
}
sp, _ := r.prefabs.Space(id)
if space != nil && *space != sp {
return Template{}, false
}
src := r.prefabs
return Template{
ID: "prefab." + id.String(), Name: name, Module: "editor", Builtin: true, NoOnAdd: true, Space: sp,
Build: func(s *scene.Scene, parent scene.NodeHandle) scene.NodeHandle {
h, err := src.Build(s, parent, id)
if err != nil {
return scene.NodeHandle{}
}
return h
},
}, true
}
d, ok := r.drop(kind, space)
if !ok {
return Template{}, false
}
return Template{
ID: d.Owner + ".drop." + string(kind), Name: name, Module: d.Owner, Builtin: true, Space: d.Space,
Build: func(s *scene.Scene, parent scene.NodeHandle) scene.NodeHandle {
h := create(s, parent, d.Space, name)
if !h.IsZero() {
d.Setup(s, h, id)
}
return h
},
}, true
}
func Parent3D(s *scene.Scene, parent scene.NodeHandle) (scene.NodeHandle, string) {
if parent.IsZero() || !s.IsAlive(parent) {
return s.Root(), ""
}
if s.AllowsSpace(parent, scene.Space3D) {
return parent, ""
}
return s.Root(), Moved3DNote
}
func resolver(ctx *context.Editor) asset.Resolver {
if ctx.Loader != nil {
return ctx.Loader
}
return nil
}
func build(t Template, s *scene.Scene) (h scene.NodeHandle, err error) {
defer func() {
if rec := recover(); rec != nil {
err = errs.New(errs.EditorPanel, "template panicked",
log.F("template", t.ID), log.F("panic", fmt.Sprint(rec)))
}
}()
h = t.Build(s, s.Root())
if h.IsZero() || !s.IsAlive(h) {
err = errs.New(errs.EditorPanel, "template returned no node", log.F("template", t.ID))
}
return h, err
}
func place(s, scratch *scene.Scene, h scene.NodeHandle, parent ids.NodeID, pl Place) error {
var ph scene.NodeHandle
if !parent.IsZero() {
ph = s.Handle(parent)
}
switch {
case pl.At3 != nil && scratch.Space(h) == scene.Space3D:
at := *pl.At3
if !ph.IsZero() {
inv, ok := s.World4(ph).InverseAffine()
if !ok {
return errs.New(errs.SceneBadReparent, "parent transform cannot be inverted", log.F("parent", s.Name(ph)))
}
at = inv.TransformPoint(at)
}
t := scratch.Local3D(h)
t.Position = t.Position.Add(at)
scratch.SetLocal3D(h, t)
case pl.At2 != nil && scratch.Space(h) == scene.Space2D:
local := *pl.At2
if !ph.IsZero() {
inv, ok := s.World(ph).Inverse()
if !ok {
return errs.New(errs.SceneBadReparent, "parent transform cannot be inverted", log.F("parent", s.Name(ph)))
}
local = inv.Apply(local)
}
scratch.SetPosition(h, local)
}
return nil
}
func InstantiatePlace(ctx *context.Editor, t Template, parent ids.NodeID, index int, pl Place) ([]ids.NodeID, error) {
d := ctx.Doc()
if d == nil {
return nil, errs.New(errs.EditorDocument, "no scene is open")
}
s := d.Scene()
if t.Space == scene.Space3D && !parent.IsZero() {
if ph := s.Handle(parent); !ph.IsZero() && !s.AllowsSpace(ph, scene.Space3D) {
parent, index = ids.NodeID{}, -1
ctx.Notify(context.LevelWarn, Moved3DNote)
}
}
scratch := scene.New(ctx.Types, resolver(ctx), scene.Options{InitialNodes: 16})
h, err := build(t, scratch)
if err != nil {
return nil, err
}
scratch.Flush()
if !t.NoOnAdd {
for _, n := range scratch.PreOrder() {
if n != scratch.Root() && scratch.IsAlive(n) {
ctx.RunOnAdd(scratch, n)
}
}
}
if err := place(s, scratch, h, parent, pl); err != nil {
return nil, err
}
cmd := commands.NewMutation("Create "+t.Name, commands.EnvFor(s),
commands.Insert(parent, index, string(sceneio.SaveSubtree(scratch, h, true))))
if err := d.History.Exec(cmd); err != nil {
return nil, err
}
created := cmd.Created()
ctx.Select(created...)
return created, nil
}
func Instantiate(ctx *context.Editor, t Template, parent ids.NodeID, index int, at *mathx.Vec2) ([]ids.NodeID, error) {
return InstantiatePlace(ctx, t, parent, index, Place{At2: at})
}
func InstantiateAt3(ctx *context.Editor, t Template, parent ids.NodeID, at mathx.Vec3) ([]ids.NodeID, error) {
return InstantiatePlace(ctx, t, parent, -1, Place{At3: &at})
}