Skip to content

Commit

Permalink
Merge pull request #214 from oakmound/feature/entities-with-explicit-…
Browse files Browse the repository at this point in the history
…children

Feature/entities with explicit children
  • Loading branch information
200sc committed Aug 23, 2022
2 parents aa57f92 + 7d11354 commit 8eb19bd
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 17 deletions.
10 changes: 10 additions & 0 deletions alg/floatgeom/polygon_test.go
Expand Up @@ -171,3 +171,13 @@ func BenchmarkPolygonConvexContains(b *testing.B) {
benchContains = poly.ConvexContains(x, y)
}
}

func Test_orient(t *testing.T) {
t.Parallel()
t.Run("3 empty points", func(t *testing.T) {
v := orient(Point2{}, Point2{}, Point2{})
if v != 0 {
t.Fatal("expected orient to return 0 for 3 empty points")
}
})
}
96 changes: 96 additions & 0 deletions alg/floatgeom/rect.go
@@ -1,5 +1,11 @@
package floatgeom

import (
"math/rand"

"github.com/oakmound/oak/v4/alg/span"
)

// A Rect2 represents a span from one point in 2D space to another.
// If Min is less than max on any axis, it will return undefined results
// for methods.
Expand Down Expand Up @@ -349,3 +355,93 @@ func (r Rect3) ProjectZ() Rect2 {
Max: r.Max.ProjectZ(),
}
}

// MulConst multiplies the boundary points of this rectangle by i.
func (r Rect2) MulConst(i float64) Rect2 {
return Rect2{
r.Min.MulConst(i),
r.Max.MulConst(i),
}
}

// Poll returns a pseudorandom point from within this rectangle
func (r Rect2) Poll() Point2 {
return Point2{
r.Min.X() + rand.Float64()*float64(r.W()),
r.Min.Y() + rand.Float64()*float64(r.H()),
}
}

// Clamp returns a version of the provided point such that it is contained within r. If it was already contained in
// r, it will not be changed.
func (r Rect2) Clamp(pt Point2) Point2 {
for i := 0; i < r.MaxDimensions(); i++ {
if pt[i] < r.Min[i] {
pt[i] = r.Min[i]
} else if pt[i] > r.Max[i] {
pt[i] = r.Max[i]
}
}
return pt
}

// Percentile returns a point within this rectangle along the vector from the top left to the bottom right of the
// rectangle, where for example, 0.0 will be r.Min, 1.0 will be r.Max, and 2.0 will be project the vector beyond r
// and return r.Min + {r.W()*2, r.H()*2}
func (r Rect2) Percentile(f float64) Point2 {
return Point2{
r.Min.X() + f*float64(r.W()),
r.Min.Y() + f*float64(r.H()),
}
}

// MulSpan returns this rectangle as a Point2 Span after multiplying the boundary points of the rectangle by f.
func (r Rect2) MulSpan(f float64) span.Span[Point2] {
return r.MulConst(f)
}

// MulConst multiplies the boundary points of this rectangle by i.
func (r Rect3) MulConst(i float64) Rect3 {
return Rect3{
r.Min.MulConst(i),
r.Max.MulConst(i),
}
}

// Poll returns a pseudorandom point from within this rectangle
func (r Rect3) Poll() Point3 {
return Point3{
r.Min.X() + (rand.Float64() * float64(r.W())),
r.Min.Y() + (rand.Float64() * float64(r.H())),
r.Min.Z() + (rand.Float64() * float64(r.D())),
}
}

// Clamp returns a version of the provided point such that it is contained within r. If it was already contained in
// r, it will not be changed.
func (r Rect3) Clamp(pt Point3) Point3 {
for i := 0; i < r.MaxDimensions(); i++ {
if pt[i] < r.Min[i] {
pt[i] = r.Min[i]
} else if pt[i] > r.Max[i] {
pt[i] = r.Max[i]
}
}
return pt
}

// Percentile returns a point within this rectangle along the vector from the top left to the bottom right of the
// rectangle, where for example, 0.0 will be r.Min, 1.0 will be r.Max, and 2.0 will be project the vector beyond r
// and return r.Min + {r.W()*2, r.H()*2, r.D()*2}
func (r Rect3) Percentile(f float64) Point3 {
return Point3{
r.Min.X() + (f * float64(r.W())),
r.Min.Y() + (f * float64(r.H())),
r.Min.Z() + (f * float64(r.D())),
}
}

