Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev November 2024 #6

Merged
merged 4 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading