567 lines
15 KiB
Go
567 lines
15 KiB
Go
package sceneio
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"aego/core/errs"
|
|
"aego/core/ids"
|
|
"aego/core/log"
|
|
"aego/format/aetext"
|
|
"aego/scene"
|
|
"aego/scene/prefab"
|
|
"aego/scene/scriptcomp"
|
|
"aego/schema"
|
|
)
|
|
|
|
func pseudo(name string, k schema.Kind) *schema.FieldDescriptor {
|
|
return &schema.FieldDescriptor{Name: name, Kind: k}
|
|
}
|
|
|
|
func (l *loader) localOf(n *aetext.Node) ids.NodeID {
|
|
if id, err := ids.ParseNodeID(n.Label); err == nil {
|
|
return id
|
|
}
|
|
return ids.NewNodeID()
|
|
}
|
|
|
|
func transformKind(field string, v aetext.Value) (schema.Kind, bool) {
|
|
list := v.Kind == aetext.VList
|
|
switch field {
|
|
case prefab.FieldPosition, prefab.FieldScale:
|
|
switch {
|
|
case list && len(v.List) == 2:
|
|
return schema.KindVec2, true
|
|
case list && len(v.List) == 3:
|
|
return schema.KindVec3, true
|
|
}
|
|
case prefab.FieldRotation:
|
|
switch {
|
|
case list && len(v.List) == 4:
|
|
return schema.KindQuat, true
|
|
case v.Kind == aetext.VInt || v.Kind == aetext.VFloat:
|
|
return schema.KindFloat32, true
|
|
}
|
|
}
|
|
return schema.KindInvalid, false
|
|
}
|
|
|
|
func (l *loader) propField(p prefab.PropKey, v aetext.Value) (*schema.FieldDescriptor, prefab.PropKey, bool) {
|
|
switch {
|
|
case p.Owner == prefab.OwnerNode:
|
|
switch p.Field {
|
|
case prefab.FieldName:
|
|
return pseudo(p.Field, schema.KindString), p, true
|
|
case prefab.FieldEnabled:
|
|
return pseudo(p.Field, schema.KindBool), p, true
|
|
case prefab.FieldLayers:
|
|
return pseudo(p.Field, schema.KindInt64), p, true
|
|
}
|
|
case p.Owner == prefab.OwnerTransform:
|
|
if k, ok := transformKind(p.Field, v); ok {
|
|
return pseudo(p.Field, k), p, true
|
|
}
|
|
case strings.HasPrefix(p.Owner, prefab.ScriptPrefix):
|
|
td, ok := l.reg.ByName(strings.TrimPrefix(p.Owner, prefab.ScriptPrefix))
|
|
if !ok || td.Kind != schema.TypeScript {
|
|
break
|
|
}
|
|
owner := prefab.ScriptOwner(td.FullName)
|
|
if p.Field == prefab.FieldActive {
|
|
return pseudo(p.Field, schema.KindBool), prefab.PropKey{Owner: owner, Field: p.Field}, true
|
|
}
|
|
if fd, ok := td.Field(p.Field); ok {
|
|
return fd, prefab.PropKey{Owner: owner, Field: fd.Name}, true
|
|
}
|
|
default:
|
|
td, ok := l.reg.ByName(p.Owner)
|
|
if !ok || td.Kind != schema.TypeComponent {
|
|
break
|
|
}
|
|
if fd, ok := td.Field(p.Field); ok {
|
|
return fd, prefab.PropKey{Owner: td.FullName, Field: fd.Name}, true
|
|
}
|
|
}
|
|
return nil, p, false
|
|
}
|
|
|
|
func (l *loader) readModel(n *aetext.Node) (*prefab.Node, error) {
|
|
local := l.localOf(n)
|
|
if _, ok := n.Attr("prefab"); ok {
|
|
inst, err := l.readInstance(n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &prefab.Node{Local: local, Instance: inst}, nil
|
|
}
|
|
space3D := false
|
|
if v, ok := n.Attr("space"); ok {
|
|
sp, _ := scene.ParseSpace(v.Str)
|
|
space3D = sp == scene.Space3D
|
|
}
|
|
name := "Node"
|
|
if v, ok := n.Attr("name"); ok {
|
|
name = v.Str
|
|
}
|
|
nd := prefab.NewNode(local, name, space3D)
|
|
base := nd.Comp(prefab.OwnerNode)
|
|
if v, ok := n.Attr("enabled"); ok {
|
|
base.Set(prefab.FieldEnabled, schema.Bool(v.Bool))
|
|
}
|
|
if v, ok := n.Attr("layers"); ok {
|
|
base.Set(prefab.FieldLayers, schema.Int64(v.AsInt()))
|
|
}
|
|
tr := nd.Comp(prefab.OwnerTransform)
|
|
for _, b := range n.Blocks {
|
|
switch b.Head {
|
|
case "transform":
|
|
if !space3D {
|
|
t := readTransform(b)
|
|
tr.Set(prefab.FieldPosition, schema.Vec2(t.Position))
|
|
tr.Set(prefab.FieldRotation, schema.Float32(t.Rotation))
|
|
tr.Set(prefab.FieldScale, schema.Vec2(t.Scale))
|
|
}
|
|
case "transform3d":
|
|
if space3D {
|
|
t := readTransform3D(b)
|
|
tr.Set(prefab.FieldPosition, schema.Vec3(t.Position))
|
|
tr.Set(prefab.FieldRotation, schema.Quat(t.Rotation))
|
|
tr.Set(prefab.FieldScale, schema.Vec3(t.Scale))
|
|
}
|
|
case "component":
|
|
if b.Label == scriptcomp.ScriptName {
|
|
for _, sb := range b.Blocks {
|
|
if sb.Head == "script" {
|
|
nd.Comps = append(nd.Comps, l.modelScript(sb))
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
c, err := l.modelComp(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nd.Comps = append(nd.Comps, c)
|
|
case "node":
|
|
child, err := l.readModel(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nd.Children = append(nd.Children, child)
|
|
}
|
|
}
|
|
return nd, nil
|
|
}
|
|
|
|
func (l *loader) modelComp(b *aetext.Node) (prefab.Comp, error) {
|
|
td, ok := l.reg.ByName(b.Label)
|
|
if !ok || td.Kind != schema.TypeComponent {
|
|
return prefab.Comp{Owner: b.Label, Kind: prefab.CompUnknown, Raw: b.Clone()}, nil
|
|
}
|
|
p := td.New()
|
|
if td.Default != nil && td.CloneInto != nil {
|
|
td.CloneInto(p, td.Default())
|
|
}
|
|
c := prefab.Comp{Owner: td.FullName, Kind: prefab.CompComponent}
|
|
td.Serializable(func(fd *schema.FieldDescriptor) {
|
|
var v schema.Value
|
|
fd.Get(p, &v)
|
|
c.Fields = append(c.Fields, prefab.Field{Name: fd.Name, Value: v})
|
|
})
|
|
for _, f := range b.Fields {
|
|
fd, ok := td.Field(f.Name)
|
|
if !ok {
|
|
l.report.warn(l.name, f.Pos, "unknown field "+td.FullName+"."+f.Name)
|
|
continue
|
|
}
|
|
v, err := l.convert(fd, f.Value)
|
|
if err != nil {
|
|
if l.opt.Strict {
|
|
return prefab.Comp{}, err
|
|
}
|
|
l.report.warn(l.name, f.Pos, err.Error())
|
|
continue
|
|
}
|
|
c.Set(fd.Name, v)
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (l *loader) modelScript(sb *aetext.Node) prefab.Comp {
|
|
enabled := true
|
|
if v, ok := sb.Attr("enabled"); ok {
|
|
enabled = v.Bool
|
|
}
|
|
active := prefab.Field{Name: prefab.FieldActive, Value: schema.Bool(enabled)}
|
|
td, known := l.reg.ByName(sb.Label)
|
|
if !known || td.Kind != schema.TypeScript {
|
|
return prefab.Comp{Owner: prefab.ScriptOwner(sb.Label), Kind: prefab.CompScript, Fields: []prefab.Field{active}, Raw: slices.Clone(sb.Fields)}
|
|
}
|
|
c := prefab.Comp{Owner: prefab.ScriptOwner(td.FullName), Kind: prefab.CompScript, Fields: []prefab.Field{active}}
|
|
var bag schema.PropertyBag
|
|
for i := range td.Fields {
|
|
var v schema.Value
|
|
td.Fields[i].Get(unsafe.Pointer(&bag), &v)
|
|
c.Fields = append(c.Fields, prefab.Field{Name: td.Fields[i].Name, Value: v})
|
|
}
|
|
var unknown []aetext.Field
|
|
for _, f := range sb.Fields {
|
|
fd, ok := td.Field(f.Name)
|
|
if !ok {
|
|
unknown = append(unknown, f)
|
|
continue
|
|
}
|
|
v, err := scriptValue(fd, f.Value)
|
|
if err != nil {
|
|
unknown = append(unknown, f)
|
|
continue
|
|
}
|
|
c.Set(fd.Name, v)
|
|
}
|
|
if len(unknown) > 0 {
|
|
c.Raw = unknown
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (l *loader) readInstance(n *aetext.Node) (*prefab.Instance, error) {
|
|
v, _ := n.Attr("prefab")
|
|
id, err := ids.ParseAssetID(v.Str)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err, errs.SceneIOParse, "bad prefab reference").With(log.F("path", l.name), log.F("line", n.Pos.Line))
|
|
}
|
|
inst := &prefab.Instance{Prefab: id, IDs: map[prefab.Key]ids.NodeID{}}
|
|
var extra []*aetext.Node
|
|
for _, b := range n.Blocks {
|
|
if !l.instanceEntry(inst, b) {
|
|
extra = append(extra, b.Clone())
|
|
}
|
|
}
|
|
if len(extra) > 0 {
|
|
inst.Raw = extra
|
|
}
|
|
return inst, nil
|
|
}
|
|
|
|
func (l *loader) instanceEntry(inst *prefab.Instance, b *aetext.Node) bool {
|
|
orphan := false
|
|
if v, ok := b.Attr("orphan"); ok {
|
|
orphan = v.Bool
|
|
}
|
|
k, okKey := prefab.ParseKey(b.Label)
|
|
if !okKey {
|
|
return false
|
|
}
|
|
o := &inst.Overrides
|
|
switch b.Head {
|
|
case "id":
|
|
v, ok := b.Attr("node")
|
|
if !ok {
|
|
return false
|
|
}
|
|
id, err := ids.ParseNodeID(v.Str)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
inst.IDs[k] = id
|
|
case "override":
|
|
pv, okp := b.Attr("prop")
|
|
val, okv := b.Attr("value")
|
|
if !okp || !okv {
|
|
return false
|
|
}
|
|
pk, ok := prefab.ParsePropKey(pv.Str)
|
|
if !ok {
|
|
return false
|
|
}
|
|
fd, canon, ok := l.propField(pk, val)
|
|
if !ok {
|
|
return false
|
|
}
|
|
v, err := l.convert(fd, val)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
o.Props = append(o.Props, prefab.PropOverride{Node: k, Prop: canon, Value: v, Orphan: orphan})
|
|
case "addComponent":
|
|
if len(b.Blocks) != 1 {
|
|
return false
|
|
}
|
|
cb := b.Blocks[0]
|
|
var c prefab.Comp
|
|
switch cb.Head {
|
|
case "component":
|
|
mc, err := l.modelComp(cb)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
c = mc
|
|
case "script":
|
|
c = l.modelScript(cb)
|
|
default:
|
|
return false
|
|
}
|
|
o.AddComps = append(o.AddComps, prefab.CompAdd{Node: k, Comp: c, Orphan: orphan})
|
|
case "removeComponent":
|
|
v, ok := b.Attr("owner")
|
|
if !ok {
|
|
return false
|
|
}
|
|
o.RemoveComps = append(o.RemoveComps, prefab.CompRemove{Node: k, Owner: v.Str})
|
|
case "addNode":
|
|
if len(b.Blocks) != 1 || b.Blocks[0].Head != "node" {
|
|
return false
|
|
}
|
|
nd, err := l.readModel(b.Blocks[0])
|
|
if err != nil {
|
|
return false
|
|
}
|
|
o.AddNodes = append(o.AddNodes, prefab.NodeAdd{Parent: k, Node: nd, Orphan: orphan})
|
|
case "remove":
|
|
o.Remove = append(o.Remove, k)
|
|
case "order":
|
|
v, ok := b.Attr("children")
|
|
if !ok {
|
|
return false
|
|
}
|
|
ord := prefab.ChildOrder{Parent: k}
|
|
for _, item := range v.List {
|
|
ck, ok := prefab.ParseKey(item.Str)
|
|
if !ok {
|
|
return false
|
|
}
|
|
ord.Children = append(ord.Children, ck)
|
|
}
|
|
o.Order = append(o.Order, ord)
|
|
default:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func transform2DOf(c *prefab.Comp) scene.Transform2D {
|
|
t := scene.IdentityTransform()
|
|
if c == nil {
|
|
return t
|
|
}
|
|
if v, ok := c.Get(prefab.FieldPosition); ok && v.Kind == schema.KindVec2 {
|
|
t.Position = v.V
|
|
}
|
|
if v, ok := c.Get(prefab.FieldRotation); ok && v.Kind == schema.KindFloat32 {
|
|
t.Rotation = float32(v.F)
|
|
}
|
|
if v, ok := c.Get(prefab.FieldScale); ok && v.Kind == schema.KindVec2 {
|
|
t.Scale = v.V
|
|
}
|
|
return t
|
|
}
|
|
|
|
func transform3DOf(c *prefab.Comp) scene.Transform3D {
|
|
t := scene.IdentityTransform3D()
|
|
if c == nil {
|
|
return t
|
|
}
|
|
if v, ok := c.Get(prefab.FieldPosition); ok && v.Kind == schema.KindVec3 {
|
|
t.Position = v.V3
|
|
}
|
|
if v, ok := c.Get(prefab.FieldRotation); ok && v.Kind == schema.KindQuat {
|
|
t.Rotation = v.Q
|
|
}
|
|
if v, ok := c.Get(prefab.FieldScale); ok && v.Kind == schema.KindVec3 {
|
|
t.Scale = v.V3
|
|
}
|
|
return t
|
|
}
|
|
|
|
func (w *writer) fieldOf(p prefab.PropKey, v schema.Value) *schema.FieldDescriptor {
|
|
if p.Field != prefab.FieldActive && p.Owner != prefab.OwnerNode && p.Owner != prefab.OwnerTransform {
|
|
if td, ok := w.reg.ByName(strings.TrimPrefix(p.Owner, prefab.ScriptPrefix)); ok {
|
|
if fd, ok := td.Field(p.Field); ok {
|
|
return fd
|
|
}
|
|
}
|
|
}
|
|
return pseudo(p.Field, v.Kind)
|
|
}
|
|
|
|
func (w *writer) modelNode(nd *prefab.Node, idOf func(*prefab.Node) ids.NodeID, owned map[prefab.Key]bool) *aetext.Node {
|
|
if nd.Instance != nil {
|
|
return w.instanceBlock(idOf(nd), nd.Instance)
|
|
}
|
|
if nd.Origin != nil && owned != nil && !owned[nd.Key] {
|
|
return w.instanceBlock(idOf(nd), nd.Origin)
|
|
}
|
|
n := &aetext.Node{Head: "node", Label: idOf(nd).String()}
|
|
enabled, layers := true, int64(1)
|
|
if v, ok := nd.Get(prefab.PropKey{Owner: prefab.OwnerNode, Field: prefab.FieldEnabled}); ok {
|
|
enabled = v.I != 0
|
|
}
|
|
if v, ok := nd.Get(prefab.PropKey{Owner: prefab.OwnerNode, Field: prefab.FieldLayers}); ok {
|
|
layers = v.AsInt()
|
|
}
|
|
n.Attrs = append(n.Attrs,
|
|
aetext.Field{Name: "name", Value: aetext.String(nd.Name())},
|
|
aetext.Field{Name: "enabled", Value: aetext.Bool(enabled)},
|
|
aetext.Field{Name: "layers", Value: aetext.Int(layers)},
|
|
)
|
|
tr := nd.Comp(prefab.OwnerTransform)
|
|
if nd.Space3D {
|
|
n.Attrs = append(n.Attrs, aetext.Field{Name: "space", Value: aetext.String(scene.Space3D.String())})
|
|
n.Blocks = append(n.Blocks, saveTransform3D(transform3DOf(tr)))
|
|
} else {
|
|
n.Blocks = append(n.Blocks, saveTransform(transform2DOf(tr)))
|
|
}
|
|
if nd.Origin != nil && !nd.Missing {
|
|
n.Attrs = append(n.Attrs, aetext.Field{Name: "instanceOf", Value: aetext.AssetRef(nd.Origin.Prefab.String())})
|
|
}
|
|
var scripts *aetext.Node
|
|
for i := range nd.Comps {
|
|
c := &nd.Comps[i]
|
|
switch c.Kind {
|
|
case prefab.CompComponent, prefab.CompUnknown:
|
|
if b := w.modelComp(c); b != nil {
|
|
n.Blocks = append(n.Blocks, b)
|
|
}
|
|
case prefab.CompScript:
|
|
if scripts == nil {
|
|
scripts = &aetext.Node{Head: "component", Label: scriptcomp.ScriptName,
|
|
Attrs: []aetext.Field{{Name: "version", Value: aetext.Int(1)}}}
|
|
}
|
|
scripts.Blocks = append(scripts.Blocks, w.modelScript(c))
|
|
}
|
|
}
|
|
if scripts != nil {
|
|
n.Blocks = append(n.Blocks, scripts)
|
|
}
|
|
for _, c := range nd.Children {
|
|
n.Blocks = append(n.Blocks, w.modelNode(c, idOf, owned))
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (w *writer) modelComp(c *prefab.Comp) *aetext.Node {
|
|
if c.Kind == prefab.CompUnknown {
|
|
if raw, ok := c.Raw.(*aetext.Node); ok {
|
|
return raw.Clone()
|
|
}
|
|
return nil
|
|
}
|
|
td, ok := w.reg.ByName(c.Owner)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
n := &aetext.Node{Head: "component", Label: td.FullName}
|
|
n.Attrs = append(n.Attrs, aetext.Field{Name: "version", Value: aetext.Int(int64(td.Version))})
|
|
td.Serializable(func(fd *schema.FieldDescriptor) {
|
|
if v, ok := c.Get(fd.Name); ok {
|
|
n.Fields = append(n.Fields, aetext.Field{Name: fd.Name, Value: w.value(fd, v)})
|
|
}
|
|
})
|
|
return n
|
|
}
|
|
|
|
func (w *writer) modelScript(c *prefab.Comp) *aetext.Node {
|
|
full := strings.TrimPrefix(c.Owner, prefab.ScriptPrefix)
|
|
enabled := true
|
|
if v, ok := c.Get(prefab.FieldActive); ok {
|
|
enabled = v.I != 0
|
|
}
|
|
b := &aetext.Node{Head: "script", Label: full}
|
|
b.Attrs = append(b.Attrs, aetext.Field{Name: "enabled", Value: aetext.Bool(enabled)})
|
|
td, known := w.reg.ByName(full)
|
|
known = known && td.Kind == schema.TypeScript
|
|
if known {
|
|
for i := range td.Fields {
|
|
fd := &td.Fields[i]
|
|
if v, ok := c.Get(fd.Name); ok {
|
|
b.Fields = append(b.Fields, aetext.Field{Name: fd.Name, Value: w.value(fd, v)})
|
|
}
|
|
}
|
|
}
|
|
if raw, ok := c.Raw.([]aetext.Field); ok {
|
|
for _, f := range raw {
|
|
if known {
|
|
if _, dup := td.Field(f.Name); dup {
|
|
continue
|
|
}
|
|
}
|
|
b.Fields = append(b.Fields, f)
|
|
}
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (w *writer) compBlock(c *prefab.Comp) *aetext.Node {
|
|
if c.Kind == prefab.CompScript {
|
|
return w.modelScript(c)
|
|
}
|
|
return w.modelComp(c)
|
|
}
|
|
|
|
func entry(head, label string, attrs ...aetext.Field) *aetext.Node {
|
|
return &aetext.Node{Head: head, Label: label, Attrs: attrs}
|
|
}
|
|
|
|
func orphaned(b *aetext.Node, orphan bool) *aetext.Node {
|
|
if orphan {
|
|
b.Attrs = append(b.Attrs, aetext.Field{Name: "orphan", Value: aetext.Bool(true)})
|
|
}
|
|
return b
|
|
}
|
|
|
|
func localID(n *prefab.Node) ids.NodeID {
|
|
return n.Local
|
|
}
|
|
|
|
func (w *writer) instanceBlock(label ids.NodeID, inst *prefab.Instance) *aetext.Node {
|
|
n := &aetext.Node{Head: "node", Label: label.String()}
|
|
n.Attrs = append(n.Attrs, aetext.Field{Name: "prefab", Value: aetext.AssetRef(inst.Prefab.String())})
|
|
for _, k := range slices.Sorted(maps.Keys(inst.IDs)) {
|
|
n.Blocks = append(n.Blocks, entry("id", string(k), aetext.Field{Name: "node", Value: aetext.NodeRef(inst.IDs[k].String())}))
|
|
}
|
|
o := inst.Overrides.Clone()
|
|
o.Sort()
|
|
for _, p := range o.Props {
|
|
b := entry("override", string(p.Node),
|
|
aetext.Field{Name: "prop", Value: aetext.String(p.Prop.String())},
|
|
aetext.Field{Name: "value", Value: w.value(w.fieldOf(p.Prop, p.Value), p.Value)})
|
|
n.Blocks = append(n.Blocks, orphaned(b, p.Orphan))
|
|
}
|
|
for i := range o.AddComps {
|
|
a := &o.AddComps[i]
|
|
cb := w.compBlock(&a.Comp)
|
|
if cb == nil {
|
|
continue
|
|
}
|
|
b := orphaned(entry("addComponent", string(a.Node)), a.Orphan)
|
|
b.Blocks = append(b.Blocks, cb)
|
|
n.Blocks = append(n.Blocks, b)
|
|
}
|
|
for _, r := range o.RemoveComps {
|
|
n.Blocks = append(n.Blocks, entry("removeComponent", string(r.Node), aetext.Field{Name: "owner", Value: aetext.String(r.Owner)}))
|
|
}
|
|
for _, a := range o.AddNodes {
|
|
b := orphaned(entry("addNode", string(a.Parent)), a.Orphan)
|
|
b.Blocks = append(b.Blocks, w.modelNode(a.Node, localID, nil))
|
|
n.Blocks = append(n.Blocks, b)
|
|
}
|
|
for _, k := range o.Remove {
|
|
n.Blocks = append(n.Blocks, entry("remove", string(k)))
|
|
}
|
|
for _, ord := range o.Order {
|
|
items := make([]aetext.Value, len(ord.Children))
|
|
for i, k := range ord.Children {
|
|
items[i] = aetext.String(string(k))
|
|
}
|
|
n.Blocks = append(n.Blocks, entry("order", string(ord.Parent), aetext.Field{Name: "children", Value: aetext.List(items...)}))
|
|
}
|
|
if extra, ok := inst.Raw.([]*aetext.Node); ok {
|
|
for _, e := range extra {
|
|
n.Blocks = append(n.Blocks, e.Clone())
|
|
}
|
|
}
|
|
return n
|
|
}
|