// MulConst multiplies the boundary points of this rectangle by i.
func (r Rect3) MulSpan(f float64) span.Span[Point3] {
return r.MulConst(f)
}
64 changes: 64 additions & 0 deletions alg/floatgeom/rect_test.go
Expand Up @@ -269,3 +269,67 @@ func projectZHolds(x1, y1, z1, x2, y2, z2 float64) bool {
projected := r.ProjectZ()
return expected == projected
}

func TestRect2Span(t *testing.T) {
t.Run("Basic", func(t *testing.T) {
r := NewRect2WH(1, 1, 9, 9)
p1 := r.Percentile(1.0)
if p1 != r.Max {
t.Errorf("Percentile(1.0) did not return max point: got %v expected %v", p1, r.Max)
}
p2 := r.Percentile(0.0)
if p2 != r.Min {
t.Errorf("Percentile(0.0) did not return min point: got %v expected %v", p2, r.Min)
}
const pollTries = 100
for i := 0; i < pollTries; i++ {
if !r.Contains(r.Poll()) {
t.Fatalf("polled point did not lie within the creating rectangle")
}
}
p3 := r.Clamp(Point2{0, 5})
if p3 != (Point2{1, 5}) {
t.Errorf("Clamp(0,5) did not return {1,5}: got %v", p3)
}
p4 := r.Clamp(Point2{2, 11})
if p4 != (Point2{2, 10}) {
t.Errorf("Clamp(2,11) did not return {2,10}: got %v", p4)
}
r2 := r.MulSpan(4)
if r2 != NewRect2(4, 4, 40, 40) {
t.Errorf("MulSpan did not return {4,4,40,40}: got %v", r2)
}
})
}

func TestRect3Span(t *testing.T) {
t.Run("Basic", func(t *testing.T) {
r := NewRect3WH(1, 1, 1, 9, 9, 9)
p1 := r.Percentile(1.0)
if p1 != r.Max {
t.Errorf("Percentile(1.0) did not return max point: got %v expected %v", p1, r.Max)
}
p2 := r.Percentile(0.0)
if p2 != r.Min {
t.Errorf("Percentile(0.0) did not return min point: got %v expected %v", p2, r.Min)
}
const pollTries = 100
for i := 0; i < pollTries; i++ {
if !r.Contains(r.Poll()) {
t.Fatalf("polled point did not lie within the creating rectangle")
}
}
p3 := r.Clamp(Point3{0, -1, 5})
if p3 != (Point3{1, 1, 5}) {
t.Errorf("Clamp(0,-1,5) did not return {1,1,5}: got %v", p3)
}
p4 := r.Clamp(Point3{20, 2, 11})
if p4 != (Point3{10, 2, 10}) {
t.Errorf("Clamp(20, 2,11) did not return {10,2,10}: got %v", p4)
}
r2 := r.MulSpan(4)
if r2 != NewRect3(4, 4, 4, 40, 40, 40) {
t.Errorf("MulSpan did not return {4,4,4,40,40,40}: got %v", r2)
}
})
}
46 changes: 30 additions & 16 deletions entities/entity.go
Expand Up @@ -31,7 +31,8 @@ type Generator struct {
UseMouseTree bool
WithoutCollision bool

Children [][]Option
Children [][]Option
ExplicitChildren []*Entity
}

func And(opts ...Option) Option {
Expand All @@ -50,6 +51,13 @@ func WithChild(opts ...Option) Option {
}
}

func WithExplicitChild(e *Entity) Option {
return func(s Generator) Generator {
s.ExplicitChildren = append(s.ExplicitChildren, e)
return s
}
}

func WithRect(v floatgeom.Rect2) Option {
return func(s Generator) Generator {
s.Position = v.Min
Expand Down Expand Up @@ -135,6 +143,9 @@ func (e *Entity) Shift(delta floatgeom.Point2) {
e.X(), e.Y(), e.W(), e.H(), e.Space,
)
}
for _, c := range e.Children {
c.Shift(delta)
}
}

func (e *Entity) SetX(x float64) {
Expand All @@ -153,6 +164,9 @@ func (e *Entity) ShiftX(x float64) {
e.X(), e.Y(), e.W(), e.H(), e.Space,
)
}
for _, c := range e.Children {
c.ShiftX(x)
}
}

func (e *Entity) ShiftY(y float64) {
Expand All @@ -163,23 +177,17 @@ func (e *Entity) ShiftY(y float64) {
e.X(), e.Y(), e.W(), e.H(), e.Space,
)
}
for _, c := range e.Children {
c.ShiftY(y)
}
}

