Skip to content

Commit 4d31c55

Browse files
authored
GLTF Animation + 2D SDF Nodes (#129)
* Cut sphere and torus SDF. Closest point on line fix * okay gltf polyform models now can deal with pointers, as well as the node system * gltf animations * more typing fixes * edit delete variable fixes * 2D SDF nodes * bug fix: image deserialilzation from profiles * white * File variable fix, node rename
1 parent 71fe45e commit 4d31c55

File tree

51 files changed

+1223
-246
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1223
-246
lines changed

cmd/polyform/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,15 @@ import (
4444
_ "github.com/EliCDavis/polyform/math/noise"
4545
_ "github.com/EliCDavis/polyform/math/quaternion"
4646
_ "github.com/EliCDavis/polyform/math/sdf"
47+
_ "github.com/EliCDavis/polyform/math/sequence"
4748
_ "github.com/EliCDavis/polyform/math/trig"
4849
_ "github.com/EliCDavis/polyform/math/trs"
4950
_ "github.com/EliCDavis/polyform/math/unit"
5051
_ "github.com/EliCDavis/polyform/math/vector2"
5152
_ "github.com/EliCDavis/polyform/math/vector3"
5253

5354
_ "github.com/EliCDavis/polyform/modeling"
55+
_ "github.com/EliCDavis/polyform/modeling/animation"
5456
_ "github.com/EliCDavis/polyform/modeling/extrude"
5557
_ "github.com/EliCDavis/polyform/modeling/marching"
5658
_ "github.com/EliCDavis/polyform/modeling/meshops"

docs/resources/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ Resources either directly contributing to the code, or are just interesting find
100100
- [SDFs](https://iquilezles.org/articles/distfunctions/)
101101
- [Fast 2D SDF](https://stackoverflow.com/questions/68178747/fast-2d-signed-distance)
102102
- [Got Subtraction from Here](https://www.ronja-tutorials.com/post/035-2d-sdf-combination/)
103+
- SDFs from images
104+
- [_"Distance Transforms of Sampled Functions"_ by Pedro F. Felzenszwalb and Daniel P. Huttenlocher](https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf)
105+
- [The Fast Euclidean Distance Transform](https://hellorob.org/files/lectures/fast_euclidean_dt.pdf)
106+
- [_"Euclidean Distance Transform (EDT) — An Introduction"_ by Kareim Tarek](https://medium.com/@kareimtarek1972/euclidean-distance-transform-edt-introduction-5d7d7c144aa)
103107
- Collisions
104108
- [Closest point on Triangle](https://gdbooks.gitbooks.io/3dcollisions/content/Chapter4/closest_point_to_triangle.html)
105109
- Ray Tracing

drawing/coloring/nodes.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ func init() {
2929
refutil.RegisterType[nodes.Struct[GradientKeyNode[vector4.Float64]]](factory)
3030
refutil.RegisterType[nodes.Struct[GradientKeyNode[Color]]](factory)
3131

32+
refutil.RegisterType[nodes.Struct[RedNode]](factory)
33+
refutil.RegisterType[nodes.Struct[GreenNode]](factory)
34+
refutil.RegisterType[nodes.Struct[BlueNode]](factory)
35+
refutil.RegisterType[nodes.Struct[MagentaNode]](factory)
36+
refutil.RegisterType[nodes.Struct[CyanNode]](factory)
37+
refutil.RegisterType[nodes.Struct[YellowNode]](factory)
38+
refutil.RegisterType[nodes.Struct[WhiteNode]](factory)
39+
refutil.RegisterType[nodes.Struct[BlackNode]](factory)
40+
3241
generator.RegisterTypes(factory)
3342
}
3443

@@ -230,3 +239,41 @@ func (n GradientKeyNode[T]) Gradient(out *nodes.StructOutput[GradientKey[T]]) {
230239
Value: val,
231240
})
232241
}
242+
243+
// ============================================================================
244+
245+
type RedNode struct{}
246+
247+
func (n RedNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{1, 0, 0, 1}) }
248+
249+
type GreenNode struct{}
250+
251+
func (n GreenNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 1, 0, 1}) }
252+
253+
type BlueNode struct{}
254+
255+
func (n BlueNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 0, 1, 1}) }
256+
257+
type YellowNode struct{}
258+
259+
func (n YellowNode) Color(out *nodes.StructOutput[Color]) {
260+
out.Set(Color{1, 1, 0, 1})
261+
}
262+
263+
type MagentaNode struct{}
264+
265+
func (n MagentaNode) Color(out *nodes.StructOutput[Color]) {
266+
out.Set(Color{1, 0, 1, 1})
267+
}
268+
269+
type CyanNode struct{}
270+
271+
func (n CyanNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 1, 1, 1}) }
272+
273+
type BlackNode struct{}
274+
275+
func (n BlackNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 0, 0, 1}) }
276+
277+
type WhiteNode struct{}
278+
279+
func (n WhiteNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{1, 1, 1, 1}) }

