Skip to content

Commit 34de309

Browse files
committed
add arc and line
1 parent 70320aa commit 34de309

File tree

6 files changed

+166
-17
lines changed

6 files changed

+166
-17
lines changed

cpu_evaluators.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (bf *boxframe) Evaluate(pos []ms3.Vec, dist []float32, userData any) error
4949

5050
func (t *torus) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
5151
t1 := t.rGreater
52-
t2 := t.rRing
52+
t2 := t.rLesser
5353
for i, p := range pos {
5454
p = ms3.Vec{X: p.X, Y: p.Z, Z: p.Y}
5555
q := ms2.Vec{X: hypotf(p.X, p.Z) - t1, Y: p.Y}
@@ -538,6 +538,36 @@ func (e *revolution) Evaluate(pos []ms3.Vec, dist []float32, userData any) error
538538
return sdf.Evaluate(pos2, dist, userData)
539539
}
540540

541+
func (l *line2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
542+
a := l.a
543+
ba := ms2.Sub(l.b, l.a)
544+
dotba := ms2.Dot(ba, ba)
545+
t := l.thick / 2
546+
for i, p := range pos {
547+
pa := ms2.Sub(p, a)
548+
h := ms1.Clamp(ms2.Dot(pa, ba)/dotba, 0, 1)
549+
dist[i] = ms2.Norm(ms2.Sub(pa, ms2.Scale(h, ba))) - t
550+
}
551+
return nil
552+
}
553+
554+
func (a *arc2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
555+
r := a.radius
556+
t := a.thick / 2
557+
s, c := math32.Sincos(a.angle / 2)
558+
sc := ms2.Vec{X: s, Y: c}
559+
scr := ms2.Scale(r, sc)
560+
for i, p := range pos {
561+
p.X = math32.Abs(p.X)
562+
if sc.Y*p.X > sc.X*p.Y {
563+
dist[i] = ms2.Norm(ms2.Sub(p, scr)) - t
564+
} else {
565+
dist[i] = math32.Abs(ms2.Norm(p)-r) - t
566+
}
567+
}
568+
return nil
569+
}
570+
541571
func (c *circle2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
542572
r := c.r
543573
for i, p := range pos {

examples/test/glsdf3test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"log"
8+
"math"
89
"math/rand"
910
"os"
1011
"reflect"
@@ -77,6 +78,8 @@ var npt threads.NPT
7778
var _ = npt.SetFromNominal(1.0 / 2.0)
7879

7980
var PremadePrimitives2D = []glbuild.Shader2D{
81+
mustShader2D(gsdf.NewLine2D(-2.3, 1, 13, 12, .1)),
82+
mustShader2D(gsdf.NewArc(2.3, math.Pi/3, 0.1)),
8083
mustShader2D(gsdf.NewCircle(1)),
8184
mustShader2D(gsdf.NewHexagon(1)),
8285
mustShader2D(gsdf.NewPolygon([]ms2.Vec{

gsdf2d.go

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,103 @@ import (
1212
"github.com/soypat/gsdf/glbuild"
1313
)
1414

15+
// NewLine2D creates a straight line between (x0,y0) and (x1,y1) with a given thickness.
16+
func NewLine2D(x0, y0, x1, y1, thick float32) (glbuild.Shader2D, error) {
17+
hasNaN := math32.IsNaN(x0) || math32.IsNaN(y0) || math32.IsNaN(x1) || math32.IsNaN(y1) || math32.IsNaN(thick)
18+
if hasNaN {
19+
return nil, errors.New("NaN argument to NewLine2D")
20+
} else if thick < 0 {
21+
return nil, errors.New("negative thickness to NewLine2D")
22+
}
23+
return &line2D{a: ms2.Vec{X: x0, Y: y0}, b: ms2.Vec{X: x1, Y: y1}, thick: thick}, nil
24+
}
25+
26+
type line2D struct {
27+
thick float32
28+
a, b ms2.Vec
29+
}
30+
31+
func (l *line2D) Bounds() ms2.Box {
32+
b := ms2.Box{Min: l.a, Max: l.b}.Canon()
33+
b.Max = ms2.AddScalar(l.thick, b.Max)
34+
b.Min = ms2.AddScalar(-l.thick, b.Min)
35+
return b
36+
}
37+
38+
func (l *line2D) AppendShaderName(b []byte) []byte {
39+
b = append(b, "line"...)
40+
b = glbuild.AppendFloats(b, 0, 'n', 'p', l.a.X, l.a.Y, l.b.X, l.b.Y, l.thick)
41+
return b
42+
}
43+
44+
func (l *line2D) AppendShaderBody(b []byte) []byte {
45+
b = glbuild.AppendVec2Decl(b, "a", l.a)
46+
b = glbuild.AppendVec2Decl(b, "ba", ms2.Sub(l.b, l.a))
47+
b = glbuild.AppendFloatDecl(b, "t", l.thick/2)
48+
b = append(b, `vec2 pa=p-a;
49+
float h=clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
50+
return length(pa-ba*h)-t;`...)
51+
return b
52+
}
53+
54+
func (l *line2D) ForEach2DChild(userData any, fn func(userData any, s *glbuild.Shader2D) error) error {
55+
return nil
56+
}
57+
58+
// NewArc returns a 2D arc centered at the origin (x,y)=(0,0) for a given radius and arc angle and thickness of the arc.
59+
// The arc begins opening at (x,y)=(0,r) in both positive and negative x direction.
60+
func NewArc(radius, arcAngle, thick float32) (glbuild.Shader2D, error) {
61+
ok := radius > 0 && arcAngle > 0 && thick >= 0
62+
if !ok {
63+
return nil, errors.New("invalid argument to NewArc2D")
64+
} else if arcAngle > 2*math.Pi {
65+
return nil, errors.New("arc angle exceeds full circle")
66+
} else if 2*math.Pi-arcAngle < 0.5e-6 {
67+
arcAngle = 2*math.Pi - 1e-7 // Condition the arc to be closed.
68+
}
69+
return &arc2D{radius: radius, angle: arcAngle, thick: thick}, nil
70+
}
71+
72+
type arc2D struct {
73+
radius float32
74+
angle float32
75+
thick float32
76+
}
77+
78+
func (a *arc2D) Bounds() ms2.Box {
79+
r := a.radius + a.thick
80+
rcos := a.radius*math32.Cos(a.angle/2) - a.thick
81+
return ms2.Box{
82+
Min: ms2.Vec{X: -r, Y: rcos},
83+
Max: ms2.Vec{X: r, Y: r},
84+
}
85+
}
86+
87+
func (a *arc2D) AppendShaderName(b []byte) []byte {
88+
b = append(b, "arc"...)
89+
b = glbuild.AppendFloats(b, 0, 'n', 'p', a.radius, a.angle, a.thick)
90+
return b
91+
}
92+
93+
func (a *arc2D) AppendShaderBody(b []byte) []byte {
94+
s, c := math32.Sincos(a.angle / 2)
95+
b = glbuild.AppendFloatDecl(b, "r", a.radius)
96+
b = glbuild.AppendFloatDecl(b, "t", a.thick/2)
97+
b = glbuild.AppendVec2Decl(b, "sc", ms2.Vec{X: s, Y: c})
98+
b = append(b, `p.x=abs(p.x);
99+
return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*r) : abs(length(p)-r))-t;`...)
100+
return b
101+
}
102+
103+
func (a *arc2D) ForEach2DChild(userData any, fn func(userData any, s *glbuild.Shader2D) error) error {
104+
return nil
105+
}
106+
15107
type circle2D struct {
16108
r float32
17109
}
18110

111+
// NewCircle creates a circle of a radius centered at the origin (x,y)=(0,0).
19112
func NewCircle(radius float32) (glbuild.Shader2D, error) {
20113
if radius > 0 && !math32.IsInf(radius, 1) {
21114
return &circle2D{r: radius}, nil
@@ -91,6 +184,7 @@ type rect2D struct {
91184
d ms2.Vec
92185
}
93186

187+
// NewRectangle creates a rectangle centered at (x,y)=(0,0) with given x and y dimensions.
94188
func NewRectangle(x, y float32) (glbuild.Shader2D, error) {
95189
if x > 0 && y > 0 && !math32.IsInf(x, 1) && !math32.IsInf(y, 1) {
96190
return &rect2D{d: ms2.Vec{X: x, Y: y}}, nil
@@ -125,6 +219,7 @@ type hex2D struct {
125219
side float32
126220
}
127221

222+
// NewHexagon creates a regular hexagon centered at (x,y)=(0,0) with sides of length `side`.
128223
func NewHexagon(side float32) (glbuild.Shader2D, error) {
129224
if side > 0 && !math32.IsInf(side, 1) {
130225
return &hex2D{side: side}, nil
@@ -161,6 +256,7 @@ type ellipse2D struct {
161256
a, b float32
162257
}
163258

259+
// NewEllipse creates a 2D ellipse SDF with a and b ellipse parameters.
164260
func NewEllipse(a, b float32) (glbuild.Shader2D, error) {
165261
if a > 0 && b > 0 && !math32.IsInf(a, 1) && !math32.IsInf(b, 1) {
166262
return &ellipse2D{a: a, b: b}, nil
@@ -227,6 +323,7 @@ type poly2D struct {
227323
vert []ms2.Vec
228324
}
229325

326+
// NewPolygon creates a polygon from a set of vertices. The polygon can be self-intersecting.
230327
func NewPolygon(vertices []ms2.Vec) (glbuild.Shader2D, error) {
231328
if len(vertices) < 3 {
232329
return nil, errors.New("polygon needs at least 3 vertices")
@@ -705,7 +802,7 @@ func (s *translate2D) AppendShaderBody(b []byte) []byte {
705802
return b
706803
}
707804

708-
// Symmetry reflects the SDF around one or more cartesian planes.
805+
// Symmetry reflects the SDF around x or y (or both) axis.
709806
func Symmetry2D(s glbuild.Shader2D, mirrorX, mirrorY bool) glbuild.Shader2D {
710807
if !mirrorX && !mirrorY {
711808
panic("ineffective symmetry")

gsdfaux/gsdfaux.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,18 @@ func RenderPNGFile(filename string, s glbuild.Shader2D, picHeight int, useGPU bo
197197
return nil
198198
}
199199

200+
var red = color.RGBA{R: 255, A: 255}
201+
200202
// ColorConversionInigoQuilez creates a new color conversion using [Inigo Quilez]'s style.
201-
// A good value for characteristic distance is the bounding box diagonal divided by 3.
203+
// A good value for characteristic distance is the bounding box diagonal divided by 3. Returns red for NaN values/
202204
//
203205
// [Inigo Quilez]: https://iquilezles.org/articles/distfunctions2d/
204206
func ColorConversionInigoQuilez(characteristicDistance float32) func(float32) color.Color {
205207
inv := 1. / characteristicDistance
206208
return func(d float32) color.Color {
209+
if math.IsNaN(d) {
210+
return red
211+
}
207212
d *= inv
208213
var one = ms3.Vec{X: 1, Y: 1, Z: 1}
209214
var c ms3.Vec

operations.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ func Rotate(s glbuild.Shader3D, radians float32, axis ms3.Vec) (glbuild.Shader3D
310310
return Transform(s, T)
311311
}
312312

313-
// Translate moves the SDF s in the given direction.
313+
// Translate moves the SDF s in the given direction (dirX, dirY, dirZ) and returns the result.
314314
func Translate(s glbuild.Shader3D, dirX, dirY, dirZ float32) glbuild.Shader3D {
315315
return &translate{s: s, p: ms3.Vec{X: dirX, Y: dirY, Z: dirZ}}
316316
}

primitives.go

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type sphere struct {
2828
r float32
2929
}
3030

31+
// NewSphere creates a sphere centered at the origin of radius r.
3132
func NewSphere(r float32) (glbuild.Shader3D, error) {
3233
valid := r > 0
3334
if !valid {
@@ -60,6 +61,7 @@ func (s *sphere) Bounds() ms3.Box {
6061
}
6162
}
6263

64+
// NewBox creates a box centered at the origin with x,y,z dimensions and a rounding parameter to round edges.
6365
func NewBox(x, y, z, round float32) (glbuild.Shader3D, error) {
6466
if round < 0 || round > x/2 || round > y/2 || round > z/2 {
6567
return nil, errors.New("invalid box rounding value")
@@ -98,6 +100,8 @@ func (s *box) Bounds() ms3.Box {
98100
return ms3.NewCenteredBox(ms3.Vec{}, s.dims)
99101
}
100102

103+
// NewCylinder creates a cylinder centered at the origin with given radius and height.
104+
// The cylinder's axis points in z direction.
101105
func NewCylinder(r, h, rounding float32) (glbuild.Shader3D, error) {
102106
okRounding := rounding >= 0 && rounding < r && rounding < h/2
103107
if !okRounding {
@@ -152,6 +156,8 @@ func (c *cylinder) args() (r, h, round float32) {
152156
return c.r, (c.h - 2*c.round) / 2, c.round
153157
}
154158

159+
// NewHexagonalPrism creates a hexagonal prism given a face-to-face dimension and height.
160+
// The hexagon's length is in the z axis.
155161
func NewHexagonalPrism(face2Face, h float32) (glbuild.Shader3D, error) {
156162
if face2Face <= 0 || h <= 0 {
157163
return nil, errors.New("invalid hexagonal prism parameter")
@@ -196,6 +202,8 @@ return min(max(d.x,d.y),0.0) + length(max(d,0.0));`...)
196202
return b
197203
}
198204

205+
// NewTriangularPrism creates a 3D triangular prism with a given triangle cross-sectional height (2D)
206+
// and a extrude length. The prism's extrude axis is in the z axis direction.
199207
func NewTriangularPrism(triHeight, extrudeLength float32) (glbuild.Shader3D, error) {
200208
if extrudeLength > 0 && !math32.IsInf(extrudeLength, 1) {
201209
tri, err := NewEquilateralTriangle(triHeight)
@@ -208,16 +216,21 @@ func NewTriangularPrism(triHeight, extrudeLength float32) (glbuild.Shader3D, err
208216
}
209217

210218
type torus struct {
211-
rRing, rGreater float32
212-
}
213-
214-
func NewTorus(greaterRadius, ringRadius float32) (glbuild.Shader3D, error) {
215-
if greaterRadius < 2*ringRadius {
216-
return nil, errors.New("too large torus ring radius")
217-
} else if greaterRadius <= 0 || ringRadius <= 0 {
219+
rLesser, rGreater float32
220+
}
221+
222+
// NewTorus creates a 3D torus given 2 radii to define the radius
223+
// across (greaterRadius) and the "solid" radius (lesserRadius).
224+
// If the radius were cut and stretched straight to form a cylinder the lesser
225+
// radius would be the radius of the cylinder.
226+
// The torus' axis is in the z axis.
227+
func NewTorus(greaterRadius, lesserRadius float32) (glbuild.Shader3D, error) {
228+
if greaterRadius < 2*lesserRadius {
229+
return nil, errors.New("too large torus lesser radius")
230+
} else if greaterRadius <= 0 || lesserRadius <= 0 {
218231
return nil, errors.New("invalid torus parameter")
219232
}
220-
return &torus{rRing: ringRadius, rGreater: greaterRadius}, nil
233+
return &torus{rLesser: lesserRadius, rGreater: greaterRadius}, nil
221234
}
222235

223236
func (s *torus) ForEachChild(userData any, fn func(userData any, s *glbuild.Shader3D) error) error {
@@ -226,14 +239,14 @@ func (s *torus) ForEachChild(userData any, fn func(userData any, s *glbuild.Shad
226239

227240
func (s *torus) AppendShaderName(b []byte) []byte {
228241
b = append(b, "torus"...)
229-
b = glbuild.AppendFloat(b, 'n', 'p', s.rRing)
242+
b = glbuild.AppendFloat(b, 'n', 'p', s.rLesser)
230243
b = glbuild.AppendFloat(b, 'n', 'p', s.rGreater)
231244
return b
232245
}
233246

234247
func (s *torus) AppendShaderBody(b []byte) []byte {
235248
b = glbuild.AppendFloatDecl(b, "t1", s.rGreater) // Counteract rounding effect.
236-
b = glbuild.AppendFloatDecl(b, "t2", s.rRing)
249+
b = glbuild.AppendFloatDecl(b, "t2", s.rLesser)
237250
b = append(b, `p = p.xzy;
238251
vec2 t = vec2(t1, t2);
239252
vec2 q = vec2(length(p.xz)-t.x,p.y);
@@ -242,13 +255,14 @@ return length(q)-t.y;`...)
242255
}
243256

244257
func (s *torus) Bounds() ms3.Box {
245-
R := s.rRing + s.rGreater
258+
R := s.rLesser + s.rGreater
246259
return ms3.Box{
247-
Min: ms3.Vec{X: -R, Y: -R, Z: -s.rRing},
248-
Max: ms3.Vec{X: R, Y: R, Z: s.rRing},
260+
Min: ms3.Vec{X: -R, Y: -R, Z: -s.rLesser},
261+
Max: ms3.Vec{X: R, Y: R, Z: s.rLesser},
249262
}
250263
}
251264

265+
// NewBoxFrame creates a framed box with the frame being composed of square beams of thickness e.
252266
func NewBoxFrame(dimX, dimY, dimZ, e float32) (glbuild.Shader3D, error) {
253267
e /= 2
254268
if dimX <= 0 || dimY <= 0 || dimZ <= 0 || e <= 0 {

0 commit comments

Comments
 (0)