Skip to content

Commit

Permalink
fix smoothDiff, shell dimension consistency, add more test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Aug 15, 2024
1 parent 214ee04 commit 9421e98
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 35 deletions.
18 changes: 14 additions & 4 deletions cpu_evaluators.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func (u *smoothDiff) Evaluate(pos []ms3.Vec, dist []float32, userData any) error
for i := range dist {
a, b := d1[i], d2[i]
h := clampf(0.5-0.5*(b+a)/k, 0, 1)
dist[i] = mixf(b, -a, h) + k*h*(1-h)
dist[i] = mixf(a, -b, h) + k*h*(1-h)
}
return nil
}
Expand Down Expand Up @@ -425,17 +425,27 @@ func (e *elongate) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
}

func (sh *shell) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
vp, err := gleval.GetVecPool(userData)
if err != nil {
return err
}
auxpos := vp.V3.Acquire(len(pos))
defer vp.V3.Release(auxpos)
thickness := sh.thick
for i, p := range pos {
auxpos[i] = ms3.Scale(1/thickness, p)
}
sdf, err := gleval.AssertSDF3(sh.s)
if err != nil {
return err
}
err = sdf.Evaluate(pos, dist, userData)
err = sdf.Evaluate(auxpos, dist, userData)
if err != nil {
return err
}
thickness := sh.thick

for i, d := range dist {
dist[i] = absf(d) - thickness
dist[i] = thickness * (absf(d) - thickness)
}
return nil
}
Expand Down
104 changes: 85 additions & 19 deletions examples/test/glsdf3test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"errors"
"fmt"
"log"
"math"
"math/rand"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"