drawing/texturing/nodes.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,19 @@ func init() {
2929
refutil.RegisterType[nodes.Struct[FromArrayNode[bool]]](factory)
3030
refutil.RegisterType[nodes.Struct[FromArrayNode[coloring.Color]]](factory)
3131

32+
refutil.RegisterType[nodes.Struct[FromImageNode]](factory)
33+
3234
refutil.RegisterType[nodes.Struct[SelectNode[float64]]](factory)
3335
refutil.RegisterType[nodes.Struct[SelectNode[vector2.Float64]]](factory)
3436
refutil.RegisterType[nodes.Struct[SelectNode[vector3.Float64]]](factory)
3537
refutil.RegisterType[nodes.Struct[SelectNode[bool]]](factory)
36-
refutil.RegisterType[nodes.Struct[SelectNode[coloring.Color]]](factory)
38+
refutil.RegisterType[nodes.Struct[SelectColorNode]](factory)
3739

3840
refutil.RegisterType[nodes.Struct[CompareValueNode[float64]]](factory)
3941

42+
refutil.RegisterType[nodes.Struct[MaskToSDFNode]](factory)
43+
refutil.RegisterType[nodes.Struct[SampleSDFNode]](factory)
44+
4045
refutil.RegisterType[nodes.Struct[RadialGradientNode[float64]]](factory)
4146
refutil.RegisterType[nodes.Struct[RadialGradientNode[vector2.Float64]]](factory)
4247
refutil.RegisterType[nodes.Struct[RadialGradientNode[vector3.Float64]]](factory)

drawing/texturing/sdf.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package texturing
2+
3+
import (
4+
"math"
5+
6+
"github.com/EliCDavis/polyform/math/geometry"
7+
"github.com/EliCDavis/polyform/math/sample"
8+
"github.com/EliCDavis/polyform/nodes"
9+
"github.com/EliCDavis/vector/vector2"
10+
"github.com/EliCDavis/vector/vector3"
11+
)
12+
13+
type SampleSDFNode struct {
14+
Texture nodes.Output[Texture[float64]]
15+
Size nodes.Output[vector3.Float64]
16+
}
17+
18+
func (n SampleSDFNode) SDF(out *nodes.StructOutput[sample.Vec3ToFloat]) {
19+
if n.Texture == nil {
20+
return
21+
}
22+
tex := nodes.GetOutputValue(out, n.Texture)
23+
size := nodes.TryGetOutputValue(out, n.Size, vector3.One[float64]())
24+
bounds := geometry.NewAABB(vector3.Zero[float64](), size)
25+
half := size.Scale(0.5)
26+
out.Set(func(f vector3.Float64) float64 {
27+
v := bounds.ClosestPoint(f)
28+
p := v.XZ().
29+
DivByVector(half.XZ()).
30+
Scale(0.5).
31+
Add(vector2.Fill(0.5))
32+
33+
t := tex.UV(p.X(), p.Y())
34+
return t + f.Distance(v)
35+
})
36+
}
37+
38+
type MaskToSDFNode struct {
39+
Mask nodes.Output[Texture[bool]]
40+
}
41+
42+
func (n MaskToSDFNode) SDF(out *nodes.StructOutput[Texture[float64]]) {
43+
if n.Mask == nil {
44+
return
45+
}
46+
out.Set(ToSDF(nodes.GetOutputValue(out, n.Mask)))
47+
}
48+
49+
func (n MaskToSDFNode) NormalizedSDF(out *nodes.StructOutput[Texture[float64]]) {
50+
if n.Mask == nil {
51+
return
52+
}
53+
54+
sdf := ToSDF(nodes.GetOutputValue(out, n.Mask))
55+
56+
maxV := -math.MaxFloat64
57+
for _, v := range sdf.data {
58+
maxV = max(maxV, v)
59+
}
60+
61+
sdf.MutateParallel(func(x, y int, v float64) float64 {
62+
return v / maxV
63+
})
64+
65+
out.Set(sdf)
66+
}
67+
68+
func edt1D(input, result []float64, n int) {
69+
paras := make([]int, n)
70+
ranges := make([]float64, n+1)
71+
72+
k := 0
73+
ranges[0] = math.Inf(-1)
74+
ranges[1] = math.Inf(1)
75+
76+
for q := 1; q < n; q++ {
77+
q2 := float64(q * q)
78+
s := ((input[q] + q2) -
79+
(input[paras[k]] + float64(paras[k]*paras[k]))) /
80+
float64(2*(q-paras[k]))
81+
82+
for s <= ranges[k] {
83+
k--
84+
s = ((input[q] + q2) -
85+
(input[paras[k]] + float64(paras[k]*paras[k]))) /
86+
float64(2*(q-paras[k]))
87+
}
88+
89+
k++
90+
paras[k] = q
91+
ranges[k] = s
92+
ranges[k+1] = math.Inf(1)
93+
}
94+
95+
k = 0
96+
for q := range n {
97+
for ranges[k+1] < float64(q) {
98+
k++
99+
}
100+
v := q - paras[k]
101+
result[q] = float64(v*v) + input[paras[k]]
102+
}
103+
}
104+
105+
// Implementation of:
106+
//
107+
// Distance Transforms of Sampled Functions
108+
// by Pedro F. Felzenszwalb and Daniel P. Huttenlocher
109+
// https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf
110+
func ToSDF(tex Texture[bool]) Texture[float64] {
111+
w, h := tex.width, tex.height
112+
n := w * h
113+
114+
// Output
115+
sdf := Texture[float64]{
116+
width: w,
117+
height: h,
118+
data: make([]float64, n),
119+
}
120+
121+
// --- Boundary detection ---
122+
isBoundary := func(x, y int) bool {
123+
if !tex.data[y*w+x] {
124+
return false
125+
}
126+
for dy := -1; dy <= 1; dy++ {
127+
for dx := -1; dx <= 1; dx++ {
128+
if dx == 0 && dy == 0 {
129+
continue
130+
}
131+
nx, ny := x+dx, y+dy
132+
if nx < 0 || ny < 0 || nx >= w || ny >= h {
133+
return true
134+
}
135+
if !tex.data[ny*w+nx] {
136+
return true
137+
}
138+
}
139+
}
140+
return false
141+
}
142+
143+
// Distance buffers
144+
inside := make([]float64, n)
145+
for y := range h {
146+
for x := range w {
147+
i := y*w + x
148+
if isBoundary(x, y) {
149+
inside[i] = 0
150+
} else {
151+
inside[i] = math.MaxFloat64
152+
}
153+
}
154+
}
155+
156+
// --- 2D EDT ---
157+
edt2D := func(grid []float64) []float64 {
158+
tmp := make([]float64, n)
159+
out := make([]float64, n)
160+
161+
// Rows
162+
for y := range h {
163+
row := grid[y*w : (y+1)*w]
164+
edt1D(row, tmp[y*w:], w)
165+
}
166+
167+
// Columns
168+
result := make([]float64, max(w, h))
169+
col := make([]float64, h)
170+
for x := range w {
171+
for y := range h {
172+
col[y] = tmp[y*w+x]
173+
}
174+
edt1D(col, result, h)
175+
for y := range h {
176+
out[y*w+x] = math.Sqrt(result[y])
177+
}
178+
}
179+
180+
return out
181+
}
182+
183+
// Compute distances
184+
dist := edt2D(inside)
185+
186+
// --- Combine into signed field ---
187+
for i := range n {
188+
if tex.data[i] {
189+
sdf.data[i] = -dist[i]
190+
} else {
191+
sdf.data[i] = dist[i]
192+
}
193+
}
194+
195+
return sdf
196+
}

drawing/texturing/texture.go

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -372,29 +372,82 @@ func (n FromArrayNode[T]) Texture(out *nodes.StructOutput[Texture[T]]) {
372372
}
373373

374374
// ============================================================================
375-
type SelectNode[T any] struct {
376-
Texture nodes.Output[Texture[T]]
375+
376+
func selectArray[T any](out *nodes.StructOutput[[]T], texture nodes.Output[Texture[T]]) {
377+
if texture == nil {
378+
return
379+
}
380+
out.Set(nodes.GetOutputValue(out, texture).data)
381+
}
382+
383+
func selectWidth[T any](out *nodes.StructOutput[int], texture nodes.Output[Texture[T]]) {
384+
if texture == nil {
385+
return
386+
}
387+
out.Set(nodes.GetOutputValue(out, texture).Width())
388+
}
389+
390+
func selectHeight[T any](out *nodes.StructOutput[int], texture nodes.Output[Texture[T]]) {
391+
if texture == nil {
392+
return
393+
}
394+
out.Set(nodes.GetOutputValue(out, texture).Height())
395+
}
396+
397+
type SelectNode[T any] struct{ Texture nodes.Output[Texture[T]] }
398+
399+
func (n SelectNode[T]) Array(out *nodes.StructOutput[[]T]) { selectArray(out, n.Texture) }
400+
func (n SelectNode[T]) Width(out *nodes.StructOutput[int]) { selectWidth(out, n.Texture) }
401+
func (n SelectNode[T]) Height(out *nodes.StructOutput[int]) { selectHeight(out, n.Texture) }
402+
403+
type SelectColorNode struct {
404+
Texture nodes.Output[Texture[coloring.Color]]
405+
}
406+
407+
func (n SelectColorNode) Width(out *nodes.StructOutput[int]) { selectWidth(out, n.Texture) }
408+
func (n SelectColorNode) Height(out *nodes.StructOutput[int]) { selectHeight(out, n.Texture) }
409+
func (n SelectColorNode) Array(out *nodes.StructOutput[[]coloring.Color]) {
410+
selectArray(out, n.Texture)
411+
}
412+
413+
func (n SelectColorNode) R(out *nodes.StructOutput[Texture[float64]]) {
414+
if n.Texture == nil {
415+
return
416+
}
417+
tex := nodes.GetOutputValue(out, n.Texture)
418+
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
419+
return c.R
420+
}))
377421
}
378422

