Skip to content

Commit

Permalink
add arc and line
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Sep 20, 2024
1 parent 70320aa commit 34de309
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 17 deletions.
32 changes: 31 additions & 1 deletion cpu_evaluators.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (bf *boxframe) Evaluate(pos []ms3.Vec, dist []float32, userData any) error

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

func (l *line2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
a := l.a
ba := ms2.Sub(l.b, l.a)
dotba := ms2.Dot(ba, ba)
t := l.thick / 2
for i, p := range pos {
pa := ms2.Sub(p, a)
h := ms1.Clamp(ms2.Dot(pa, ba)/dotba, 0, 1)
dist[i] = ms2.Norm(ms2.Sub(pa, ms2.Scale(h, ba))) - t
}
return nil
}

func (a *arc2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
r := a.radius
t := a.thick / 2
s, c := math32.Sincos(a.angle / 2)
sc := ms2.Vec{X: s, Y: c}
scr := ms2.Scale(r, sc)
for i, p := range pos {
p.X = math32.Abs(p.X)
if sc.Y*p.X > sc.X*p.Y {
dist[i] = ms2.Norm(ms2.Sub(p, scr)) - t
} else {
dist[i] = math32.Abs(ms2.Norm(p)-r) - t
}
}
return nil
}

func (c *circle2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
r := c.r
for i, p := range pos {
Expand Down
3 changes: 3 additions & 0 deletions examples/test/glsdf3test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"log"
"math"
"math/rand"
"os"
"reflect"
Expand Down Expand Up @@ -77,6 +78,8 @@ var npt threads.NPT
var _ = npt.SetFromNominal(1.0 / 2.0)

var PremadePrimitives2D = []glbuild.Shader2D{
mustShader2D(gsdf.NewLine2D(-2.3, 1, 13, 12, .1)),
mustShader2D(gsdf.NewArc(2.3, math.Pi/3, 0.1)),
mustShader2D(gsdf.NewCircle(1)),
mustShader2D(gsdf.NewHexagon(1)),
mustShader2D(gsdf.NewPolygon([]ms2.Vec{
Expand Down
99 changes: 98 additions & 1 deletion gsdf2d.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,103 @@ import (
"github.com/soypat/gsdf/glbuild"
)

// NewLine2D creates a straight line between (x0,y0) and (x1,y1) with a given thickness.
func NewLine2D(x0, y0, x1, y1, thick float32) (glbuild.Shader2D, error) {
hasNaN := math32.IsNaN(x0) || math32.IsNaN(y0) || math32.IsNaN(x1) || math32.IsNaN(y1) || math32.IsNaN(thick)
if hasNaN {
return nil, errors.New("NaN argument to NewLine2D")
} else if thick < 0 {
return nil, errors.New("negative thickness to NewLine2D")
}
return &line2D{a: ms2.Vec{X: x0, Y: y0}, b: ms2.Vec{X: x1, Y: y1}, thick: thick}, nil
}

type line2D struct {
thick float32
a, b ms2.Vec
}

func (l *line2D) Bounds() ms2.Box {
b := ms2.Box{Min: l.a, Max: l.b}.Canon()
b.Max = ms2.AddScalar(l.thick, b.Max)
b.Min = ms2.AddScalar(-l.thick, b.Min)
return b
}

func (l *line2D) AppendShaderName(b []byte) []byte {
b = append(b, "line"...)
b = glbuild.AppendFloats(b, 0, 'n', 'p', l.a.X, l.a.Y, l.b.X, l.b.Y, l.thick)
return b
}

func (l *line2D) AppendShaderBody(b []byte) []byte {
b = glbuild.AppendVec2Decl(b, "a", l.a)
b = glbuild.AppendVec2Decl(b, "ba", ms2.Sub(l.b, l.a))
b = glbuild.AppendFloatDecl(b, "t", l.thick/2)
b = append(b, `vec2 pa=p-a;
float h=clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
return length(pa-ba*h)-t;`...)
return b
}

func (l *line2D) ForEach2DChild(userData any, fn func(userData any, s *glbuild.Shader2D) error) error {
return nil
}

// 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.
// The arc begins opening at (x,y)=(0,r) in both positive and negative x direction.
func NewArc(radius, arcAngle, thick float32) (glbuild.Shader2D, error) {
ok := radius > 0 && arcAngle > 0 && thick >= 0
if !ok {
return nil, errors.New("invalid argument to NewArc2D")
} else if arcAngle > 2*math.Pi {
return nil, errors.New("arc angle exceeds full circle")
} else if 2*math.Pi-arcAngle < 0.5e-6 {
arcAngle = 2*math.Pi - 1e-7 // Condition the arc to be closed.
}
return &arc2D{radius: radius, angle: arcAngle, thick: thick}, nil
}

type arc2D struct {
radius float32
angle float32
thick float32
}

func (a *arc2D) Bounds() ms2.Box {
r := a.radius + a.thick
rcos := a.radius*math32.Cos(a.angle/2) - a.thick
return ms2.Box{
Min: ms2.Vec{X: -r, Y: rcos},
Max: ms2.Vec{X: r, Y: r},
}
}

func (a *arc2D) AppendShaderName(b []byte) []byte {
b = append(b, "arc"...)
b = glbuild.AppendFloats(b, 0, 'n', 'p', a.radius, a.angle, a.thick)
return b
}

func (a *arc2D) AppendShaderBody(b []byte) []byte {
s, c := math32.Sincos(a.angle / 2)
b = glbuild.AppendFloatDecl(b, "r", a.radius)
b = glbuild.AppendFloatDecl(b, "t", a.thick/2)
b = glbuild.AppendVec2Decl(b, "sc", ms2.Vec{X: s, Y: c})
b = append(b, `p.x=abs(p.x);
return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*r) : abs(length(p)-r))-t;`...)
return b
}

func (a *arc2D) ForEach2DChild(userData any, fn func(userData any, s *glbuild.Shader2D) error) error {
return nil
}

type circle2D struct {
r float32
}

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

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

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

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

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

// Symmetry reflects the SDF around one or more cartesian planes.
// Symmetry reflects the SDF around x or y (or both) axis.
func Symmetry2D(s glbuild.Shader2D, mirrorX, mirrorY bool) glbuild.Shader2D {
if !mirrorX && !mirrorY {
panic("ineffective symmetry")
Expand Down
7 changes: 6 additions & 1 deletion gsdfaux/gsdfaux.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,18 @@ func RenderPNGFile(filename string, s glbuild.Shader2D, picHeight int, useGPU bo
return nil
}

var red = color.RGBA{R: 255, A: 255}

// ColorConversionInigoQuilez creates a new color conversion using [Inigo Quilez]'s style.
// A good value for characteristic distance is the bounding box diagonal divided by 3.
// A good value for characteristic distance is the bounding box diagonal divided by 3. Returns red for NaN values/
//
// [Inigo Quilez]: https://iquilezles.org/articles/distfunctions2d/
func ColorConversionInigoQuilez(characteristicDistance float32) func(float32) color.Color {
inv := 1. / characteristicDistance
return func(d float32) color.Color {
if math.IsNaN(d) {
return red
}
d *= inv
var one = ms3.Vec{X: 1, Y: 1, Z: 1}
var c ms3.Vec
Expand Down
2 changes: 1 addition & 1 deletion operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func Rotate(s glbuild.Shader3D, radians float32, axis ms3.Vec) (glbuild.Shader3D
return Transform(s, T)
}

// Translate moves the SDF s in the given direction.
// Translate moves the SDF s in the given direction (dirX, dirY, dirZ) and returns the result.
func Translate(s glbuild.Shader3D, dirX, dirY, dirZ float32) glbuild.Shader3D {
return &translate{s: s, p: ms3.Vec{X: dirX, Y: dirY, Z: dirZ}}
}
Expand Down
40 changes: 27 additions & 13 deletions primitives.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type sphere struct {
r float32
}

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

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

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

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

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

type torus struct {
rRing, rGreater float32
}

func NewTorus(greaterRadius, ringRadius float32) (glbuild.Shader3D, error) {
if greaterRadius < 2*ringRadius {
return nil, errors.New("too large torus ring radius")
} else if greaterRadius <= 0 || ringRadius <= 0 {
rLesser, rGreater float32
}

// NewTorus creates a 3D torus given 2 radii to define the radius
// across (greaterRadius) and the "solid" radius (lesserRadius).
// If the radius were cut and stretched straight to form a cylinder the lesser
// radius would be the radius of the cylinder.
// The torus' axis is in the z axis.
func NewTorus(greaterRadius, lesserRadius float32) (glbuild.Shader3D, error) {
if greaterRadius < 2*lesserRadius {
return nil, errors.New("too large torus lesser radius")
} else if greaterRadius <= 0 || lesserRadius <= 0 {
return nil, errors.New("invalid torus parameter")
}
return &torus{rRing: ringRadius, rGreater: greaterRadius}, nil
return &torus{rLesser: lesserRadius, rGreater: greaterRadius}, nil
}

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

func (s *torus) AppendShaderName(b []byte) []byte {
b = append(b, "torus"...)
b = glbuild.AppendFloat(b, 'n', 'p', s.rRing)
b = glbuild.AppendFloat(b, 'n', 'p', s.rLesser)
b = glbuild.AppendFloat(b, 'n', 'p', s.rGreater)
return b
}

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

func (s *torus) Bounds() ms3.Box {
R := s.rRing + s.rGreater
R := s.rLesser + s.rGreater
return ms3.Box{
Min: ms3.Vec{X: -R, Y: -R, Z: -s.rRing},
Max: ms3.Vec{X: R, Y: R, Z: s.rRing},
Min: ms3.Vec{X: -R, Y: -R, Z: -s.rLesser},
Max: ms3.Vec{X: R, Y: R, Z: s.rLesser},
}
}

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

0 comments on commit 34de309

Please sign in to comment.