ci / release (push) Successful in 5m5s
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 4m41s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 7m14s
The remaining editor strings and undo step names; component, field, enum, setting, diagnostic and Script API texts under schema.*, enum.*, setting.*, diag.* and api.*; the launcher, the in-game Settings View with a Language setting, the CLI and aego schema show --lang. Every documentation page has a .ru.md translation; the site has a full Russian tree with hreflang, a language switch and ru/llms.txt; aego docs check --lang ru. New doc.tutorial and doc.recipe icons. Fix: an animator restored by a rewind prepares its controller before the replayed step.
187 lines
5.9 KiB
Go
187 lines
5.9 KiB
Go
package editor
|
|
|
|
import (
|
|
"aego/core/errs"
|
|
"aego/core/ids"
|
|
"aego/core/log"
|
|
"aego/core/mathx"
|
|
"aego/editor/core/command"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/history"
|
|
"aego/editor/ext"
|
|
"aego/editor/ext/inspect"
|
|
"aego/engine/i18n"
|
|
"aego/modules/physics"
|
|
"aego/modules/physics/components"
|
|
"aego/modules/physics/settings"
|
|
"aego/modules/physics/systems"
|
|
"aego/schema"
|
|
)
|
|
|
|
const (
|
|
CmdDebug = "physics.debug"
|
|
FlagShapes = "physics.debug.shapes"
|
|
FlagContacts = "physics.debug.contacts"
|
|
FlagAABBs = "physics.debug.aabbs"
|
|
FlagJoints = "physics.debug.joints"
|
|
FlagIslands = "physics.debug.islands"
|
|
FlagBroad = "physics.debug.broadphase"
|
|
CmdSettings = "physics.settings"
|
|
PageID = settings.ID
|
|
Category = "Physics"
|
|
layerForces = "forces"
|
|
partBase = 32
|
|
)
|
|
|
|
type issueKey struct {
|
|
node ids.NodeID
|
|
comp string
|
|
}
|
|
|
|
type plugin struct {
|
|
ed *context.Editor
|
|
types [components.KindCount]*schema.TypeDescriptor
|
|
body *schema.TypeDescriptor
|
|
distance *schema.TypeDescriptor
|
|
revolute *schema.TypeDescriptor
|
|
index map[issueKey]string
|
|
rev uint64
|
|
tx *history.Transaction
|
|
col components.Collider
|
|
pts []mathx.Vec2
|
|
focus string
|
|
}
|
|
|
|
func lookup(sr *schema.Registry, name string) (*schema.TypeDescriptor, error) {
|
|
td, ok := sr.ByName(name)
|
|
if !ok {
|
|
return nil, errs.New(errs.SchemaUnknownType, "physics component is not registered", log.F("type", name))
|
|
}
|
|
return td, nil
|
|
}
|
|
|
|
func newPlugin(ed *context.Editor, sr *schema.Registry) (*plugin, error) {
|
|
p := &plugin{ed: ed, index: map[issueKey]string{}}
|
|
var err error
|
|
for k := components.Kind(0); k < components.KindCount; k++ {
|
|
if p.types[k], err = lookup(sr, components.Names[k]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if p.body, err = lookup(sr, components.NameBody); err != nil {
|
|
return nil, err
|
|
}
|
|
if p.distance, err = lookup(sr, components.NameDistance); err != nil {
|
|
return nil, err
|
|
}
|
|
if p.revolute, err = lookup(sr, components.NameRevolute); err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func Hook(e *ext.Registry) error {
|
|
p, err := newPlugin(e.Editor(), e.Schema())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k := components.Kind(0); k < components.KindCount; k++ {
|
|
name := components.Names[k]
|
|
if err := e.Gizmo(name, &colliderGizmo{p: p, kind: k, td: p.types[k]}); err != nil {
|
|
return err
|
|
}
|
|
if err := e.Bounds(name, bounds(k)); err != nil {
|
|
return err
|
|
}
|
|
if err := e.Inspector(name, inspect.CustomFunc(p.colliderInspector(k))); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := e.Gizmo(components.NameDistance, &jointGizmo{p: p, td: p.distance}); err != nil {
|
|
return err
|
|
}
|
|
if err := e.Gizmo(components.NameRevolute, &jointGizmo{p: p, td: p.revolute, revolute: true}); err != nil {
|
|
return err
|
|
}
|
|
if err := e.Inspector(components.NameBody, inspect.CustomFunc(p.bodyInspector)); err != nil {
|
|
return err
|
|
}
|
|
e.GameOverlay(p.overlay)
|
|
for _, l := range []struct {
|
|
id string
|
|
title i18n.Text
|
|
fn func(*context.VectorSink)
|
|
}{
|
|
{"velocity", i18n.T("i18n.physics.vectors.velocity", "Velocity"), p.velocity},
|
|
{"angular", i18n.T("i18n.physics.vectors.angular_velocity", "Angular Velocity"), p.angular},
|
|
{layerForces, i18n.T("i18n.physics.vectors.forces", "Forces"), p.forces},
|
|
{"contacts", i18n.T("i18n.physics.vectors.contact_impulses", "Contact Impulses"), p.contacts},
|
|
{"gravity", i18n.T("i18n.physics.vectors.gravity", "Gravity"), p.gravity},
|
|
} {
|
|
if err := e.Vectors(l.id, l.title, l.fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := e.SettingsPage(settings.ID, p.settingsPage, i18n.T("i18n.physics.gravity", "Gravity"), i18n.T("i18n.physics.substeps", "Substeps"), i18n.T("i18n.physics.velocity_iterations", "Velocity Iterations"), i18n.T("i18n.physics.relax_iterations", "Relax Iterations"), i18n.T("i18n.physics.contact_hertz", "Contact Hertz"), i18n.T("i18n.physics.contact_damping_ratio", "Contact Damping Ratio"), i18n.T("i18n.physics.contact_push_velocity", "Contact Push Velocity"), i18n.T("i18n.physics.restitution_threshold", "Restitution Threshold"), i18n.T("i18n.physics.linear_slop", "Linear Slop"), i18n.T("i18n.physics.sleep_linear", "Sleep Linear"), i18n.T("i18n.physics.sleep_angular", "Sleep Angular"), i18n.T("i18n.physics.sleep_time", "Sleep Time"), i18n.T("i18n.physics.layers", "Layers"), i18n.T("i18n.physics.collision_matrix", "Collision Matrix")); err != nil {
|
|
return err
|
|
}
|
|
e.Profiler(i18n.T("i18n.physics.profiler", "Physics"), true, p.profile)
|
|
if p.ed != nil {
|
|
e.Tick(p.syncChannels)
|
|
o := &p.ed.Overlay
|
|
o.Set(CmdDebug, true)
|
|
o.Set(FlagShapes, true)
|
|
o.Set(FlagJoints, true)
|
|
}
|
|
return p.commands(e)
|
|
}
|
|
|
|
func (p *plugin) commands(e *ext.Registry) error {
|
|
o := &p.ed.Overlay
|
|
toggle := func(id, name string, order int) command.Desc {
|
|
return command.Desc{
|
|
ID: id, Name: name, Category: Category, Order: order,
|
|
Checked: func() bool { return o.On(id) },
|
|
Run: func() error {
|
|
o.Toggle(id)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
list := []command.Desc{
|
|
toggle(CmdDebug, i18n.Tr("i18n.physics.debug_overlay", "Debug Overlay"), 100),
|
|
toggle(FlagShapes, i18n.Tr("i18n.physics.shapes", "Shapes"), 200),
|
|
toggle(FlagContacts, i18n.Tr("i18n.physics.contacts", "Contacts"), 210),
|
|
toggle(FlagAABBs, "AABBs", 220),
|
|
toggle(FlagJoints, i18n.Tr("i18n.physics.joints", "Joints"), 230),
|
|
toggle(FlagIslands, i18n.Tr("i18n.physics.islands", "Islands"), 240),
|
|
toggle(FlagBroad, i18n.Tr("i18n.physics.broadphase_tree", "Broadphase Tree"), 250),
|
|
{ID: CmdSettings, Name: "Physics Settings…", Category: Category, Order: 900, Run: func() error {
|
|
if p.ed.ShowSettings != nil {
|
|
p.ed.ShowSettings(PageID)
|
|
}
|
|
return nil
|
|
}},
|
|
}
|
|
for _, d := range list {
|
|
if err := e.Command(d); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *plugin) sim() *systems.Sim {
|
|
if p.ed == nil || p.ed.Docs == nil {
|
|
return nil
|
|
}
|
|
return physics.SimOf(p.ed.Docs.ActiveWorld())
|
|
}
|
|
|
|
func (p *plugin) playSim() *systems.Sim {
|
|
if p.ed == nil {
|
|
return nil
|
|
}
|
|
return physics.SimOf(p.ed.PlayWorld())
|
|
}
|