func (e *Entity) SetPos(p floatgeom.Point2) {
w, h := e.W(), e.H()
e.Rect = floatgeom.NewRect2WH(p.X(), p.Y(), w, h)
e.Renderable.SetPos(p.X(), p.Y())
if e.Tree != nil {
e.Tree.UpdateSpace(
e.X(), e.Y(), e.W(), e.H(), e.Space,
)
}
e.Shift(p.Sub(e.Rect.Min))
}

// TODO: take a point, not floats
func (e *Entity) ShiftPos(x, y float64) {
p := e.Rect.Min
e.SetPos(p.Add(floatgeom.Point2{x, y}))
e.Shift(floatgeom.Point2{x, y})
}

func (e *Entity) HitLabel(label collision.Label) *collision.Space {
Expand Down Expand Up @@ -214,11 +222,16 @@ func New(ctx *scene.Context, opts ...Option) *Entity {
g = o(g)
}

children := make([]*Entity, len(g.Children))
children := make([]*Entity, len(g.Children)+len(g.ExplicitChildren))
for i, childOpts := range g.Children {
childOpts = append(childOpts, WithOffset(g.Position))
children[i] = New(ctx, childOpts...)
}
for i, explicitChild := range g.ExplicitChildren {
child := explicitChild
child.ShiftPos(g.Position.X(), g.Position.Y())
children[i+len(g.Children)] = child
}

e := &Entity{
ctx: ctx,
Expand All @@ -240,8 +253,9 @@ func New(ctx *scene.Context, opts ...Option) *Entity {
if m, isMod := e.Renderable.(render.Modifiable); g.Mod != nil && isMod {
e.Renderable = m.Modify(g.Mod)
}

e.Renderable.SetPos(e.X(), e.Y())
if e.Renderable != nil {
e.Renderable.SetPos(e.X(), e.Y())
}

if g.Parent == nil {
cid := ctx.CallerMap.Register(e)
Expand All @@ -265,7 +279,7 @@ func New(ctx *scene.Context, opts ...Option) *Entity {
e.Tree.Add(e.Space)
}

if len(g.DrawLayers) != 0 {
if len(g.DrawLayers) != 0 && e.Renderable != nil {
ctx.Draw(e.Renderable, g.DrawLayers...)
}

Expand Down
3 changes: 3 additions & 0 deletions render/particle/generator.go
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/oakmound/oak/v4/alg/span"
"github.com/oakmound/oak/v4/physics"
"github.com/oakmound/oak/v4/render"
)

var (
Expand All @@ -29,6 +30,7 @@ type Generator interface {
// Modeled after Parcycle
type BaseGenerator struct {
physics.Vector
DrawStack *render.DrawStack
// This float is currently forced to an integer
// at new particle rotation. This should be changed
// to something along the lines of 'new per 30 frames',
Expand Down Expand Up @@ -68,6 +70,7 @@ func (bg *BaseGenerator) GetBaseGenerator() *BaseGenerator {
func (bg *BaseGenerator) setDefaults() {
*bg = BaseGenerator{
Vector: physics.NewVector(0, 0),
DrawStack: nil,
NewPerFrame: span.NewConstant(1.0),
LifeSpan: span.NewConstant(60.0),
Angle: span.NewConstant(0.0),
Expand Down
8 changes: 8 additions & 0 deletions render/particle/options.go
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/oakmound/oak/v4/alg"
"github.com/oakmound/oak/v4/alg/span"
"github.com/oakmound/oak/v4/physics"
"github.com/oakmound/oak/v4/render"
)

// And chains together particle options into a single option
Expand Down Expand Up @@ -116,3 +117,10 @@ func Limit(limit int) func(Generator) {
g.GetBaseGenerator().ParticleLimit = limit
}
}

// DrawStack sets the current drawstack so that we dont use the globaldrawstack
func DrawStack(drawStack *render.DrawStack) func(Generator) {
return func(g Generator) {
g.GetBaseGenerator().DrawStack = drawStack
}
}
6 changes: 5 additions & 1 deletion render/particle/source.go
Expand Up @@ -177,7 +177,11 @@ func (ps *Source) addParticles() {
ps.particles[ps.nextPID] = p
ps.nextPID++
p.SetLayer(ps.Layer(bp.GetPos()))
render.Draw(p, ps.stackLevel)
if pg.DrawStack == nil {
render.Draw(p, ps.stackLevel)
} else {
pg.DrawStack.Draw(p, ps.stackLevel)
}
}

}
Expand Down

0 comments on commit 8eb19bd

Please sign in to comment.