diff --git a/cpu_evaluators.go b/cpu_evaluators.go index 97d1c80..0732e99 100644 --- a/cpu_evaluators.go +++ b/cpu_evaluators.go @@ -596,7 +596,7 @@ func (t *equilateralTri2d) Evaluate(pos []ms2.Vec, dist []float32, userData any) } func (c *rect2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error { - b := c.d + b := ms2.Scale(0.5, c.d) for i, p := range pos { d := ms2.Sub(ms2.AbsElem(p), b) dist[i] = ms2.Norm(ms2.MaxElem(d, ms2.Vec{})) + math32.Min(0, math32.Max(d.X, d.Y)) @@ -907,3 +907,54 @@ func (s *annulus2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error } return nil } + +func (c *circarray2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error { + vp, err := gleval.GetVecPool(userData) + if err != nil { + return err + } + sdf, err := gleval.AssertSDF2(c.s) + if err != nil { + return err + } + pos0 := vp.V2.Acquire(len(pos)) + pos1 := vp.V2.Acquire(len(pos)) + defer vp.V2.Release(pos0) + defer vp.V2.Release(pos1) + + angle := 2 * math32.Pi / float32(c.circleDiv) + ncirc := float32(c.circleDiv) + ninsm1 := float32(c.nInst - 1) + for i, p := range pos { + pangle := math32.Atan2(p.Y, p.X) + id := math32.Floor(pangle / angle) + if id < 0 { + id += ncirc + } + var i0, i1 float32 + if id >= ninsm1 { + i0 = ninsm1 + i1 = 0 + } else { + i0 = id + i1 = id + 1 + } + pos0[i] = ms2.MulMatVecTrans(ms2.RotationMat2(angle*i0), p) + pos1[i] = ms2.MulMatVecTrans(ms2.RotationMat2(angle*i1), p) + } + + dist1 := vp.Float.Acquire(len(pos)) + defer vp.Float.Release(dist1) + err = sdf.Evaluate(pos1, dist1, userData) + if err != nil { + return err + } + err = sdf.Evaluate(pos0, dist, userData) + if err != nil { + return err + } + for i, d := range dist { + dist[i] = math32.Min(d, dist1[i]) + } + return nil +} diff --git a/examples/test/glsdf3test.go b/examples/test/glsdf3test.go index 5daed27..d4086fa 100644 --- a/examples/test/glsdf3test.go +++ b/examples/test/glsdf3test.go @@ -114,6 +114,7 @@ var _ = npt.SetFromNominal(1.0 / 2.0) var PremadePrimitives2D = []glbuild.Shader2D{ mustShader2D(gsdf.NewLine2D(-2.3, 1, 13, 12, .1)), + mustShader2D(gsdf.NewRectangle(1, 2)), mustShader2D(gsdf.NewArc(2.3, math.Pi/3, 0.1)), mustShader2D(gsdf.NewCircle(1)), mustShader2D(gsdf.NewHexagon(1)), @@ -121,6 +122,7 @@ var PremadePrimitives2D = []glbuild.Shader2D{ {X: -1, Y: -1}, {X: -1, Y: 0}, {X: 0.5, Y: 2}, })), mustShader2D(npt.Thread()), + // mustShader2D(gsdf.NewEllipse(1, 2)), // Ellipse seems to be very sensitive to position. } var BinaryOps = []func(a, b glbuild.Shader3D) glbuild.Shader3D{ @@ -143,7 +145,7 @@ var SmoothBinaryOps = []func(k float32, a, b glbuild.Shader3D) glbuild.Shader3D{ gsdf.SmoothIntersect, } -var OtherUnaryRandomizedOps = []func(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D{ +var UnaryRandomizedOps = []func(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D{ randomRotation, randomShell, randomElongate, @@ -154,6 +156,10 @@ var OtherUnaryRandomizedOps = []func(a glbuild.Shader3D, rng *rand.Rand) glbuild // randomArray, // round() differs from go's math.Round() } +var UnaryRandomizedOps2D = []func(a glbuild.Shader2D, rng *rand.Rand) glbuild.Shader2D{ + // randomCircArray2D, +} + var OtherUnaryRandomizedOps2D3D = []func(a glbuild.Shader2D, rng *rand.Rand) glbuild.Shader3D{ randomExtrude, randomRevolve, @@ -305,7 +311,7 @@ func test_sdf_gpu_cpu() error { } } rng := rand.New(rand.NewSource(1)) - for _, op := range OtherUnaryRandomizedOps { + for _, op := range UnaryRandomizedOps { log.Printf("begin evaluating %s\n", getFnName(op)) for i := 0; i < 50; i++ { primitive := PremadePrimitives[rng.Intn(len(PremadePrimitives))] @@ -345,6 +351,47 @@ func test_sdf_gpu_cpu() error { } } } + for _, op := range UnaryRandomizedOps2D { + log.Printf("begin evaluating %s\n", getFnName(op)) + for i := 0; i < 50; i++ { + primitive := PremadePrimitives2D[rng.Intn(len(PremadePrimitives2D))] + obj := op(primitive, rng) + bounds := obj.Bounds() + pos := appendMeshgrid2D(scratchPos2[:0], bounds, nx, ny) + distCPU := scratchDistCPU[:len(pos)] + distGPU := scratchDistGPU[:len(pos)] + sdfcpu, err := gleval.AssertSDF2(obj) + if err != nil { + return err + } + err = sdfcpu.Evaluate(pos, distCPU, vp) + if err != nil { + return err + } + sdfgpu := makeGPUSDF2(obj) + err = sdfgpu.Evaluate(pos, distGPU, nil) + if err != nil { + return err + } + if getBaseTypename(primitive) == "poly2d" || + (getBaseTypename(primitive) == "tri" && getFnName(op) == "randomRotation") { + log.Println("omit 2d dist checks") + continue + } + err = cmpDist(pos, distCPU, distGPU) + if err != nil { + description := sprintOpPrimitive(op, primitive) + return fmt.Errorf("%d %s: %s", i, description, err) + } + // log.Printf("allocated v3=%dMB v2=%dMB f32=%dMB", vp.V3.TotalAlloc()/MB, vp.V2.TotalAlloc()/MB, vp.Float.TotalAlloc()/MB) + + // err = test_bounds(sdfcpu, scratchDist, vp) + // if err != nil { + // description := sprintOpPrimitive(op, primitive) + // return fmt.Errorf("%s: %s", description, err) + // } + } + } for _, op := range OtherUnaryRandomizedOps2D3D { log.Printf("begin evaluating %s\n", getFnName(op)) @@ -530,7 +577,7 @@ func test_union3D() error { } func test_polygongpu() error { - const Nvertices = 1600 + const Nvertices = 13 var polybuilder ms2.PolygonBuilder polybuilder.Nagon(Nvertices, 2) vecs, err := polybuilder.AppendVecs(nil) @@ -919,6 +966,16 @@ func cmpDist[T any](pos []T, dcpu, dgpu []float32) error { return mismatchErr } +func randomCircArray2D(a glbuild.Shader2D, rng *rand.Rand) glbuild.Shader2D { + circleDiv := rng.Intn(16) + 3 + nInst := rng.Intn(circleDiv) + 1 + s, err := gsdf.CircularArray2D(a, nInst, circleDiv) + if err != nil { + panic(err) + } + return s +} + func randomRotation(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D { var axis ms3.Vec for ms3.Norm(axis) < .5 { diff --git a/gsdf2d.go b/gsdf2d.go index 5c0cdc0..5ae82a1 100644 --- a/gsdf2d.go +++ b/gsdf2d.go @@ -286,8 +286,12 @@ func NewRectangle(x, y float32) (glbuild.Shader2D, error) { } func (c *rect2D) Bounds() ms2.Box { - min := ms2.Scale(-0.5, c.d) - return ms2.Box{Min: min, Max: ms2.AbsElem(min)} + xd2 := c.d.X / 2 + yd2 := c.d.Y / 2 + return ms2.Box{ + Min: ms2.Vec{X: -xd2, Y: -yd2}, + Max: ms2.Vec{X: xd2, Y: yd2}, + } } func (c *rect2D) AppendShaderName(b []byte) []byte { @@ -298,7 +302,7 @@ func (c *rect2D) AppendShaderName(b []byte) []byte { } func (c *rect2D) AppendShaderBody(b []byte) []byte { - b = glbuild.AppendVec2Decl(b, "b", c.d) + b = glbuild.AppendVec2Decl(b, "b", ms2.Scale(0.5, c.d)) b = append(b, `vec2 d = abs(p)-b; return length(max(d,0.0)) + min(max(d.x,d.y),0.0);`...) return b @@ -993,3 +997,80 @@ func (s *annulus2D) AppendShaderBody(b []byte) []byte { b = append(b, "return abs(d)-r;"...) return b } + +// CircularArray2 is the circular domain repetition operation around the origin (x,y)=(0,0). +// It repeats the shape numInstances times and the spacing angle is defined by circleDiv such that angle = 2*pi/circleDiv. +// The operation is defined this way so that the argument shape is evaluated only twice per circular array evaluation, regardless of instances. +func CircularArray2D(s glbuild.Shader2D, numInstances, circleDiv int) (glbuild.Shader2D, error) { + if circleDiv <= 1 || numInstances <= 0 { + return nil, errors.New("invalid circarray repeat param") + } else if s == nil { + return nil, errors.New("nil argument to circarray") + } else if numInstances > circleDiv { + return nil, errors.New("bad circular array instances, must be less than circleDiv") + } + return &circarray2D{s: s, circleDiv: circleDiv, nInst: numInstances}, nil +} + +type circarray2D struct { + s glbuild.Shader2D + nInst int + circleDiv int +} + +func (ca *circarray2D) Bounds() ms2.Box { + bb := ca.s.Bounds() + verts := bb.Vertices() + angle := 2 * math.Pi / float32(ca.circleDiv) + m := ms2.RotationMat2(angle) + for i := 0; i < ca.nInst-1; i++ { + for i := range verts { + verts[i] = ms2.MulMatVec(m, verts[i]) + bb = bb.IncludePoint(verts[i]) + } + } + return bb +} + +func (ca *circarray2D) ForEach2DChild(userData any, fn func(userData any, s *glbuild.Shader2D) error) error { + return fn(userData, &ca.s) +} + +// func (ca *circarray2D) angle() float32 { return 2 * math32.Pi / float32(ca.n) } + +func (ca *circarray2D) AppendShaderName(b []byte) []byte { + b = append(b, "circarray2D2d"...) + b = glbuild.AppendFloats(b, 0, 'n', 'p', float32(ca.nInst), float32(ca.circleDiv)) + b = append(b, '_') + b = ca.s.AppendShaderName(b) + return b +} + +func (ca *circarray2D) AppendShaderBody(b []byte) []byte { + angle := 2 * math.Pi / float32(ca.circleDiv) + b = glbuild.AppendFloatDecl(b, "ncirc", float32(ca.circleDiv)) + b = glbuild.AppendFloatDecl(b, "angle", angle) + b = glbuild.AppendFloatDecl(b, "ninsm1", float32(ca.nInst-1)) + b = append(b, `float pangle=atan(p.y, p.x); + float i=floor(pangle/angle); + if (i<0.0) i=ncirc+i; + float i0,i1; + if (i>=ninsm1) { + i0=ninsm1; + i1=0.0; + } else { + i0=i; + i1=i+1.0; + } + float c0 = cos(angle*i0); + float s0 = sin(angle*i0); + vec2 p0 = mat2(c0,-s0,s0,c0)*p; + float c1 = cos(angle*i1); + float s1 = sin(angle*i1); + vec2 p1 = mat2(c1,-s1,s1,c1)*p; + `...) + b = glbuild.AppendDistanceDecl(b, "d0", "p0", ca.s) + b = glbuild.AppendDistanceDecl(b, "d1", "p1", ca.s) + b = append(b, "return min(d0, d1);"...) + return b +}