Skip to content

Commit 269ef3e

Browse files
authored
Merge Dev (#6)
* fix CPU ellipse; fix GPU vec2 ssbo; add polyssbo/translatemulti2d; 87% test coverage; * add gsdf.Builder to font config * update README * fix uninitialized Font crash; fix polygon self-closing case; admit multiple identical SSBOs; color conversion additions
1 parent da0bdf9 commit 269ef3e

15 files changed

+570
-167
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,19 @@ for visualization or 3D printing file outputs. Quick jump to usage: [bolt exampl
1111

1212
All images and shapes in readme were generated using this library.
1313

14-
![circle](https://github.com/user-attachments/assets/91c99f47-0c52-4cb1-83e7-452b03b69dff)
1514
![bolt-example](https://github.com/user-attachments/assets/8da50871-2415-423f-beb3-0d78ad67c79e)
15+
![circle](https://github.com/user-attachments/assets/91c99f47-0c52-4cb1-83e7-452b03b69dff)
16+
![text](https://github.com/user-attachments/assets/73a90941-9279-449d-9f4d-3f2746af5dd5)
1617

1718
## Requirements
19+
1820
- [Go](https://go.dev/)
1921
- **Optional**: See latest requirements on [go-glfw](https://github.com/go-gl/glfw) if using GPU
2022

2123
## Features
2224

25+
- High test coverage (when GPU available, not the case in CI)
26+
2327
- Extremely coherent API design.
2428

2529
- UI for visualizing parts, rendered directly from shaders. See [UI example](./examples/ui-mandala) by running `go run ./examples/ui-mandala`
@@ -128,4 +132,4 @@ go run ./examples/fibonacci-showerhead -resdiv 350 36,16s user 0,76s system 100
128132

129133
![iso-screw](https://github.com/user-attachments/assets/6bc987b9-d522-42a4-89df-71a20c3ae7ff)
130134
![array-triangles](https://github.com/user-attachments/assets/6a479889-2836-464c-b8ea-82109a5aad13)
131-
![geb-book-cover](https://github.com/user-attachments/assets/1ed945fb-5729-4028-bed8-26e0de3073ab)
135+
![geb-book-cover](https://github.com/user-attachments/assets/a6727481-07f3-4636-8e1c-9b1a02bb108f)

cpu_evaluators.go

+57-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package gsdf
22

33
import (
4+
"math"
5+
46
"github.com/chewxy/math32"
57
"github.com/soypat/glgl/math/ms1"
68
"github.com/soypat/glgl/math/ms2"
79
"github.com/soypat/glgl/math/ms3"
810
"github.com/soypat/gsdf/gleval"
911
)
1012

13+
// minReduce takes element-wise minimum of arguments and stores to first argument.
14+
func minReduce(d1AndDst, d2 []float32) {
15+
for i := range d1AndDst {
16+
d1AndDst[i] = math32.Min(d1AndDst[i], d2[i])
17+
}
18+
}
19+
1120
func (u *sphere) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
1221
r := u.r
1322
for i, p := range pos {
@@ -129,9 +138,7 @@ func (u *OpUnion) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
129138
if err != nil {
130139
return err
131140
}
132-
for i, d := range dist {
133-
dist[i] = math32.Min(d, auxDist[i])
134-
}
141+
minReduce(dist, auxDist)
135142
}
136143
return nil
137144
}
@@ -619,8 +626,8 @@ func (c *hex2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
619626

620627
func (c *ellipse2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
621628
// https://iquilezles.org/articles/ellipsedist
622-
a, b := c.a, c.b
623629
for i, p := range pos {
630+
a, b := c.a, c.b
624631
p = ms2.AbsElem(p)
625632
if p.X > p.Y {
626633
p.X, p.Y = p.Y, p.X
@@ -646,8 +653,8 @@ func (c *ellipse2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error
646653
co = (ry + signf(l)*rx + math32.Abs(g)/(rx*ry) - m) / 2
647654
} else {
648655
h := 2 * m * n * math32.Sqrt(d)
649-
s := signf(q+h) * math32.Pow(math32.Abs(q+h), 1./3.)
650-
u := signf(q-h) * math32.Pow(math32.Abs(q-h), 1./3.)
656+
s := signf(q+h) * math32.Cbrt(math32.Abs(q+h))
657+
u := signf(q-h) * math32.Cbrt(math32.Abs(q-h))
651658

652659
rx := -s - u - 4*c + 2*m2
653660
ry := sqrt3 * (s - u)
@@ -927,7 +934,6 @@ func (c *circarray) Evaluate(pos []ms3.Vec, dist []float32, userData any) error
927934
ncirc := float32(c.circleDiv)
928935
ninsm1 := float32(c.nInst - 1)
929936
for i, p := range pos {
930-
931937
pangle := math32.Atan2(p.Y, p.X)
932938
id := math32.Floor(pangle / angle)
933939
if id < 0 {
@@ -958,9 +964,7 @@ func (c *circarray) Evaluate(pos []ms3.Vec, dist []float32, userData any) error
958964
if err != nil {
959965
return err
960966
}
961-
for i, d := range dist {
962-
dist[i] = math32.Min(d, dist1[i])
963-
}
967+
minReduce(dist, dist1)
964968
return nil
965969
}
966970

@@ -1031,3 +1035,46 @@ func (l *lines2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
10311035
}
10321036
return nil
10331037
}
1038+
1039+
func (c *translateMulti2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
1040+
vp, err := gleval.GetVecPool(userData)
1041+
if err != nil {
1042+
return err
1043+
}
1044+
for i := range dist {
1045+
dist[i] = math.MaxFloat32
1046+
}
1047+
d1 := vp.Float.Acquire(len(pos))
1048+
defer vp.Float.Release(d1)
1049+
for _, p := range c.displacements {
1050+
t2d := translate2D{
1051+
s: c.s,
1052+
p: p,
1053+
}
1054+
err = t2d.Evaluate(pos, d1, userData)
1055+
if err != nil {
1056+
return err
1057+
}
1058+
minReduce(dist, d1)
1059+
}
1060+
return nil
1061+
}
1062+
1063+
func (c *rotation2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
1064+
sdf, err := gleval.AssertSDF2(c.s)
1065+
if err != nil {
1066+
return err
1067+
}
1068+
vp, err := gleval.GetVecPool(userData)
1069+
if err != nil {
1070+
return err
1071+
}
1072+
posTransf := vp.V2.Acquire(len(pos))
1073+
defer vp.V2.Release(posTransf)
1074+
invT := c.tInv
1075+
for i, p := range pos {
1076+
posTransf[i] = ms2.MulMatVec(invT, p)
1077+
}
1078+
err = sdf.Evaluate(posTransf, dist, userData)
1079+
return err
1080+
}

examples/image-text/text.go

+3-25
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"runtime"
99
"time"
1010

11-
"github.com/chewxy/math32"
12-
"github.com/soypat/glgl/math/ms1"
1311
"github.com/soypat/gsdf"
1412
"github.com/soypat/gsdf/forge/textsdf"
1513
"github.com/soypat/gsdf/glbuild"
@@ -66,32 +64,12 @@ func main() {
6664

6765
charHeight := sdf2.Bounds().Size().Y
6866
edgeAliasing := charHeight / 1000
67+
conversion := gsdfaux.ColorConversionLinearGradient(edgeAliasing, color.Black, color.White)
6968
start := time.Now()
70-
err = gsdfaux.RenderPNGFile(filename, sdf2, 300, blackAndWhite(edgeAliasing))
69+
err = gsdfaux.RenderPNGFile(filename, sdf2, 300, conversion)
7170
if err != nil {
7271
log.Fatal(err)
7372
}
73+
_ = conversion
7474
fmt.Println("PNG file rendered to", filename, "in", time.Since(start))
7575
}
76-
77-
func blackAndWhite(edgeSmooth float32) func(d float32) color.Color {
78-
if edgeSmooth <= 0 {
79-
return blackAndWhiteNoSmoothing
80-
}
81-
return func(d float32) color.Color {
82-
// Smoothstep anti-aliasing near the edge
83-
blend := 0.5 + 0.5*math32.Tanh(d/edgeSmooth)
84-
// Clamp blend to [0, 1] for valid grayscale values
85-
blend = ms1.Clamp(blend, 0, 1)
86-
// Convert blend to grayscale
87-
grayValue := uint8(blend * 255)
88-
return color.Gray{Y: grayValue}
89-
}
90-
}
91-
92-
func blackAndWhiteNoSmoothing(d float32) color.Color {
93-
if d < 0 {
94-
return color.Black
95-
}
96-
return color.White
97-
}

examples/ui-geb/uigeb.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) {
2424
var f textsdf.Font
2525
f.Configure(textsdf.FontConfig{
2626
RelativeGlyphTolerance: 0.01,
27+
Builder: bld,
2728
})
2829
err := f.LoadTTFBytes(textsdf.ISO3098TTF())
2930
if err != nil {
@@ -57,7 +58,7 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) {
5758
sclG := ms2.DivElem(sz, szG)
5859
sclE := ms2.DivElem(sz, szE)
5960
sclB := ms2.DivElem(sz, szB)
60-
fmt.Println(sclG, sclE, sclB)
61+
6162
// Create 3D letters.
6263
L := sz.Max()
6364
G3 := bld.Extrude(G, L)
@@ -89,6 +90,7 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) {
8990

9091
func main() {
9192
var bld gsdf.Builder
93+
// bld.SetFlags(gsdf.FlagUseShaderBuffers)
9294
shape, err := scene(&bld)
9395
shape = bld.Scale(shape, 0.3)
9496
if err != nil {

forge/textsdf/font.go

+34-22
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ import (
1616
const firstBasic = '!'
1717
const lastBasic = '~'
1818

19+
var defaultBuilder = &gsdf.Builder{}
20+
1921
type FontConfig struct {
2022
// RelativeGlyphTolerance sets the permissible curve tolerance for glyphs. Must be between 0..1. If zero a reasonable value is chosen.
2123
RelativeGlyphTolerance float32
24+
Builder *gsdf.Builder
2225
}
2326

2427
// Font implements font parsing and glyph (character) generation.
@@ -28,8 +31,8 @@ type Font struct {
2831
// basicGlyphs optimized array access for common ASCII glyphs.
2932
basicGlyphs [lastBasic - firstBasic + 1]glyph
3033
// Other kinds of glyphs.
31-
otherGlyphs map[rune]glyph
32-
bld gsdf.Builder
34+
otherGlyphs map[rune]*glyph
35+
bld *gsdf.Builder
3336
reltol float32 // Set by config or reset call if zeroed.
3437
}
3538

@@ -39,6 +42,9 @@ func (f *Font) Configure(cfg FontConfig) error {
3942
}
4043
f.reset()
4144
f.reltol = cfg.RelativeGlyphTolerance
45+
if cfg.Builder != nil {
46+
f.bld = cfg.Builder
47+
}
4248
return nil
4349
}
4450

@@ -59,7 +65,7 @@ func (f *Font) reset() {
5965
f.basicGlyphs[i] = glyph{}
6066
}
6167
if f.otherGlyphs == nil {
62-
f.otherGlyphs = make(map[rune]glyph)
68+
f.otherGlyphs = make(map[rune]*glyph)
6369
} else {
6470
for k := range f.otherGlyphs {
6571
delete(f.otherGlyphs, k)
@@ -68,6 +74,9 @@ func (f *Font) reset() {
6874
if f.reltol == 0 {
6975
f.reltol = 0.15
7076
}
77+
if f.bld == nil {
78+
f.bld = defaultBuilder
79+
}
7180
}
7281

7382
type glyph struct {
@@ -133,30 +142,38 @@ func (f *Font) AdvanceWidth(c rune) float32 {
133142

134143
// Glyph returns a SDF for a character defined by the argument rune.
135144
func (f *Font) Glyph(c rune) (_ glbuild.Shader2D, err error) {
136-
var g glyph
145+
g, err := f.glyph(c)
146+
if err != nil {
147+
return nil, err
148+
}
149+
return g.sdf, nil
150+
}
151+
152+
func (f *Font) glyph(c rune) (g *glyph, err error) {
137153
if c >= firstBasic && c <= lastBasic {
138154
// Basic ASCII glyph case.
139-
g = f.basicGlyphs[c-firstBasic]
155+
g = &f.basicGlyphs[c-firstBasic]
140156
if g.sdf == nil {
141157
// Glyph not yet created. create it.
142-
g, err = f.makeGlyph(c)
158+
gc, err := f.makeGlyph(c)
143159
if err != nil {
144160
return nil, err
145161
}
146-
f.basicGlyphs[c-firstBasic] = g
162+
*g = gc
147163
}
148-
return g.sdf, nil
164+
return g, nil
149165
}
150166
// Unicode or other glyph.
151167
g, ok := f.otherGlyphs[c]
152168
if !ok {
153-
g, err = f.makeGlyph(c)
169+
gc, err := f.makeGlyph(c)
154170
if err != nil {
155171
return nil, err
156172
}
173+
g = &gc
157174
f.otherGlyphs[c] = g
158175
}
159-
return g.sdf, nil
176+
return g, nil
160177
}
161178

162179
func (f *Font) scale() fixed.Int26_6 {
@@ -180,7 +197,7 @@ func (f *Font) scaleout() float32 {
180197

181198
func (f *Font) makeGlyph(char rune) (glyph, error) {
182199
g := &f.gb
183-
bld := &f.bld
200+
bld := f.bld
184201

185202
idx := f.ttf.Index(char)
186203
scale := f.scale()
@@ -219,8 +236,8 @@ func (f *Font) makeGlyph(char rune) (glyph, error) {
219236

220237
func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int, tol, scale float32) (glbuild.Shader2D, bool, error) {
221238
var (
222-
sampler = ms2.Spline3Sampler{Spline: quadBezier, Tolerance: tol}
223-
sum float32
239+
sampler = ms2.Spline3Sampler{Spline: quadBezier, Tolerance: tol}
240+
windingSum float32
224241
)
225242
points = points[start:end]
226243
n := len(points)
@@ -241,7 +258,7 @@ func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int, tol,
241258
// on-on Straight line.
242259
poly = append(poly, v0)
243260
i += 1
244-
sum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
261+
windingSum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
245262
vPrev = v0
246263
continue
247264

@@ -269,10 +286,10 @@ func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int, tol,
269286
}
270287
poly = append(poly, v0) // Append start point.
271288
poly = sampler.SampleBisect(poly, 4)
272-
sum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
289+
windingSum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
273290
vPrev = v0
274291
}
275-
return bld.NewPolygon(poly), sum > 0, bld.Err()
292+
return bld.NewPolygon(poly), windingSum > 0, bld.Err()
276293
}
277294

278295
func p2v(p truetype.Point, scale float32) ms2.Vec {
@@ -282,12 +299,7 @@ func p2v(p truetype.Point, scale float32) ms2.Vec {
282299
}
283300
}
284301

285-
var quadBezier = ms2.NewSpline3([]float32{
286-
1, 0, 0, 0,
287-
-2, 2, 0, 0,
288-
1, -2, 1, 0,
289-
0, 0, 0, 0,
290-
})
302+
var quadBezier = ms2.SplineBezierQuadratic()
291303

292304
func onbits3(points []truetype.Point, start, end, i int) uint32 {
293305
n := end - start

0 commit comments

Comments
 (0)