"github.com/chewxy/math32"
Expand Down Expand Up @@ -63,10 +63,10 @@ var PremadePrimitives = []glbuild.Shader3D{
mustShader(gsdf.NewSphere(1)),
mustShader(gsdf.NewBoxFrame(1, 1.2, 2.2, .2)),
mustShader(gsdf.NewBox(1, 1.2, 2.2, 0.3)),
// mustShader(gsdf.NewTorus(3, .5)), // Negative normal?
mustShader(gsdf.NewTorus(3, .5)), // Negative normal?
mustShader(gsdf.NewTriangularPrism(1, 3)),
mustShader(gsdf.NewCylinder(1, 3, .1)),
mustShader(threads.Screw(5, threads.ISO{
mustShader(threads.Screw(5, threads.ISO{ // Negative normal.
D: 1,
P: 0.1,
Ext: true,
Expand Down Expand Up @@ -213,6 +213,11 @@ func test_sdf_gpu_cpu() error {
description := sprintOpPrimitive(op, p1, p2)
return fmt.Errorf("%s: %s", description, err)
}
err = test_bounds(sdfcpu, scratchDist, vp)
if err != nil {
description := sprintOpPrimitive(op, p1, p2)
return fmt.Errorf("%s: %s", description, err)
}
}
rng := rand.New(rand.NewSource(1))
for _, op := range OtherUnaryRandomizedOps {
Expand Down Expand Up @@ -242,6 +247,16 @@ func test_sdf_gpu_cpu() error {
description := sprintOpPrimitive(op, primitive)
return fmt.Errorf("%d %s: %s", i, description, err)
}
if getBaseTypename(primitive) == "screw" ||
(getBaseTypename(primitive) == "tri" && getFnName(op) == "randomRotation") {
log.Println("omit screw unary testbounds checks")
continue
}
err = test_bounds(sdfcpu, scratchDist, vp)
if err != nil {
description := sprintOpPrimitive(op, primitive)
return fmt.Errorf("%s: %s", description, err)
}
}
}
for _, op := range OtherUnaryRandomizedOps2D3D {
Expand Down Expand Up @@ -347,26 +362,31 @@ func test_visualizer_generation() error {
P: 0.01,
Ext: true,
})

// shape, err := gsdf.NewCylinder(r, 2*r, r/8)
if err != nil {
return err
}
s = gsdf.Union(s, shape)
return visualize(s, filename)
}

// s = gsdf.Union(s, box)
envelope, err := gsdf.NewBoundsBoxFrame(shape.Bounds())
// visualize facilitates the SDF visualization.
func visualize(sdf glbuild.Shader3D, filename string) error {
bb := sdf.Bounds()
envelope, err := gsdf.NewBoundsBoxFrame(bb)
if err != nil {
return err
}
s = gsdf.Union(s, envelope)
s, _ = gsdf.Rotate(s, math.Pi/2, ms3.Vec{X: 1})
sdf = gsdf.Union(sdf, envelope)
fp, err := os.Create(filename)
if err != nil {
return err
}
defer fp.Close()
written, err := programmer.WriteFragVisualizerSDF3(fp, gsdf.Scale(s, 4))

const desiredScale = 2.0
diag := ms3.Norm(bb.Size())
sdf = gsdf.Scale(sdf, desiredScale/diag)
written, err := programmer.WriteFragVisualizerSDF3(fp, sdf)
if err != nil {
return err
}
Expand All @@ -378,11 +398,36 @@ func test_visualizer_generation() error {
if int64(written) != size {
return fmt.Errorf("written (%d) vs filesize (%d) mismatch", written, size)
}
log.Println("PASS visualizer generation")
return nil
}

func test_bounds(sdf gleval.SDF3, scratchDist []float32, userData any) error {
func test_bounds(sdf gleval.SDF3, scratchDist []float32, userData any) (err error) {
typename := getBaseTypename(sdf)
shader := sdf.(glbuild.Shader3D)
shader.ForEachChild(nil, func(userData any, s *glbuild.Shader3D) error {
ss := *s
typename += "|" + getBaseTypename(ss)
ss.ForEachChild(userData, func(userData any, s *glbuild.Shader3D) error {
typename += "|" + getBaseTypename(*s)
return nil
})
return nil
})

var skipNormCheck bool
skipNormCheck = skipNormCheck || strings.Contains(typename, "screw")
skipNormCheck = skipNormCheck || strings.Contains(typename, "torus")
skipNormCheck = skipNormCheck || strings.Contains(typename, "smoothDiff")
if skipNormCheck {
log.Println("omit normal checks for", typename)
}
defer func() {
if err != nil {
filename := "testboundfail_" + typename + ".glsl"
log.Println("test_bounds failed: generating visualization aid file", filename)
visualize(shader, filename)
}
}()
const nxbb, nybb, nzbb = 16, 16, 16
const ndim = nxbb * nybb * nzbb
const eps = 1e-2
Expand All @@ -406,7 +451,7 @@ func test_bounds(sdf gleval.SDF3, scratchDist []float32, userData any) error {
// Calculate approximate expected normal directions.
wantNormals := make([]ms3.Vec, len(originalPos))
wantNormals = appendMeshgrid(wantNormals[:0], bb.Add(ms3.Scale(-1, bb.Center())), nxbb, nybb, nzbb)

var normOmitLog sync.Once
var offsize ms3.Vec
for _, xo := range offs {
offsize.X = xo * (size.X + eps)
Expand All @@ -422,7 +467,7 @@ func test_bounds(sdf gleval.SDF3, scratchDist []float32, userData any) error {
newPos = appendMeshgrid(newPos[:0], newBB, nxbb, nybb, nzbb)
// Calculate expected normal directions.

err := sdf.Evaluate(newPos, dist, userData)
err = sdf.Evaluate(newPos, dist, userData)
if err != nil {
return err
}
Expand All @@ -435,14 +480,23 @@ func test_bounds(sdf gleval.SDF3, scratchDist []float32, userData any) error {
if err != nil {
return err
}
if skipNormCheck {
continue
}
switch typename {
case "screw", "torus", "smoothDiff":
normOmitLog.Do(func() {})
continue // Skip certain shapes for normal calculation. Bad conditioning?
}
for i, got := range normals {
want := ms3.Add(offsize, wantNormals[i])
got = ms3.Unit(got)
angle := ms3.Cos(got, want)
if angle < math32.Sqrt2/2 {
msg := fmt.Sprintf("bad norm p=%v got %v, want %v -> angle=%f off=%v bb=%+v", newPos[i], got, want, angle, offsize, newBB)
msg := fmt.Sprintf("bad norm angle %frad p=%v got %v, want %v -> off=%v bb=%+v", angle, newPos[i], got, want, offsize, newBB)
if angle <= 0 {
return errors.New(msg) // Definitely have a surface outside of the bounding box.
err = errors.New(msg)
return err //errors.New(msg) // Definitely have a surface outside of the bounding box.
} else {
// fmt.Println("WARN bad normal:", msg) // Is this possible with a surface contained within the bounding box? Maybe an ill-conditioned/pointy surface?
}
Expand Down Expand Up @@ -548,11 +602,23 @@ func randomRotation(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
}

func randomShell(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
thickness := rng.Float32()
bb := a.Bounds()
size := bb.Size()
maxSize := bb.Size().Max() / 128
thickness := math32.Min(maxSize, rng.Float32())
if thickness <= 1e-8 {
thickness = rng.Float32()
thickness = math32.Min(maxSize, rng.Float32())
}
return gsdf.Shell(a, thickness)
shell := gsdf.Shell(a, thickness)
// Cut shell to visualize interior.

center := bb.Center()
bb.Max.Y = center.Y

halfbox, _ := gsdf.NewBox(size.X*20, size.Y/3, size.Z*20, 0)
halfbox = gsdf.Translate(halfbox, 0, size.Y/3, 0)
fmt.Println("thick", thickness, maxSize)
return gsdf.Difference(shell, halfbox)
}

func randomArray(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
Expand Down
2 changes: 1 addition & 1 deletion gleval/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
//
// The returned [gleval.SDF3] should only require a [gleval.VecPool] as a userData argument,
// this is automatically taken care of if a nil userData is passed in.
func NewCPUSDF3(root bounder3) (SDF3, error) {
func NewCPUSDF3(root bounder3) (*SDF3CPU, error) {
sdf, err := AssertSDF3(root)
if err != nil {
return nil, fmt.Errorf("top level SDF cannot be CPU evaluated: %s", err.Error())
Expand Down
10 changes: 5 additions & 5 deletions gleval/gpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

// NewComputeGPUSDF3 instantiates a SDF3 that runs on the GPU.
func NewComputeGPUSDF3(glglSourceCode io.Reader, bb ms3.Box) (SDF3, error) {
func NewComputeGPUSDF3(glglSourceCode io.Reader, bb ms3.Box) (*SDF3Compute, error) {
combinedSource, err := glgl.ParseCombined(glglSourceCode)
if err != nil {
return nil, err
Expand All @@ -21,23 +21,23 @@ func NewComputeGPUSDF3(glglSourceCode io.Reader, bb ms3.Box) (SDF3, error) {
if err != nil {
return nil, errors.New(string(combinedSource.Compute) + "\n" + err.Error())
}
sdf := computeSDF{
sdf := SDF3Compute{
prog: glprog,
bb: bb,
}
return &sdf, nil
}

type computeSDF struct {
type SDF3Compute struct {
prog glgl.Program
bb ms3.Box
}

func (sdf *computeSDF) Bounds() ms3.Box {
func (sdf *SDF3Compute) Bounds() ms3.Box {
return sdf.bb
}

func (sdf *computeSDF) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
func (sdf *SDF3Compute) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
sdf.prog.Bind()
defer sdf.prog.Unbind()
posCfg := glgl.TextureImgConfig{
Expand Down
16 changes: 10 additions & 6 deletions operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ func (s *smoothDiff) AppendShaderBody(b []byte) []byte {
b = appendDistanceDecl(b, s.s2, "d2", "p")
b = appendFloatDecl(b, "k", s.k)
b = append(b, `float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
return mix( d2, -d1, h ) + k*h*(1.0-h);`...)
return mix( d1, -d2, h ) + k*h*(1.0-h);`...)
return b
}

Expand Down Expand Up @@ -592,7 +592,12 @@ type shell struct {
}

func (u *shell) Bounds() ms3.Box {
return u.s.Bounds()
bb := u.s.Bounds()
return bb
return ms3.Box{
Min: ms3.Sub(bb.Min, ms3.Vec{u.thick, u.thick, u.thick}),
Max: ms3.Add(bb.Max, ms3.Vec{u.thick, u.thick, u.thick}),
}
}

func (s *shell) ForEachChild(userData any, fn func(userData any, s *glbuild.Shader3D) error) error {
Expand All @@ -608,11 +613,10 @@ func (s *shell) AppendShaderName(b []byte) []byte {
}

func (s *shell) AppendShaderBody(b []byte) []byte {
b = append(b, "return abs("...)
b = appendFloatDecl(b, "t", s.thick)
b = append(b, "return t*(abs("...)
b = s.s.AppendShaderName(b)
b = append(b, "(p))-"...)
b = fappend(b, s.thick, '-', '.')
b = append(b, ';')
b = append(b, "(p/t))-t);"...)
return b
}

Expand Down

0 comments on commit 9421e98

Please sign in to comment.