289 lines
6.1 KiB
Go
289 lines
6.1 KiB
Go
package fieldgizmo
|
|
|
|
import (
|
|
"aego/core/mathx"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/gizmo"
|
|
"aego/platform/input"
|
|
"aego/scene"
|
|
"aego/scene/scriptcomp"
|
|
"aego/schema"
|
|
)
|
|
|
|
const partBase = 32
|
|
|
|
var colField = mathx.Color{R: 0.35, G: 0.55, B: 1, A: 1}
|
|
|
|
type field struct {
|
|
t *schema.TypeDescriptor
|
|
fd *schema.FieldDescriptor
|
|
}
|
|
|
|
type geom struct {
|
|
base mathx.Vec2
|
|
tip mathx.Vec2
|
|
r float32
|
|
}
|
|
|
|
func gizmoFields(td *schema.TypeDescriptor) bool {
|
|
for i := range td.Fields {
|
|
if td.Fields[i].Hints.Gizmo != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func collect(s *scene.Scene, h scene.NodeHandle, t schema.TypeID, script bool, out []field) []field {
|
|
reg := s.Registry()
|
|
add := func(td *schema.TypeDescriptor) {
|
|
for i := range td.Fields {
|
|
if fd := &td.Fields[i]; fd.Hints.Gizmo != "" {
|
|
out = append(out, field{t: td, fd: fd})
|
|
}
|
|
}
|
|
}
|
|
if !script {
|
|
if td, ok := reg.ByID(t); ok {
|
|
add(td)
|
|
}
|
|
return out
|
|
}
|
|
sc, ok := scriptcomp.ScriptsOf(s, h)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for i := range sc.Attachments {
|
|
if td, ok := reg.ByName(sc.Attachments[i].TypeName); ok && td.Kind == schema.TypeScript {
|
|
add(td)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func shapeOf(s *scene.Scene, h scene.NodeHandle, f field, scale float32) (geom, bool) {
|
|
v, err := s.GetProperty(h, f.t.ID, f.fd.ID)
|
|
if err != nil {
|
|
return geom{}, false
|
|
}
|
|
m := s.World(h)
|
|
base := mathx.Vec2{X: m[6], Y: m[7]}
|
|
local := f.fd.Hints.GizmoSpace != schema.GizmoWorld
|
|
switch f.fd.Hints.Gizmo {
|
|
case schema.GizmoVector:
|
|
d := v.V
|
|
if local {
|
|
d = m.Apply(d).Sub(base)
|
|
}
|
|
return geom{base: base, tip: base.Add(d.Scale(scale))}, true
|
|
case schema.GizmoPoint:
|
|
p := v.V
|
|
if local {
|
|
p = m.Apply(p)
|
|
}
|
|
return geom{base: base, tip: p}, true
|
|
case schema.GizmoRadius:
|
|
r := float32(v.AsFloat())
|
|
if local {
|
|
r *= mathx.Sqrt(m[0]*m[0] + m[1]*m[1])
|
|
}
|
|
return geom{base: base, tip: base.Add(mathx.Vec2{X: r}), r: r}, true
|
|
}
|
|
return geom{}, false
|
|
}
|
|
|
|
type Gizmo struct {
|
|
ctx *context.Editor
|
|
t schema.TypeID
|
|
script bool
|
|
list []field
|
|
active field
|
|
}
|
|
|
|
func Register(ctx *context.Editor, reg *gizmo.Registry) error {
|
|
for _, td := range ctx.Types.Schema().Components() {
|
|
if td.ID == scriptcomp.ScriptTypeID || !gizmoFields(td) {
|
|
continue
|
|
}
|
|
if err := reg.Register(td.ID, &Gizmo{ctx: ctx, t: td.ID}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := reg.Register(scriptcomp.ScriptTypeID, &Gizmo{ctx: ctx, script: true}); err != nil {
|
|
return err
|
|
}
|
|
l := &layer{ctx: ctx}
|
|
ctx.Vectors = append(ctx.Vectors, context.VectorLayer{ID: "fields", Title: "Fields", Draw: l.draw})
|
|
return nil
|
|
}
|
|
|
|
func (g *Gizmo) scale() float32 {
|
|
if g.ctx == nil {
|
|
return context.DefaultVectorScale
|
|
}
|
|
return g.ctx.Overlay.VectorScale()
|
|
}
|
|
|
|
func (g *Gizmo) mods() input.Mods {
|
|
if g.ctx == nil || g.ctx.UI == nil {
|
|
return 0
|
|
}
|
|
return g.ctx.UI.Mods()
|
|
}
|
|
|
|
func (g *Gizmo) Draw(c *gizmo.DrawCtx) {
|
|
s := c.Scene
|
|
h := s.Handle(c.Node)
|
|
if h.IsZero() || !s.IsAlive(h) {
|
|
return
|
|
}
|
|
g.list = collect(s, h, g.t, g.script, g.list[:0])
|
|
sc := g.scale()
|
|
for i, f := range g.list {
|
|
gm, ok := shapeOf(s, h, f, sc)
|
|
if !ok {
|
|
continue
|
|
}
|
|
col := c.Color(gizmo.Part(partBase+i), colField)
|
|
switch f.fd.Hints.Gizmo {
|
|
case schema.GizmoVector:
|
|
c.Line(gm.base, gm.tip, col)
|
|
if d := gm.tip.Sub(gm.base); d.LenSq() > 0 {
|
|
c.Triangle(gm.tip, d, 12, 9, col)
|
|
}
|
|
case schema.GizmoPoint:
|
|
dim := col
|
|
dim.A *= 0.4
|
|
c.Line(gm.base, gm.tip, dim)
|
|
case schema.GizmoRadius:
|
|
c.Debug.Circle(gm.base, gm.r, 48, col)
|
|
}
|
|
c.FillHandle(gm.tip, 8, col)
|
|
}
|
|
}
|
|
|
|
func (g *Gizmo) HitTest(c *gizmo.HitCtx) (gizmo.Hit, bool) {
|
|
s := c.Scene
|
|
h := s.Handle(c.Node)
|
|
if h.IsZero() || !s.IsAlive(h) {
|
|
return gizmo.Hit{}, false
|
|
}
|
|
g.list = collect(s, h, g.t, g.script, g.list[:0])
|
|
sc := g.scale()
|
|
best, bestD := -1, c.Space.Px(9)
|
|
for i, f := range g.list {
|
|
gm, ok := shapeOf(s, h, f, sc)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if d := c.Space.ToPixels(gm.tip).Sub(c.MousePx).Len(); d <= bestD {
|
|
best, bestD = i, d
|
|
}
|
|
}
|
|
if best < 0 {
|
|
return gizmo.Hit{}, false
|
|
}
|
|
return gizmo.Hit{Part: gizmo.Part(partBase + best), Dist: bestD}, true
|
|
}
|
|
|
|
func (g *Gizmo) Begin(e *gizmo.EditCtx, hit gizmo.Hit) {
|
|
e.Part = hit.Part
|
|
s := e.Scene
|
|
g.list = collect(s, s.Handle(e.Node), g.t, g.script, g.list[:0])
|
|
g.active = field{}
|
|
if i := int(hit.Part) - partBase; i >= 0 && i < len(g.list) {
|
|
g.active = g.list[i]
|
|
}
|
|
}
|
|
|
|
func snapAngle(d mathx.Vec2, step float32) mathx.Vec2 {
|
|
l := d.Len()
|
|
if l == 0 {
|
|
return d
|
|
}
|
|
a := mathx.Atan2(d.Y, d.X)
|
|
a = float32(int(a/step+mathx.Sign(a)*0.5)) * step
|
|
return mathx.Vec2{X: mathx.Cos(a) * l, Y: mathx.Sin(a) * l}
|
|
}
|
|
|
|
func snapGrid(v mathx.Vec2, step float32) mathx.Vec2 {
|
|
if step <= 0 {
|
|
return v
|
|
}
|
|
r := func(f float32) float32 { return float32(int(f/step+mathx.Sign(f)*0.5)) * step }
|
|
return mathx.Vec2{X: r(v.X), Y: r(v.Y)}
|
|
}
|
|
|
|
func (g *Gizmo) grid() float32 {
|
|
if g.ctx == nil {
|
|
return 0
|
|
}
|
|
return g.ctx.Settings.SnapMove
|
|
}
|
|
|
|
func (g *Gizmo) Update(e *gizmo.EditCtx) {
|
|
f := g.active
|
|
if f.fd == nil {
|
|
return
|
|
}
|
|
s := e.Scene
|
|
h := s.Handle(e.Node)
|
|
if h.IsZero() {
|
|
return
|
|
}
|
|
m := s.World(h)
|
|
inv, ok := m.Inverse()
|
|
if !ok {
|
|
return
|
|
}
|
|
base := mathx.Vec2{X: m[6], Y: m[7]}
|
|
local := f.fd.Hints.GizmoSpace != schema.GizmoWorld
|
|
mods := g.mods()
|
|
ctrl := mods.Primary()
|
|
var val schema.Value
|
|
switch f.fd.Hints.Gizmo {
|
|
case schema.GizmoVector:
|
|
d := e.Mouse.Sub(base)
|
|
if mods.Has(input.ModShift) {
|
|
d = snapAngle(d, mathx.Deg2Rad(15))
|
|
}
|
|
w := d.Scale(1 / g.scale())
|
|
if local {
|
|
w = inv.Apply(base.Add(w)).Sub(inv.Apply(base))
|
|
}
|
|
if ctrl {
|
|
w = snapGrid(w, g.grid())
|
|
}
|
|
val = schema.Vec2(w)
|
|
case schema.GizmoPoint:
|
|
p := e.Mouse
|
|
if ctrl {
|
|
p = snapGrid(p, g.grid())
|
|
}
|
|
if local {
|
|
p = inv.Apply(p)
|
|
}
|
|
val = schema.Vec2(p)
|
|
case schema.GizmoRadius:
|
|
r := e.Mouse.Sub(base).Len()
|
|
if local {
|
|
if sx := mathx.Sqrt(m[0]*m[0] + m[1]*m[1]); sx > 0 {
|
|
r /= sx
|
|
}
|
|
}
|
|
if ctrl {
|
|
r = snapGrid(mathx.Vec2{X: r}, g.grid()).X
|
|
}
|
|
if f.fd.Kind == schema.KindFloat64 {
|
|
val = schema.Float64(float64(r))
|
|
} else {
|
|
val = schema.Float32(r)
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
gizmo.SetProperty(e, f.t.ID, f.fd, val)
|
|
}
|
|
|
|
func (g *Gizmo) End(*gizmo.EditCtx) {}
|