379-
func (n SelectNode[T]) Array(out *nodes.StructOutput[[]T]) {
423+
func (n SelectColorNode) G(out *nodes.StructOutput[Texture[float64]]) {
380424
if n.Texture == nil {
381425
return
382426
}
383-
out.Set(nodes.GetOutputValue(out, n.Texture).data)
427+
tex := nodes.GetOutputValue(out, n.Texture)
428+
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
429+
return c.G
430+
}))
384431
}
385432

386-
func (n SelectNode[T]) Width(out *nodes.StructOutput[int]) {
433+
func (n SelectColorNode) B(out *nodes.StructOutput[Texture[float64]]) {
387434
if n.Texture == nil {
388435
return
389436
}
390-
out.Set(nodes.GetOutputValue(out, n.Texture).Width())
437+
tex := nodes.GetOutputValue(out, n.Texture)
438+
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
439+
return c.B
440+
}))
391441
}
392442

393-
func (n SelectNode[T]) Height(out *nodes.StructOutput[int]) {
443+
func (n SelectColorNode) A(out *nodes.StructOutput[Texture[float64]]) {
394444
if n.Texture == nil {
395445
return
396446
}
397-
out.Set(nodes.GetOutputValue(out, n.Texture).Height())
447+
tex := nodes.GetOutputValue(out, n.Texture)
448+
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
449+
return c.A
450+
}))
398451
}
399452

400453
// ============================================================================

0 commit comments

Comments
 (0)