Skip to content

Commit

Permalink
Merge Dev (#6)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
soypat authored Dec 8, 2024
1 parent da0bdf9 commit 269ef3e
Show file tree
Hide file tree
Showing 15 changed files with 570 additions and 167 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ for visualization or 3D printing file outputs. Quick jump to usage: [bolt exampl

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

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

## Requirements

- [Go](https://go.dev/)
- **Optional**: See latest requirements on [go-glfw](https://github.com/go-gl/glfw) if using GPU

## Features

- High test coverage (when GPU available, not the case in CI)

- Extremely coherent API design.

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

![iso-screw](https://github.com/user-attachments/assets/6bc987b9-d522-42a4-89df-71a20c3ae7ff)
![array-triangles](https://github.com/user-attachments/assets/6a479889-2836-464c-b8ea-82109a5aad13)
![geb-book-cover](https://github.com/user-attachments/assets/1ed945fb-5729-4028-bed8-26e0de3073ab)
![geb-book-cover](https://github.com/user-attachments/assets/a6727481-07f3-4636-8e1c-9b1a02bb108f)
67 changes: 57 additions & 10 deletions cpu_evaluators.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package gsdf

import (
"math"

"github.com/chewxy/math32"
"github.com/soypat/glgl/math/ms1"
"github.com/soypat/glgl/math/ms2"
"github.com/soypat/glgl/math/ms3"
"github.com/soypat/gsdf/gleval"
)

// minReduce takes element-wise minimum of arguments and stores to first argument.
func minReduce(d1AndDst, d2 []float32) {
for i := range d1AndDst {
d1AndDst[i] = math32.Min(d1AndDst[i], d2[i])
}
}

func (u *sphere) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
r := u.r
for i, p := range pos {
Expand Down Expand Up @@ -129,9 +138,7 @@ func (u *OpUnion) Evaluate(pos []ms3.Vec, dist []float32, userData any) error {
if err != nil {
return err
}
for i, d := range dist {
dist[i] = math32.Min(d, auxDist[i])
}
minReduce(dist, auxDist)
}
return nil
}
Expand Down Expand Up @@ -619,8 +626,8 @@ func (c *hex2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {

func (c *ellipse2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
// https://iquilezles.org/articles/ellipsedist
a, b := c.a, c.b
for i, p := range pos {
a, b := c.a, c.b
p = ms2.AbsElem(p)
if p.X > p.Y {
p.X, p.Y = p.Y, p.X
Expand All @@ -646,8 +653,8 @@ func (c *ellipse2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error
co = (ry + signf(l)*rx + math32.Abs(g)/(rx*ry) - m) / 2
} else {
h := 2 * m * n * math32.Sqrt(d)
s := signf(q+h) * math32.Pow(math32.Abs(q+h), 1./3.)
u := signf(q-h) * math32.Pow(math32.Abs(q-h), 1./3.)
s := signf(q+h) * math32.Cbrt(math32.Abs(q+h))
u := signf(q-h) * math32.Cbrt(math32.Abs(q-h))

rx := -s - u - 4*c + 2*m2
ry := sqrt3 * (s - u)
Expand Down Expand Up @@ -927,7 +934,6 @@ func (c *circarray) Evaluate(pos []ms3.Vec, dist []float32, userData any) error
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 {
Expand Down Expand Up @@ -958,9 +964,7 @@ func (c *circarray) Evaluate(pos []ms3.Vec, dist []float32, userData any) error
if err != nil {
return err
}
for i, d := range dist {
dist[i] = math32.Min(d, dist1[i])
}
minReduce(dist, dist1)
return nil
}

Expand Down Expand Up @@ -1031,3 +1035,46 @@ func (l *lines2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
}
return nil
}

func (c *translateMulti2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
vp, err := gleval.GetVecPool(userData)
if err != nil {
return err
}
for i := range dist {
dist[i] = math.MaxFloat32
}
d1 := vp.Float.Acquire(len(pos))
defer vp.Float.Release(d1)
for _, p := range c.displacements {
t2d := translate2D{
s: c.s,
p: p,
}
err = t2d.Evaluate(pos, d1, userData)
if err != nil {
return err
}
minReduce(dist, d1)
}
return nil
}

func (c *rotation2D) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
sdf, err := gleval.AssertSDF2(c.s)
if err != nil {
return err
}
vp, err := gleval.GetVecPool(userData)
if err != nil {
return err
}
posTransf := vp.V2.Acquire(len(pos))
defer vp.V2.Release(posTransf)
invT := c.tInv
for i, p := range pos {
posTransf[i] = ms2.MulMatVec(invT, p)
}
err = sdf.Evaluate(posTransf, dist, userData)
return err
}
28 changes: 3 additions & 25 deletions examples/image-text/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"runtime"
"time"

"github.com/chewxy/math32"
"github.com/soypat/glgl/math/ms1"
"github.com/soypat/gsdf"
"github.com/soypat/gsdf/forge/textsdf"
"github.com/soypat/gsdf/glbuild"
Expand Down Expand Up @@ -66,32 +64,12 @@ func main() {

charHeight := sdf2.Bounds().Size().Y
edgeAliasing := charHeight / 1000
conversion := gsdfaux.ColorConversionLinearGradient(edgeAliasing, color.Black, color.White)
start := time.Now()
err = gsdfaux.RenderPNGFile(filename, sdf2, 300, blackAndWhite(edgeAliasing))
err = gsdfaux.RenderPNGFile(filename, sdf2, 300, conversion)
if err != nil {
log.Fatal(err)
}
_ = conversion
fmt.Println("PNG file rendered to", filename, "in", time.Since(start))
}

func blackAndWhite(edgeSmooth float32) func(d float32) color.Color {
if edgeSmooth <= 0 {
return blackAndWhiteNoSmoothing
}
return func(d float32) color.Color {
// Smoothstep anti-aliasing near the edge
blend := 0.5 + 0.5*math32.Tanh(d/edgeSmooth)
// Clamp blend to [0, 1] for valid grayscale values
blend = ms1.Clamp(blend, 0, 1)
// Convert blend to grayscale
grayValue := uint8(blend * 255)
return color.Gray{Y: grayValue}
}
}

func blackAndWhiteNoSmoothing(d float32) color.Color {
if d < 0 {
return color.Black
}
return color.White
}
4 changes: 3 additions & 1 deletion examples/ui-geb/uigeb.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) {
var f textsdf.Font
f.Configure(textsdf.FontConfig{
RelativeGlyphTolerance: 0.01,
Builder: bld,
})
err := f.LoadTTFBytes(textsdf.ISO3098TTF())
if err != nil {
Expand Down Expand Up @@ -57,7 +58,7 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) {
sclG := ms2.DivElem(sz, szG)
sclE := ms2.DivElem(sz, szE)
sclB := ms2.DivElem(sz, szB)
fmt.Println(sclG, sclE, sclB)

// Create 3D letters.
L := sz.Max()
G3 := bld.Extrude(G, L)
Expand Down Expand Up @@ -89,6 +90,7 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) {

func main() {
var bld gsdf.Builder
// bld.SetFlags(gsdf.FlagUseShaderBuffers)
shape, err := scene(&bld)
shape = bld.Scale(shape, 0.3)
if err != nil {
Expand Down
56 changes: 34 additions & 22 deletions forge/textsdf/font.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ import (
const firstBasic = '!'
const lastBasic = '~'

var defaultBuilder = &gsdf.Builder{}

type FontConfig struct {
// RelativeGlyphTolerance sets the permissible curve tolerance for glyphs. Must be between 0..1. If zero a reasonable value is chosen.
RelativeGlyphTolerance float32
Builder *gsdf.Builder
}

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

Expand All @@ -39,6 +42,9 @@ func (f *Font) Configure(cfg FontConfig) error {
}
f.reset()
f.reltol = cfg.RelativeGlyphTolerance
if cfg.Builder != nil {
f.bld = cfg.Builder
}
return nil
}

Expand All @@ -59,7 +65,7 @@ func (f *Font) reset() {
f.basicGlyphs[i] = glyph{}
}
if f.otherGlyphs == nil {
f.otherGlyphs = make(map[rune]glyph)
f.otherGlyphs = make(map[rune]*glyph)
} else {
for k := range f.otherGlyphs {
delete(f.otherGlyphs, k)
Expand All @@ -68,6 +74,9 @@ func (f *Font) reset() {
if f.reltol == 0 {
f.reltol = 0.15
}
if f.bld == nil {
f.bld = defaultBuilder
}
}

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

// Glyph returns a SDF for a character defined by the argument rune.
func (f *Font) Glyph(c rune) (_ glbuild.Shader2D, err error) {
var g glyph
g, err := f.glyph(c)
if err != nil {
return nil, err
}
return g.sdf, nil
}

func (f *Font) glyph(c rune) (g *glyph, err error) {
if c >= firstBasic && c <= lastBasic {
// Basic ASCII glyph case.
g = f.basicGlyphs[c-firstBasic]
g = &f.basicGlyphs[c-firstBasic]
if g.sdf == nil {
// Glyph not yet created. create it.
g, err = f.makeGlyph(c)
gc, err := f.makeGlyph(c)
if err != nil {
return nil, err
}
f.basicGlyphs[c-firstBasic] = g
*g = gc
}
return g.sdf, nil
return g, nil
}
// Unicode or other glyph.
g, ok := f.otherGlyphs[c]
if !ok {
g, err = f.makeGlyph(c)
gc, err := f.makeGlyph(c)
if err != nil {
return nil, err
}
g = &gc
f.otherGlyphs[c] = g
}
return g.sdf, nil
return g, nil
}

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

func (f *Font) makeGlyph(char rune) (glyph, error) {
g := &f.gb
bld := &f.bld
bld := f.bld

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

func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int, tol, scale float32) (glbuild.Shader2D, bool, error) {
var (
sampler = ms2.Spline3Sampler{Spline: quadBezier, Tolerance: tol}
sum float32
sampler = ms2.Spline3Sampler{Spline: quadBezier, Tolerance: tol}
windingSum float32
)
points = points[start:end]
n := len(points)
Expand All @@ -241,7 +258,7 @@ func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int, tol,
// on-on Straight line.
poly = append(poly, v0)
i += 1
sum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
windingSum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
vPrev = v0
continue

Expand Down Expand Up @@ -269,10 +286,10 @@ func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int, tol,
}
poly = append(poly, v0) // Append start point.
poly = sampler.SampleBisect(poly, 4)
sum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
windingSum += (v0.X - vPrev.X) * (v0.Y + vPrev.Y)
vPrev = v0
}
return bld.NewPolygon(poly), sum > 0, bld.Err()
return bld.NewPolygon(poly), windingSum > 0, bld.Err()
}

func p2v(p truetype.Point, scale float32) ms2.Vec {
Expand All @@ -282,12 +299,7 @@ func p2v(p truetype.Point, scale float32) ms2.Vec {
}
}

var quadBezier = ms2.NewSpline3([]float32{
1, 0, 0, 0,
-2, 2, 0, 0,
1, -2, 1, 0,
0, 0, 0, 0,
})
var quadBezier = ms2.SplineBezierQuadratic()

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

0 comments on commit 269ef3e

Please sign in to comment.