From 09ec98075118bc567d657fd7a6c75c798474ce4c Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Mon, 30 Mar 2020 03:17:47 -0700 Subject: [PATCH] major update to eve, with basic box-based collision detection and renaming to support path toward physics-based system working. --- README.md | 38 +++++++-- eve/Makefile | 2 +- eve/bbox.go | 25 +++++- eve/body.go | 15 +++- eve/box.go | 18 ++-- eve/capsule.go | 18 ++-- eve/collide.go | 72 ++++++++++++++++ eve/cylinder.go | 18 ++-- eve/group.go | 153 ++++++++++++++++++++++++++++------ eve/node.go | 123 +++++++++++++++++++-------- eve/nodeflags_string.go | 39 +++++++++ eve/phys.go | 69 +++++++++++++-- eve/rigid.go | 30 +++++++ eve/sphere.go | 18 ++-- eve/surface.go | 11 --- examples/virtroom/virtroom.go | 63 +++++++++----- go.mod | 6 +- go.sum | 20 +++-- 18 files changed, 594 insertions(+), 144 deletions(-) create mode 100644 eve/collide.go create mode 100644 eve/nodeflags_string.go create mode 100644 eve/rigid.go delete mode 100644 eve/surface.go diff --git a/README.md b/README.md index 628dd46..3fc1473 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The *Emergent Virtual Engine* (EVE) is a scenegraph-based physics simulator for creating virtual environments for neural network models to grow up in. -Ultimately we hope to figure out how the Bullet simulator works and get that running here, in a clean and simple implementation. +Ultimately we hope to figure out how the [Bullet](https://github.com/bulletphysics/bullet3) system works and get that running here, in a clean and simple implementation. Incrementally, we will start with a very basic explicitly driven form of physics that is sufficient to get started, and build from there. @@ -10,14 +10,40 @@ The world is made using [GoKi](https://github.com/goki/ki) based trees (groups, Rendering can *optionally* be performed using corresponding 3D renders in the `gi3d` 3D rendering framework in the [GoGi](https://github.com/goki/gi) GUI framework, using an `epev.View` object that sync's the two. -We also use the full-featured `gi.mat32` math / matrix library (adapted from the `g3n` 3D game environment package). +We also use the full-featured `goki/mat32` math / matrix library (adapted from the `g3n` 3D game environment package). -# Usual rationalization for reinventing the wheel yet again +# Organizing the World -* Pure *Go* build environment is fast, delightful, simple, clean, easy-to-read, runs fast, etc. In contrast, building other systems typically requires something like Qt or other gui dependencies, and we know what that world is like, and why we left it for Go.. +It is most efficient to create a relatively deep tree with `Group` nodes that collect nearby `Body` objects at multiple levels of spatial scale. Bounding Boxes are computed at every level of Group, and pruning is done at every level, so large chunks of the tree can be eliminated easily with this strategy. -* Control, control, control.. we can do what we want in a way that we know will work. +Also, Nodes must be specifically flagged as being `Dynamic` -- otherwise they are assumed to be static -- and each type should be organized into separate top-level Groups (there can be multiple of each, but don't mix Dynamic and Static). Static nodes are never collided against each-other. Ideally, all the Dynamic nodes are in separate top-level, or at least second-to-top level groups -- this eliminates redundant A vs. B and B vs. A collisions and focuses each collision on the most relevant information. -* Physics is easy. Seriously, the basic stuff is just a few vectors and matricies and a few simple equations. Doing the full physics version is considerably more challenging technically but we should be able to figure that out in due course, and really most environments we care about don't really require the full physics anyway. +# Updating Modes +There are two major modes of updating: Scripted, Physics -- scripted requires a program to control what happens on every time step, while physics uses computed forces from contacts, plus joint constraints, to update velocities (not yet supported). The update modes are just about which methods you call. +The `Group` has a set of `World*` methods that should be used on the top-level world Group node node to do all the init and update steps. The update loops automatically exclude non Dynamic nodes. + +* `WorldInit` -- everyone calls this at the start to set the initial config + +* `WorldRelToAbs` -- for scripted mode when updating relative positions, rotations. + +* `WorldStepPhys` -- for either scripted or physics modes, to update from current velocities. + +* `WorldCollide` -- returns list of potential collision contacts based on projected motion, focusing on dynamic vs. static and dynamic vs. dynamic bodies, with optimized tree filtering. This is the first pass for collision detection. + +## Scripted Mode + +For Scripted mode, each update step typically involves manually updating the `Rel.Pos` and `.Quat` fields on `Body` objects to update their relative positions. This field is a `Phys` type and has `MoveOnAxis` and `RotateOnAxis` (and a number of other rotation methods). The Move methods update the `LinVel` field to reflect any delta in movement. + +It is also possible to manually set the `Abs.LinVel` and `Abs.AngVel` fields and call `StepPhys` to update. + +For collision detection, it is essential to have the `Abs.LinVel` field set to anticipate the effects of motion and determine likely future impacts. The RelToAbs update call does this automatically, and if you're instead using StepPhys the LinVel is already set. Both calls will automatically compute an updated BBox and VelBBox. + +It is up to the user to manage the list of potential collisions, e.g., by setting velocity to 0 or bouncing back etc. + +## Physics Mode + +The good news so far is that the full physics version as in Bullet is actually not too bad. The core update step is a super simple forward Euler, intuitive update (just add velocity to position, with a step size factor). The remaining work is just in computing the forces to update those velocities. Bullet uses a hybrid approach that is clearly described in the [Mirtich thesis](https://people.eecs.berkeley.edu/~jfc/mirtich/thesis/mirtichThesis.pdf), which combines *impulses* with a particular way of handling joints, due originally to Featherstone. Impulses are really simple conceptually: when two objects collide, they bounce back off of each other in proportion to their `Bounce` (coefficient of restitution) factor -- these collision impact forces dominate everything else, and aren't that hard to compute (similar conceptually to the `marbles` example in GoGi). The joint constraint stuff is a bit more complicated but not the worst. Everything can be done incrementally. And the resulting system will avoid the brittle nature of the full constraint-based approach taken in ODE, which caused a lot of crashes and instability in `cemer`. + +One of the major problems with the impulse-based approach: that it causes otherwise "still" objects to jiggle around and slip down planes, seems eminently tractable with special-case code that doesn't seem too hard. diff --git a/eve/Makefile b/eve/Makefile index 3b70ec4..21e0c5b 100644 --- a/eve/Makefile +++ b/eve/Makefile @@ -18,7 +18,7 @@ clean: $(GOCLEAN) # NOTE: MUST update version number here prior to running 'make release' -VERS=v0.50.0 +VERS=v0.5.1 PACKAGE=eve GIT_COMMIT=`git rev-parse --short HEAD` VERS_DATE=`date -u +%Y-%m-%d\ %H:%M` diff --git a/eve/bbox.go b/eve/bbox.go index d220deb..784a7b0 100644 --- a/eve/bbox.go +++ b/eve/bbox.go @@ -8,7 +8,8 @@ import "github.com/goki/mat32" // BBox contains bounding box and other gross object properties type BBox struct { - BBox mat32.Box3 `desc:"bounding box in local coords"` + BBox mat32.Box3 `desc:"bounding box in world coords (Axis-Aligned Bounding Box = AABB)"` + VelBBox mat32.Box3 `desc:"velocity-projected bounding box in world coords: extend BBox to include future position of moving bodies -- collision must be made on this basis"` BSphere mat32.Sphere `desc:"bounding sphere in local coords"` Area float32 `desc:"area"` Volume float32 `desc:"volume"` @@ -28,9 +29,25 @@ func (bb *BBox) UpdateFmBBox() { bb.Volume = sz.X * sz.Y * sz.Z } -// XForm transforms bounds with given quat and position offset +// XForm transforms bounds with given quat and position offset to convert to world coords func (bb *BBox) XForm(q mat32.Quat, pos mat32.Vec3) { - bb.BBox = bb.BBox.MulQuat(q) - bb.BBox = bb.BBox.Translate(pos) + bb.BBox = bb.BBox.MulQuat(q).Translate(pos) bb.BSphere.Translate(pos) } + +// VelProject computes the velocity-projected bounding box for given velocity and step size +func (bb *BBox) VelProject(vel mat32.Vec3, step float32) { + eb := bb.BBox.Translate(vel.MulScalar(step)) + bb.VelBBox = bb.BBox + bb.VelBBox.ExpandByBox(eb) +} + +// VelNilProject is for static items -- just copy the BBox +func (bb *BBox) VelNilProject() { + bb.VelBBox = bb.BBox +} + +// IntersectsVelBox returns true if two velocity-projected bounding boxes intersect +func (bb *BBox) IntersectsVelBox(oth *BBox) bool { + return bb.VelBBox.IntersectsBox(oth.VelBBox) +} diff --git a/eve/body.go b/eve/body.go index 1f471c9..d8bb88a 100644 --- a/eve/body.go +++ b/eve/body.go @@ -10,14 +10,19 @@ type Body interface { // AsBodyBase returns the body as a BodyBase AsBodyBase() *BodyBase + + // SetDynamic sets the Dynamic flag for this body, indicating that it moves. + // It is important to collect all dynamic objects into separate top-level group(s) + // for more efficiently organizing the collision detection process. + SetDynamic() } // BodyBase is the base type for all specific Body types type BodyBase struct { NodeBase - Surf Surface `desc:"surface properties, including friction and bouncyness"` - Vis string `desc:"visualization name -- looks up an entry in the scene library that provides the visual representation of this body"` - Color string `desc:"default color of body for basic InitLibrary configuration"` + Rigid Rigid `desc:"rigid body properties, including mass, bounce, friction etc"` + Vis string `desc:"visualization name -- looks up an entry in the scene library that provides the visual representation of this body"` + Color string `desc:"default color of body for basic InitLibrary configuration"` } func (bb *BodyBase) NodeType() NodeTypes { @@ -35,3 +40,7 @@ func (bb *BodyBase) AsBodyBase() *BodyBase { func (bb *BodyBase) GroupBBox() { } + +func (bb *BodyBase) SetDynamic() { + bb.SetFlag(int(Dynamic)) +} diff --git a/eve/box.go b/eve/box.go index 7a38a14..f293229 100644 --- a/eve/box.go +++ b/eve/box.go @@ -19,7 +19,7 @@ type Box struct { var KiT_Box = kit.Types.AddType(&Box{}, BoxProps) var BoxProps = ki.Props{ - "EnumType:Flag": ki.KiT_Flags, + "EnumType:Flag": KiT_NodeFlags, } // AddNewBox adds a new box of given name, initial position and size to given parent @@ -35,12 +35,20 @@ func (bx *Box) SetBBox() { bx.BBox.XForm(bx.Abs.Quat, bx.Abs.Pos) } -func (bx *Box) InitPhys(par *NodeBase) { - bx.InitBase(par) +func (bx *Box) InitAbs(par *NodeBase) { + bx.InitAbsBase(par) bx.SetBBox() + bx.BBox.VelNilProject() } -func (bx *Box) UpdatePhys(par *NodeBase) { - bx.UpdateBase(par) +func (bx *Box) RelToAbs(par *NodeBase) { + bx.RelToAbsBase(par) bx.SetBBox() + bx.BBox.VelProject(bx.Abs.LinVel, 1) +} + +func (bx *Box) StepPhys(step float32) { + bx.StepPhysBase(step) + bx.SetBBox() + bx.BBox.VelProject(bx.Abs.LinVel, step) } diff --git a/eve/capsule.go b/eve/capsule.go index eec34d2..6fc85e5 100644 --- a/eve/capsule.go +++ b/eve/capsule.go @@ -22,7 +22,7 @@ type Capsule struct { var KiT_Capsule = kit.Types.AddType(&Capsule{}, CapsuleProps) var CapsuleProps = ki.Props{ - "EnumType:Flag": ki.KiT_Flags, + "EnumType:Flag": KiT_NodeFlags, } // AddNewCapsule adds a new capsule of given name, initial position @@ -43,12 +43,20 @@ func (cp *Capsule) SetBBox() { cp.BBox.XForm(cp.Abs.Quat, cp.Abs.Pos) } -func (cp *Capsule) InitPhys(par *NodeBase) { - cp.InitBase(par) +func (cp *Capsule) InitAbs(par *NodeBase) { + cp.InitAbsBase(par) cp.SetBBox() + cp.BBox.VelNilProject() } -func (cp *Capsule) UpdatePhys(par *NodeBase) { - cp.UpdateBase(par) +func (cp *Capsule) RelToAbs(par *NodeBase) { + cp.RelToAbsBase(par) cp.SetBBox() + cp.BBox.VelProject(cp.Abs.LinVel, 1) +} + +func (cp *Capsule) StepPhys(step float32) { + cp.StepPhysBase(step) + cp.SetBBox() + cp.BBox.VelProject(cp.Abs.LinVel, step) } diff --git a/eve/collide.go b/eve/collide.go new file mode 100644 index 0000000..16a24a1 --- /dev/null +++ b/eve/collide.go @@ -0,0 +1,72 @@ +// Copyright (c) 2019, The Emergent Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eve + +import ( + "github.com/goki/ki/ki" + "github.com/goki/mat32" +) + +// Contact is one pairwise point of contact between two bodies. +// Contacts are represented in spherical terms relative to the +// spherical BBox of A and B. +type Contact struct { + A Body `desc:"one body"` + B Body `desc:"the other body"` + NormB mat32.Vec3 `desc:"normal pointing from center of B to center of A"` + PtB mat32.Vec3 `desc:"point on spherical shell of B where A is contacting"` + Dist float32 `desc:"distance from PtB along NormB to contact point on spherical shell of A"` +} + +// UpdtDist updates the distance information for the contact +func (c *Contact) UpdtDist() { + +} + +// Contacts is a slice list of contacts +type Contacts []*Contact + +// AddNew adds a new contact to the list +func (cs *Contacts) AddNew(a, b Body) *Contact { + c := &Contact{A: a, B: b} + *cs = append(*cs, c) + return c +} + +// BodyVelBBoxIntersects returns the list of potential contact nodes between a and b +// (could be the same or different groups) that have intersecting velocity-projected +// bounding boxes. In general a should be dynamic bodies and b either dynamic or static. +// This is the broad first-pass filtering. +func BodyVelBBoxIntersects(a, b Node) Contacts { + var cts Contacts + a.FuncDownMeFirst(0, a.This(), func(k ki.Ki, level int, d interface{}) bool { + aii, ai := KiToNode(k) + if aii == nil { + return false // going into a different type of thing, bail + } + if aii.NodeType() != BODY { + return true + } + abod := aii.AsBody() // only consider bodies from a + + b.FuncDownMeFirst(0, b.This(), func(k ki.Ki, level int, d interface{}) bool { + bii, bi := KiToNode(k) + if bii == nil { + return false // going into a different type of thing, bail + } + if !ai.BBox.IntersectsVelBox(&bi.BBox) { + return false // done + } + if bii.NodeType() == BODY { + cts.AddNew(abod, bii.AsBody()) + return false // done + } + return true // keep going + }) + + return false + }) + return cts +} diff --git a/eve/cylinder.go b/eve/cylinder.go index af16ce9..e662b8e 100644 --- a/eve/cylinder.go +++ b/eve/cylinder.go @@ -22,7 +22,7 @@ type Cylinder struct { var KiT_Cylinder = kit.Types.AddType(&Cylinder{}, CylinderProps) var CylinderProps = ki.Props{ - "EnumType:Flag": ki.KiT_Flags, + "EnumType:Flag": KiT_NodeFlags, } // AddNewCylinder adds a new cylinder of given name, initial position @@ -53,12 +53,20 @@ func (cy *Cylinder) SetBBox() { cy.BBox.XForm(cy.Abs.Quat, cy.Abs.Pos) } -func (cy *Cylinder) InitPhys(par *NodeBase) { - cy.InitBase(par) +func (cy *Cylinder) InitAbs(par *NodeBase) { + cy.InitAbsBase(par) cy.SetBBox() + cy.BBox.VelNilProject() } -func (cy *Cylinder) UpdatePhys(par *NodeBase) { - cy.UpdateBase(par) +func (cy *Cylinder) RelToAbs(par *NodeBase) { + cy.RelToAbsBase(par) cy.SetBBox() + cy.BBox.VelProject(cy.Abs.LinVel, 1) +} + +func (cy *Cylinder) StepPhys(step float32) { + cy.StepPhysBase(step) + cy.SetBBox() + cy.BBox.VelProject(cy.Abs.LinVel, step) } diff --git a/eve/group.go b/eve/group.go index 084f2e2..9c44b2f 100644 --- a/eve/group.go +++ b/eve/group.go @@ -29,44 +29,47 @@ func (gp *Group) NodeType() NodeTypes { return GROUP } -func (gp *Group) InitPhys(par *NodeBase) { - gp.InitBase(par) +func (gp *Group) InitAbs(par *NodeBase) { + gp.InitAbsBase(par) } -func (gp *Group) UpdatePhys(par *NodeBase) { - gp.UpdateBase(par) +func (gp *Group) RelToAbs(par *NodeBase) { + gp.RelToAbsBase(par) // yes we can move groups +} + +func (gp *Group) StepPhys(step float32) { + // groups do NOT update physics } func (gp *Group) GroupBBox() { + hasDyn := false gp.BBox.BBox.SetEmpty() + gp.BBox.VelBBox.SetEmpty() for _, kid := range gp.Kids { nii, ni := KiToNode(kid) if nii == nil { continue } - gp.BBox.BBox.ExpandByPoint(ni.BBox.BBox.Min) - gp.BBox.BBox.ExpandByPoint(ni.BBox.BBox.Max) + gp.BBox.BBox.ExpandByBox(ni.BBox.BBox) + gp.BBox.VelBBox.ExpandByBox(ni.BBox.VelBBox) + if nii.IsDynamic() { + hasDyn = true + } } + gp.SetFlagState(hasDyn, int(Dynamic)) } -// InitWorld does the full tree InitPhys and GroupBBox updates -func (gp *Group) InitWorld() { - gp.FuncDownMeFirst(0, gp.This(), func(k ki.Ki, level int, d interface{}) bool { - nii, _ := KiToNode(k) - if nii == nil { - return false // going into a different type of thing, bail - } - _, pi := KiToNode(k.Parent()) - nii.InitPhys(pi) - return true - }) - +// WorldDynGroupBBox does a GroupBBox on all dynamic nodes +func (gp *Group) WorldDynGroupBBox() { gp.FuncDownMeLast(0, gp.This(), func(k ki.Ki, level int, d interface{}) bool { nii, _ := KiToNode(k) if nii == nil { return false // going into a different type of thing, bail } + if !nii.IsDynamic() { + return false + } return true }, func(k ki.Ki, level int, d interface{}) bool { @@ -74,21 +77,23 @@ func (gp *Group) InitWorld() { if nii == nil { return false // going into a different type of thing, bail } + if !nii.IsDynamic() { + return false + } nii.GroupBBox() return true }) - } -// UpdateWorld does the full tree UpdatePhys and GroupBBox updates -func (gp *Group) UpdateWorld() { +// WorldInit does the full tree InitAbs and GroupBBox updates +func (gp *Group) WorldInit() { gp.FuncDownMeFirst(0, gp.This(), func(k ki.Ki, level int, d interface{}) bool { nii, _ := KiToNode(k) if nii == nil { return false // going into a different type of thing, bail } _, pi := KiToNode(k.Parent()) - nii.UpdatePhys(pi) + nii.InitAbs(pi) return true }) @@ -111,11 +116,111 @@ func (gp *Group) UpdateWorld() { } +// WorldRelToAbs does a full RelToAbs update for all Dynamic groups, for +// Scripted mode updates with manual updating of Rel values. +func (gp *Group) WorldRelToAbs() { + gp.FuncDownMeFirst(0, gp.This(), func(k ki.Ki, level int, d interface{}) bool { + nii, _ := KiToNode(k) + if nii == nil { + return false // going into a different type of thing, bail + } + if !nii.IsDynamic() { + return false + } + _, pi := KiToNode(k.Parent()) + nii.RelToAbs(pi) + return true + }) + + gp.WorldDynGroupBBox() +} + +// WorldStepPhys does a full StepPhys update for all Dynamic nodes, for +// either physics or scripted mode, based on current velocities. +func (gp *Group) WorldStepPhys(step float32) { + gp.FuncDownMeFirst(0, gp.This(), func(k ki.Ki, level int, d interface{}) bool { + nii, _ := KiToNode(k) + if nii == nil { + return false // going into a different type of thing, bail + } + if !nii.IsDynamic() { + return false + } + nii.StepPhys(step) + return true + }) + + gp.WorldDynGroupBBox() +} + +const ( + // DynsTopGps is passed to WorldCollide when all dynamic objects are in separate top groups + DynsTopGps = true + + // DynsSubGps is passed to WorldCollide when all dynamic objects are in separate groups under top + // level (i.e., one level deeper) + DynsSubGps +) + +// WorldCollide does first pass filtering step of collision detection +// based on separate dynamic vs. dynamic and dynamic vs. static groups. +// If dynTop is true, then each Dynamic group is separate at the top level -- +// otherwise they are organized at the next group level. +// Contacts are organized by dynamic group, when non-nil, for easier +// processing. +func (gp *Group) WorldCollide(dynTop bool) []Contacts { + var stats []Node + var dyns []Node + for _, kid := range gp.Kids { + nii, _ := KiToNode(kid) + if nii == nil { + continue + } + if nii.IsDynamic() { + dyns = append(dyns, nii) + } else { + stats = append(stats, nii) + } + } + + var sdyns []Node + if !dynTop { + for _, d := range dyns { + for _, dk := range *d.Children() { + nii, _ := KiToNode(dk) + if nii == nil { + continue + } + sdyns = append(sdyns, nii) + } + } + dyns = sdyns + } + + var cts []Contacts + for i, d := range dyns { + var dct Contacts + for _, s := range stats { + cc := BodyVelBBoxIntersects(d, s) + dct = append(dct, cc...) + } + for di := 0; di < i; di++ { + od := dyns[di] + cc := BodyVelBBoxIntersects(d, od) + dct = append(dct, cc...) + } + if len(dct) > 0 { + cts = append(cts, dct) + } + } + return cts +} + // GroupProps define the ToolBar and MenuBar for StructView var GroupProps = ki.Props{ - "EnumType:Flag": ki.KiT_Flags, + "EnumType:Flag": KiT_NodeFlags, "ToolBar": ki.PropSlice{ - {"InitWorld", ki.Props{ + {"WorldInit", ki.Props{ "desc": "initialize all elements in the world.", "icon": "reset", }}, diff --git a/eve/node.go b/eve/node.go index 49e5687..f74b1c7 100644 --- a/eve/node.go +++ b/eve/node.go @@ -23,21 +23,34 @@ type Node interface { // AsBody returns a generic Body interface for our node -- nil if not a Body AsBody() Body - // InitPhys sets current physical state parameters from Initial values - // which are local, relative to parent -- is passed the parent (nil = top). - // Body nodes should also set their bounding boxes. - // called in a FuncDownMeFirst traversal. - InitPhys(par *NodeBase) + // IsDynamic returns true if node has Dynamic flag set -- otherwise static + // Groups that contain dynamic objects set their dynamic flags. + IsDynamic() bool // GroupBBox sets bounding boxes for groups based on groups or bodies. // called in a FuncDownMeLast traversal. GroupBBox() - // UpdatePhys updates current world physical state parameters based on propagated - // updates from higher levels -- is passed the parent (nil = top). + // InitAbs sets current Abs physical state parameters from Initial values + // which are local, relative to parent -- is passed the parent (nil = top). + // Body nodes should also set their bounding boxes. + // Called in a FuncDownMeFirst traversal. + InitAbs(par *NodeBase) + + // RelToAbs updates current world Abs physical state parameters + // based on Rel values added to updated Abs values at higher levels. + // Abs.LinVel is updated from the resulting change from prior position. + // This is useful for manual updating of relative positions (scripted movement). + // It is passed the parent (nil = top). + // Body nodes should also update their bounding boxes. + // Called in a FuncDownMeFirst traversal. + RelToAbs(par *NodeBase) + + // StepPhys computes one update of the world Abs physical state parameters, + // using *current* velocities -- add forces prior to calling. + // Use this for physics-based state updates. // Body nodes should also update their bounding boxes. - // called in a FuncDownMeFirst traversal. - UpdatePhys(par *NodeBase) + StepPhys(step float32) } // NodeBase is the basic eve node, which has position, rotation, velocity @@ -45,11 +58,16 @@ type Node interface { // There are only three different kinds of Nodes: Group, Body, and Joint type NodeBase struct { ki.Node - Initial Phys `view:"inline" desc:"initial position, orientation, velocity in *local* coordinates (relative to parent)"` - Rel Phys `view:"inline" desc:"current relative (local) position, orientation, velocity -- only change these values, as abs values are computed therefrom"` - Abs Phys `inactive:"+" view:"inline" desc:"current absolute (world) position, orientation, velocity"` - Mass float32 `desc:"mass of body or aggregate mass of group of bodies (just fyi for groups) -- 0 mass = no dynamics"` - BBox BBox `desc:"bounding box in world coordinates (aggregated for groups)"` + Initial Phys `view:"inline" desc:"initial position, orientation, velocity in *local* coordinates (relative to parent)"` + Rel Phys `view:"inline" desc:"current relative (local) position, orientation, velocity -- only change these values, as abs values are computed therefrom"` + Abs Phys `inactive:"+" view:"inline" desc:"current absolute (world) position, orientation, velocity"` + BBox BBox `desc:"bounding box in world coordinates (aggregated for groups)"` +} + +var KiT_NodeBase = kit.Types.AddType(&NodeBase{}, NodeBaseProps) + +var NodeBaseProps = ki.Props{ + "EnumType:Flag": KiT_NodeFlags, } func (nb *NodeBase) AsNodeBase() *NodeBase { @@ -60,38 +78,55 @@ func (nb *NodeBase) AsBody() Body { return nil } -// InitBase is the base-level initialization of basic Phys state from Initial conditions -func (nb *NodeBase) InitBase(par *NodeBase) { +func (nb *NodeBase) IsDynamic() bool { + return nb.HasFlag(int(Dynamic)) +} + +// InitAbsBase is the base-level version of InitAbs -- most nodes call this. +// InitAbs sets current Abs physical state parameters from Initial values +// which are local, relative to parent -- is passed the parent (nil = top). +// Body nodes should also set their bounding boxes. +// Called in a FuncDownMeFirst traversal. +func (nb *NodeBase) InitAbsBase(par *NodeBase) { if nb.Initial.Quat.IsNil() { nb.Initial.Quat.SetIdentity() } nb.Rel = nb.Initial if par != nil { - nb.Abs.Quat = nb.Initial.Quat.Mul(par.Abs.Quat) - nb.Abs.Pos = nb.Initial.Pos.MulQuat(par.Abs.Quat).Add(par.Abs.Pos) - nb.Abs.LinVel = nb.Initial.LinVel.MulQuat(nb.Initial.Quat).Add(par.Abs.LinVel) - nb.Abs.AngVel = nb.Initial.AngVel.MulQuat(nb.Initial.Quat).Add(par.Abs.AngVel) + nb.Abs.FromRel(&nb.Initial, &par.Abs) } else { - nb.Abs.Pos = nb.Initial.Pos - nb.Abs.LinVel = nb.Initial.LinVel - nb.Abs.AngVel = nb.Initial.AngVel - nb.Abs.Quat = nb.Initial.Quat + nb.Abs = nb.Initial } } -// UpdateBase is the base-level update of Phys state based on current relative values -func (nb *NodeBase) UpdateBase(par *NodeBase) { +// RelToAbsBase is the base-level version of RelToAbs -- most nodes call this. +// note: Group WorldRelToAbs ensures only called on Dynamic nodes. +// RelToAbs updates current world Abs physical state parameters +// based on Rel values added to updated Abs values at higher levels. +// Abs.LinVel is updated from the resulting change from prior position. +// This is useful for manual updating of relative positions (scripted movement). +// It is passed the parent (nil = top). +// Body nodes should also update their bounding boxes. +// Called in a FuncDownMeFirst traversal. +func (nb *NodeBase) RelToAbsBase(par *NodeBase) { + ppos := nb.Abs.Pos if par != nil { - nb.Abs.Quat = nb.Rel.Quat.Mul(par.Abs.Quat) - nb.Abs.Pos = nb.Rel.Pos.MulQuat(par.Abs.Quat).Add(par.Abs.Pos) - nb.Abs.LinVel = nb.Rel.LinVel.MulQuat(nb.Rel.Quat).Add(par.Abs.LinVel) - nb.Abs.AngVel = nb.Rel.AngVel.MulQuat(nb.Rel.Quat).Add(par.Abs.AngVel) + nb.Abs.FromRel(&nb.Rel, &par.Abs) } else { - nb.Abs.Pos = nb.Rel.Pos - nb.Abs.LinVel = nb.Rel.LinVel - nb.Abs.AngVel = nb.Rel.AngVel - nb.Abs.Quat = nb.Rel.Quat + nb.Abs = nb.Rel } + nb.Abs.LinVel = nb.Abs.Pos.Sub(ppos) // needed for VelBBox prjn +} + +// StepPhysBase is base-level version of StepPhys -- most nodes call this. +// note: Group WorldRelToAbs ensures only called on Dynamic nodes. +// Computes one update of the world Abs physical state parameters, +// using *current* velocities -- add forces prior to calling. +// Use this for physics-based state updates. +// Body nodes should also update their bounding boxes. +func (nb *NodeBase) StepPhysBase(step float32) { + nb.Abs.StepByAngVel(step) + nb.Abs.StepByLinVel(step) } // KiToNode converts Ki to a Node interface and a Node3DBase obj -- nil if not. @@ -123,3 +158,23 @@ const ( //go:generate stringer -type=NodeTypes var KiT_NodeTypes = kit.Enums.AddEnum(NodeTypesN, kit.NotBitFlag, nil) + +///////////////////////////////////////////////////////////////////// +// NodeFlags + +// NodeFlags define eve node bitflags -- uses ki Flags field (64 bit capacity) +type NodeFlags int + +//go:generate stringer -type=NodeFlags + +var KiT_NodeFlags = kit.Enums.AddEnumExt(ki.KiT_Flags, NodeFlagsN, kit.BitFlag, nil) + +const ( + // Dynamic means that this node can move -- if not so marked, it is + // a Static node. Any top-level group that is not Dynamic is immediately + // pruned from further consideration, so top-level groups should be + // separated into Dynamic and Static nodes at the start. + Dynamic NodeFlags = NodeFlags(ki.FlagsN) + iota + + NodeFlagsN +) diff --git a/eve/nodeflags_string.go b/eve/nodeflags_string.go new file mode 100644 index 0000000..75f5fed --- /dev/null +++ b/eve/nodeflags_string.go @@ -0,0 +1,39 @@ +// Code generated by "stringer -type=NodeFlags"; DO NOT EDIT. + +package eve + +import ( + "errors" + "strconv" +) + +var _ = errors.New("dummy error") + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Dynamic-16] + _ = x[NodeFlagsN-17] +} + +const _NodeFlags_name = "DynamicNodeFlagsN" + +var _NodeFlags_index = [...]uint8{0, 7, 17} + +func (i NodeFlags) String() string { + i -= 16 + if i < 0 || i >= NodeFlags(len(_NodeFlags_index)-1) { + return "NodeFlags(" + strconv.FormatInt(int64(i+16), 10) + ")" + } + return _NodeFlags_name[_NodeFlags_index[i]:_NodeFlags_index[i+1]] +} + +func StringToNodeFlags(s string) (NodeFlags, error) { + for i := 0; i < len(_NodeFlags_index)-1; i++ { + if s == _NodeFlags_name[_NodeFlags_index[i]:_NodeFlags_index[i+1]] { + return NodeFlags(i + 16), nil + } + } + return 0, errors.New("String: " + s + " is not a valid option for type: NodeFlags") +} diff --git a/eve/phys.go b/eve/phys.go index d78f4c0..0841577 100644 --- a/eve/phys.go +++ b/eve/phys.go @@ -5,19 +5,21 @@ package eve import ( + "math" + "github.com/goki/ki/ki" "github.com/goki/ki/kit" "github.com/goki/mat32" ) -// Phys contains the full specification of a given object's physical properties -// including position, orientation, velocity. +// Phys contains the basic physical properties including position, orientation, velocity. +// These are only the values that can be either relative or absolute -- other physical +// state values such as Mass should go in Rigid. type Phys struct { Pos mat32.Vec3 `desc:"position of center of mass of object"` Quat mat32.Quat `desc:"rotation specified as a Quat"` LinVel mat32.Vec3 `desc:"linear velocity"` AngVel mat32.Vec3 `desc:"angular velocity"` - // RotInertia mat32.Mat3 `desc:"Last calculated rotational inertia matrix in local coords"` } var KiT_Phys = kit.Types.AddType(&Phys{}, PhysProps) @@ -29,21 +31,73 @@ func (ps *Phys) Defaults() { } } +/////////////////////////////////////////////////////// +// State updates + +// FromRel sets state from relative values compared to a parent state +func (ps *Phys) FromRel(rel, par *Phys) { + ps.Quat = rel.Quat.Mul(par.Quat) + ps.Pos = rel.Pos.MulQuat(par.Quat).Add(par.Pos) + ps.LinVel = rel.LinVel.MulQuat(rel.Quat).Add(par.LinVel) + ps.AngVel = rel.AngVel.MulQuat(rel.Quat).Add(par.AngVel) +} + +// AngMotionMax is maximum angular motion that can be taken per update +const AngMotionMax = math.Pi / 4 + +// StepByAngVel steps the Quat rotation from angular velocity +func (ps *Phys) StepByAngVel(step float32) { + ang := mat32.Sqrt(ps.AngVel.Dot(ps.AngVel)) + + // limit the angular motion + if ang*step > AngMotionMax { + ang = AngMotionMax / step + } + var axis mat32.Vec3 + if ang < 0.001 { + // use Taylor's expansions of sync function + axis = ps.AngVel.MulScalar(0.5*step - (step*step*step)*0.020833333333*ang*ang) + } else { + // sync(fAngle) = sin(c*fAngle)/t + axis = ps.AngVel.MulScalar(mat32.Sin(0.5*ang*step) / ang) + } + var dq mat32.Quat + dq.SetFromAxisAngle(axis, ang*step) + ps.Quat = dq.Mul(ps.Quat) + ps.Quat.Normalize() +} + +// StepByLinVel steps the Pos from the linear velocity +func (ps *Phys) StepByLinVel(step float32) { + ps.Pos = ps.Pos.Add(ps.LinVel.MulScalar(step)) +} + /////////////////////////////////////////////////////// // Moving -// Note: you can just directly add to .Pos too.. +// Move moves (translates) Pos by given amount, and sets the LinVel to the given +// delta -- this can be useful for Scripted motion to track movement. +func (ps *Phys) Move(delta mat32.Vec3) { + ps.LinVel = delta + ps.Pos.SetAdd(delta) +} // MoveOnAxis moves (translates) the specified distance on the specified local axis, // relative to the current rotation orientation. +// The axis is normalized prior to aplying the distance factor. +// Sets the LinVel to motion vector. func (ps *Phys) MoveOnAxis(x, y, z, dist float32) { - ps.Pos.SetAdd(mat32.NewVec3(x, y, z).Normal().MulQuat(ps.Quat).MulScalar(dist)) + ps.LinVel = mat32.NewVec3(x, y, z).Normal().MulQuat(ps.Quat).MulScalar(dist) + ps.Pos.SetAdd(ps.LinVel) } // MoveOnAxisAbs moves (translates) the specified distance on the specified local axis, -// in absolute X,Y,Z coordinates. +// in absolute X,Y,Z coordinates (does not apply the Quat rotation factor. +// The axis is normalized prior to aplying the distance factor. +// Sets the LinVel to motion vector. func (ps *Phys) MoveOnAxisAbs(x, y, z, dist float32) { - ps.Pos.SetAdd(mat32.NewVec3(x, y, z).Normal().MulScalar(dist)) + ps.LinVel = mat32.NewVec3(x, y, z).Normal().MulScalar(dist) + ps.Pos.SetAdd(ps.LinVel) } /////////////////////////////////////////////////////// @@ -101,7 +155,6 @@ func (ps *Phys) RotateEulerRad(x, y, z, angle float32) { // PhysProps define the ToolBar and MenuBar for StructView var PhysProps = ki.Props{ - "EnumType:Flag": ki.KiT_Flags, "ToolBar": ki.PropSlice{ {"SetEulerRotation", ki.Props{ "desc": "Set the local rotation (relative to parent) using Euler angles, in degrees.", diff --git a/eve/rigid.go b/eve/rigid.go new file mode 100644 index 0000000..6b6887d --- /dev/null +++ b/eve/rigid.go @@ -0,0 +1,30 @@ +// Copyright (c) 2019, The Emergent Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package eve + +import ( + "github.com/goki/ki/ki" + "github.com/goki/ki/kit" + "github.com/goki/mat32" +) + +// Rigid contains the full specification of a given object's basic physics +// properties including position, orientation, velocity. These +type Rigid struct { + InvMass float32 `desc:"1/mass -- 0 for no mass"` + Bounce float32 `min:"0" max:"1" desc:"COR or coefficient of restitution -- how elastic is the collision i.e., final velocity / initial velocity"` + Friction float32 `desc:"friction coefficient -- how much friction is generated by transverse motion"` + Force mat32.Vec3 `desc:"record of computed force vector from last iteration"` + RotInertia mat32.Mat3 `desc:"Last calculated rotational inertia matrix in local coords"` +} + +var KiT_Rigid = kit.Types.AddType(&Rigid{}, RigidProps) + +// Defaults sets defaults only if current values are nil +func (ps *Rigid) Defaults() { +} + +// RigidProps define the ToolBar and MenuBar for StructView +var RigidProps = ki.Props{} diff --git a/eve/sphere.go b/eve/sphere.go index 74bb3b5..4b1f4e0 100644 --- a/eve/sphere.go +++ b/eve/sphere.go @@ -19,7 +19,7 @@ type Sphere struct { var KiT_Sphere = kit.Types.AddType(&Sphere{}, SphereProps) var SphereProps = ki.Props{ - "EnumType:Flag": ki.KiT_Flags, + "EnumType:Flag": KiT_NodeFlags, } // AddNewSphere adds a new sphere of given name, initial position @@ -36,12 +36,20 @@ func (sp *Sphere) SetBBox() { sp.BBox.XForm(sp.Abs.Quat, sp.Abs.Pos) } -func (sp *Sphere) InitPhys(par *NodeBase) { - sp.InitBase(par) +func (sp *Sphere) InitAbs(par *NodeBase) { + sp.InitAbsBase(par) sp.SetBBox() + sp.BBox.VelNilProject() } -func (sp *Sphere) UpdatePhys(par *NodeBase) { - sp.UpdateBase(par) +func (sp *Sphere) RelToAbs(par *NodeBase) { + sp.RelToAbsBase(par) sp.SetBBox() + sp.BBox.VelProject(sp.Abs.LinVel, 1) +} + +func (sp *Sphere) StepPhys(step float32) { + sp.StepPhysBase(step) + sp.SetBBox() + sp.BBox.VelProject(sp.Abs.LinVel, step) } diff --git a/eve/surface.go b/eve/surface.go deleted file mode 100644 index e58a13c..0000000 --- a/eve/surface.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2019, The Emergent Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package eve - -// Surface defines the physical surface properties of bodies -type Surface struct { - Friction float32 `desc:"coulomb friction coefficient (mu). 0 = frictionless, 1e22 = infinity = no slipping"` - Bounce float32 `desc:"(0-1) how bouncy is the surface (0 = hard, 1 = maximum bouncyness)"` -} diff --git a/examples/virtroom/virtroom.go b/examples/virtroom/virtroom.go index 76b678b..be145fb 100644 --- a/examples/virtroom/virtroom.go +++ b/examples/virtroom/virtroom.go @@ -5,8 +5,10 @@ package main import ( + "fmt" "image" "log" + "math/rand" "github.com/emer/eve/eve" "github.com/emer/eve/evev" @@ -42,6 +44,7 @@ type Env struct { View *evev.View `view:"-" desc:"view of world"` Emer *eve.Group `view:"-" desc:"emer group"` EyeR eve.Body `view:"-" desc:"Right eye of emer"` + Contacts eve.Contacts `view:"-" desc:"contacts from last step, for body"` Win *gi.Window `view:"-" desc:"gui window"` SnapImg *gi.Bitmap `view:"-" desc:"snapshot bitmap view"` DepthImg *gi.Bitmap `view:"-" desc:"depth map bitmap view"` @@ -70,12 +73,12 @@ func (ev *Env) MakeWorld() { ev.Emer = MakeEmer(ev.World, ev.EmerHt) ev.EyeR = ev.Emer.ChildByName("head", 1).ChildByName("eye-r", 2).(eve.Body) - ev.World.InitWorld() + ev.World.WorldInit() } // InitWorld does init on world and re-syncs -func (ev *Env) InitWorld() { - ev.World.InitWorld() +func (ev *Env) WorldInit() { + ev.World.WorldInit() if ev.View != nil { ev.View.Sync() ev.Snapshot() @@ -131,54 +134,66 @@ func (ev *Env) ViewDepth(depth []float32) { ev.DepthImg.UpdateSig() } +// WorldStep does one step of the world +func (ev *Env) WorldStep() { + ev.World.WorldRelToAbs() + cts := ev.World.WorldCollide(eve.DynsTopGps) + ev.Contacts = nil + for _, cl := range cts { + if len(cl) > 1 { + for _, c := range cl { + if c.A.Name() == "body" { + ev.Contacts = cl + } + fmt.Printf("A: %v B: %v\n", c.A.Name(), c.B.Name()) + } + } + } + if len(ev.Contacts) > 1 { // turn around + fmt.Printf("hit wall: turn around!\n") + rot := 100.0 + 90.0*rand.Float32() + ev.Emer.Rel.RotateOnAxis(0, 1, 0, rot) + } + ev.View.UpdatePose() + ev.Snapshot() +} + // StepForward moves Emer forward in current facing direction one step, and takes Snapshot func (ev *Env) StepForward() { ev.Emer.Rel.MoveOnAxis(0, 0, 1, -ev.MoveStep) - ev.World.UpdateWorld() - ev.View.UpdatePose() - ev.Snapshot() + ev.WorldStep() } // StepBackward moves Emer backward in current facing direction one step, and takes Snapshot func (ev *Env) StepBackward() { ev.Emer.Rel.MoveOnAxis(0, 0, 1, ev.MoveStep) - ev.World.UpdateWorld() - ev.View.UpdatePose() - ev.Snapshot() + ev.WorldStep() } // RotBodyLeft rotates emer left and takes Snapshot func (ev *Env) RotBodyLeft() { ev.Emer.Rel.RotateOnAxis(0, 1, 0, ev.RotStep) - ev.World.UpdateWorld() - ev.View.UpdatePose() - ev.Snapshot() + ev.WorldStep() } // RotBodyRight rotates emer right and takes Snapshot func (ev *Env) RotBodyRight() { ev.Emer.Rel.RotateOnAxis(0, 1, 0, -ev.RotStep) - ev.World.UpdateWorld() - ev.View.UpdatePose() - ev.Snapshot() + ev.WorldStep() } // RotHeadLeft rotates emer left and takes Snapshot func (ev *Env) RotHeadLeft() { hd := ev.Emer.ChildByName("head", 1).(*eve.Group) hd.Rel.RotateOnAxis(0, 1, 0, ev.RotStep) - ev.World.UpdateWorld() - ev.View.UpdatePose() - ev.Snapshot() + ev.WorldStep() } // RotHeadRight rotates emer right and takes Snapshot func (ev *Env) RotHeadRight() { hd := ev.Emer.ChildByName("head", 1).(*eve.Group) hd.Rel.RotateOnAxis(0, 1, 0, -ev.RotStep) - ev.World.UpdateWorld() - ev.View.UpdatePose() - ev.Snapshot() + ev.WorldStep() } // MakeRoom constructs a new room in given parent group with given params @@ -204,6 +219,7 @@ func MakeEmer(par *eve.Group, height float32) *eve.Group { depth := height * .15 body := eve.AddNewBox(emr, "body", mat32.Vec3{0, height / 2, 0}, mat32.Vec3{width, height, depth}) body.Color = "purple" + body.SetDynamic() headsz := depth * 1.5 hhsz := .5 * headsz @@ -212,11 +228,14 @@ func MakeEmer(par *eve.Group, height float32) *eve.Group { head := eve.AddNewBox(hgp, "head", mat32.Vec3{0, 0, 0}, mat32.Vec3{headsz, headsz, headsz}) head.Color = "tan" + head.SetDynamic() eyesz := headsz * .2 eyel := eve.AddNewBox(hgp, "eye-l", mat32.Vec3{-hhsz * .6, headsz * .1, -(hhsz + eyesz*.3)}, mat32.Vec3{eyesz, eyesz * .5, eyesz * .2}) eyel.Color = "green" + eyel.SetDynamic() eyer := eve.AddNewBox(hgp, "eye-r", mat32.Vec3{hhsz * .6, headsz * .1, -(hhsz + eyesz*.3)}, mat32.Vec3{eyesz, eyesz * .5, eyesz * .2}) eyer.Color = "green" + eyer.SetDynamic() return emr } @@ -343,7 +362,7 @@ func (ev *Env) ConfigGui() { sv.SetStruct(ev) }) tbar.AddAction(gi.ActOpts{Label: "Init", Icon: "update", Tooltip: "Initialize virtual world -- go back to starting positions etc."}, win.This(), func(recv, send ki.Ki, sig int64, data interface{}) { - ev.InitWorld() + ev.WorldInit() }) tbar.AddAction(gi.ActOpts{Label: "Make", Icon: "update", Tooltip: "Re-make virtual world -- do this if you have changed any of the world parameters."}, win.This(), func(recv, send ki.Ki, sig int64, data interface{}) { ev.ReMakeWorld() diff --git a/go.mod b/go.mod index 136a06e..5bc3f69 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.13 require ( github.com/chewxy/math32 v1.0.4 - github.com/goki/gi v0.9.14 - github.com/goki/ki v0.9.12-0.20200309063305-3102412f93be - github.com/goki/mat32 v1.0.0 + github.com/goki/gi v0.9.17-0.20200330100829-eefdcbbdaa32 + github.com/goki/ki v0.9.13 + github.com/goki/mat32 v1.0.1 golang.org/x/tools v0.0.0-20200316182129-bd88ce97550a // indirect ) diff --git a/go.sum b/go.sum index 7b0bec5..aa469ce 100644 --- a/go.sum +++ b/go.sum @@ -44,22 +44,25 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a h1:yoAEv7yeWqfL/l9A/J5QOndXIJCldv+uuQB1DSNQbS0= github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= +github.com/go-python/gopy v0.3.1/go.mod h1:gQ2Itc84itA1AjrVqnMnv7HLkfmNObRXlR1co7CXpbk= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= -github.com/goki/gi v0.9.14 h1:szB8Kc+vUdT0UoiudHKx0TipFMifOvjFO4jxXCbTVLU= -github.com/goki/gi v0.9.14/go.mod h1:3zkwhFZluAyCNiKqDLHsk2UAVIUXOV9co2qMvgstwiQ= +github.com/goki/gi v0.9.17-0.20200330100829-eefdcbbdaa32 h1:oqHdMCmnYHHeWRmCZyrGLYMd7Xa3+9ihFXfOg6/Esxo= +github.com/goki/gi v0.9.17-0.20200330100829-eefdcbbdaa32/go.mod h1:jXyxBmu5NfdqzRiFz67EQS7bs+DXg9asNN1GCz1kk7I= github.com/goki/ki v0.9.11 h1:LOsRjJUuWTj8kgo30qyDoJPhk3Da6kVvclNfAz8nJMU= github.com/goki/ki v0.9.11/go.mod h1:/nsdYEYyX152+uLTOaHGxFBLoe1qcbkCpNVwAj5W1HU= -github.com/goki/ki v0.9.12-0.20200309063305-3102412f93be h1:dLFbi7ohTbYjzyVLB17MlH7+5v7odKobmjfQpzzD4dU= -github.com/goki/ki v0.9.12-0.20200309063305-3102412f93be/go.mod h1:/nsdYEYyX152+uLTOaHGxFBLoe1qcbkCpNVwAj5W1HU= -github.com/goki/mat32 v1.0.0 h1:KLQHIg6ufFRhTK4dL6G/LMVKovaw+yv+1hLleRE3hkY= -github.com/goki/mat32 v1.0.0/go.mod h1:SCqUsgLhG48i7wu/Kce9OF62abYJnxCX7YL5TOxZ0K4= -github.com/goki/pi v0.9.14 h1:gIXztX+wc3IN6HN9OScrOlmXdR634O9VY829xBPUBWY= -github.com/goki/pi v0.9.14/go.mod h1:LN118HwYbEmYPCOy6Csnpeb/opgxc1kdDmX7kf6VlrA= +github.com/goki/ki v0.9.13 h1:f/NQYTC0E+nMnS130K3oRxXGkAMdV9PhTZ/kkdAYR3o= +github.com/goki/ki v0.9.13/go.mod h1:/nsdYEYyX152+uLTOaHGxFBLoe1qcbkCpNVwAj5W1HU= +github.com/goki/mat32 v1.0.1 h1:S65NwaGnEN8usd8NjKQ2gFcGulu6PhHGIksF4LSeyYA= +github.com/goki/mat32 v1.0.1/go.mod h1:SCqUsgLhG48i7wu/Kce9OF62abYJnxCX7YL5TOxZ0K4= +github.com/goki/pi v0.9.16-0.20200327120350-557dd2a804bb h1:zZUrv5IRwvDO3Ou50mgSQMPP9HDGKPBF+3Rfglu589I= +github.com/goki/pi v0.9.16-0.20200327120350-557dd2a804bb/go.mod h1:I1v+twrG2JHtQo8c1aYx14EeBXStEqyJXLrIX1wX4Rc= github.com/goki/prof v0.0.0-20180502205428-54bc71b5d09b h1:3zU6niF8uvEaNtRBhOkmgbE/Fx7D6xuALotArTpycNc= github.com/goki/prof v0.0.0-20180502205428-54bc71b5d09b/go.mod h1:pgRizZOb3eUJr+ByZnXnPvt+a0fVOTn0Ujc2TqVZpW4= github.com/goki/vci v0.90.1 h1:50BIleTIDzkk0QM3BE36a3fBkEbZEkVqvCuSOyOghbk= github.com/goki/vci v0.90.1/go.mod h1:Dg6U9TS5Jtc4aw9hKudKkpDwmylO6W50d3v/CrjZDBA= +github.com/gonuts/commander v0.1.0/go.mod h1:qkb5mSlcWodYgo7vs8ulLnXhfinhZsZcm6+H/z1JjgY= +github.com/gonuts/flag v0.1.0/go.mod h1:ZTmTGtrSPejTo/SRNhCqwLTmiAgyBdCkLYhHrAoBdz4= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/h2non/filetype v1.0.12 h1:yHCsIe0y2cvbDARtJhGBTD2ecvqMSTvlIcph9En/Zao= @@ -118,6 +121,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 h1:gkI/wGGwpcG5W4hLCzZNGxA4wzWBGGDStRI1MrjDl2Q= golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=