Skip to content
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
2 changes: 2 additions & 0 deletions cmd/polyform/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ import (
_ "github.com/EliCDavis/polyform/math/noise"
_ "github.com/EliCDavis/polyform/math/quaternion"
_ "github.com/EliCDavis/polyform/math/sdf"
_ "github.com/EliCDavis/polyform/math/sequence"
_ "github.com/EliCDavis/polyform/math/trig"
_ "github.com/EliCDavis/polyform/math/trs"
_ "github.com/EliCDavis/polyform/math/unit"
_ "github.com/EliCDavis/polyform/math/vector2"
_ "github.com/EliCDavis/polyform/math/vector3"

_ "github.com/EliCDavis/polyform/modeling"
_ "github.com/EliCDavis/polyform/modeling/animation"
_ "github.com/EliCDavis/polyform/modeling/extrude"
_ "github.com/EliCDavis/polyform/modeling/marching"
_ "github.com/EliCDavis/polyform/modeling/meshops"
Expand Down
4 changes: 4 additions & 0 deletions docs/resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ Resources either directly contributing to the code, or are just interesting find
- [SDFs](https://iquilezles.org/articles/distfunctions/)
- [Fast 2D SDF](https://stackoverflow.com/questions/68178747/fast-2d-signed-distance)
- [Got Subtraction from Here](https://www.ronja-tutorials.com/post/035-2d-sdf-combination/)
- SDFs from images
- [_"Distance Transforms of Sampled Functions"_ by Pedro F. Felzenszwalb and Daniel P. Huttenlocher](https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf)
- [The Fast Euclidean Distance Transform](https://hellorob.org/files/lectures/fast_euclidean_dt.pdf)
- [_"Euclidean Distance Transform (EDT) — An Introduction"_ by Kareim Tarek](https://medium.com/@kareimtarek1972/euclidean-distance-transform-edt-introduction-5d7d7c144aa)
- Collisions
- [Closest point on Triangle](https://gdbooks.gitbooks.io/3dcollisions/content/Chapter4/closest_point_to_triangle.html)
- Ray Tracing
Expand Down
47 changes: 47 additions & 0 deletions drawing/coloring/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ func init() {
refutil.RegisterType[nodes.Struct[GradientKeyNode[vector4.Float64]]](factory)
refutil.RegisterType[nodes.Struct[GradientKeyNode[Color]]](factory)

refutil.RegisterType[nodes.Struct[RedNode]](factory)
refutil.RegisterType[nodes.Struct[GreenNode]](factory)
refutil.RegisterType[nodes.Struct[BlueNode]](factory)
refutil.RegisterType[nodes.Struct[MagentaNode]](factory)
refutil.RegisterType[nodes.Struct[CyanNode]](factory)
refutil.RegisterType[nodes.Struct[YellowNode]](factory)
refutil.RegisterType[nodes.Struct[WhiteNode]](factory)
refutil.RegisterType[nodes.Struct[BlackNode]](factory)

generator.RegisterTypes(factory)
}

Expand Down Expand Up @@ -230,3 +239,41 @@ func (n GradientKeyNode[T]) Gradient(out *nodes.StructOutput[GradientKey[T]]) {
Value: val,
})
}

// ============================================================================

type RedNode struct{}

func (n RedNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{1, 0, 0, 1}) }

type GreenNode struct{}

func (n GreenNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 1, 0, 1}) }

type BlueNode struct{}

func (n BlueNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 0, 1, 1}) }

type YellowNode struct{}

func (n YellowNode) Color(out *nodes.StructOutput[Color]) {
out.Set(Color{1, 1, 0, 1})
}

type MagentaNode struct{}

func (n MagentaNode) Color(out *nodes.StructOutput[Color]) {
out.Set(Color{1, 0, 1, 1})
}

type CyanNode struct{}

func (n CyanNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 1, 1, 1}) }

type BlackNode struct{}

func (n BlackNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{0, 0, 0, 1}) }

type WhiteNode struct{}

func (n WhiteNode) Color(out *nodes.StructOutput[Color]) { out.Set(Color{1, 1, 1, 1}) }
7 changes: 6 additions & 1 deletion drawing/texturing/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@ func init() {
refutil.RegisterType[nodes.Struct[FromArrayNode[bool]]](factory)
refutil.RegisterType[nodes.Struct[FromArrayNode[coloring.Color]]](factory)

refutil.RegisterType[nodes.Struct[FromImageNode]](factory)

refutil.RegisterType[nodes.Struct[SelectNode[float64]]](factory)
refutil.RegisterType[nodes.Struct[SelectNode[vector2.Float64]]](factory)
refutil.RegisterType[nodes.Struct[SelectNode[vector3.Float64]]](factory)
refutil.RegisterType[nodes.Struct[SelectNode[bool]]](factory)
refutil.RegisterType[nodes.Struct[SelectNode[coloring.Color]]](factory)
refutil.RegisterType[nodes.Struct[SelectColorNode]](factory)

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

refutil.RegisterType[nodes.Struct[MaskToSDFNode]](factory)
refutil.RegisterType[nodes.Struct[SampleSDFNode]](factory)

refutil.RegisterType[nodes.Struct[RadialGradientNode[float64]]](factory)
refutil.RegisterType[nodes.Struct[RadialGradientNode[vector2.Float64]]](factory)
refutil.RegisterType[nodes.Struct[RadialGradientNode[vector3.Float64]]](factory)
Expand Down
196 changes: 196 additions & 0 deletions drawing/texturing/sdf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package texturing

import (
"math"

"github.com/EliCDavis/polyform/math/geometry"
"github.com/EliCDavis/polyform/math/sample"
"github.com/EliCDavis/polyform/nodes"
"github.com/EliCDavis/vector/vector2"
"github.com/EliCDavis/vector/vector3"
)

type SampleSDFNode struct {
Texture nodes.Output[Texture[float64]]
Size nodes.Output[vector3.Float64]
}

func (n SampleSDFNode) SDF(out *nodes.StructOutput[sample.Vec3ToFloat]) {
if n.Texture == nil {
return
}
tex := nodes.GetOutputValue(out, n.Texture)
size := nodes.TryGetOutputValue(out, n.Size, vector3.One[float64]())
bounds := geometry.NewAABB(vector3.Zero[float64](), size)
half := size.Scale(0.5)
out.Set(func(f vector3.Float64) float64 {
v := bounds.ClosestPoint(f)
p := v.XZ().
DivByVector(half.XZ()).
Scale(0.5).
Add(vector2.Fill(0.5))

t := tex.UV(p.X(), p.Y())
return t + f.Distance(v)
})
}

type MaskToSDFNode struct {
Mask nodes.Output[Texture[bool]]
}

func (n MaskToSDFNode) SDF(out *nodes.StructOutput[Texture[float64]]) {
if n.Mask == nil {
return
}
out.Set(ToSDF(nodes.GetOutputValue(out, n.Mask)))
}

func (n MaskToSDFNode) NormalizedSDF(out *nodes.StructOutput[Texture[float64]]) {
if n.Mask == nil {
return
}

sdf := ToSDF(nodes.GetOutputValue(out, n.Mask))

maxV := -math.MaxFloat64
for _, v := range sdf.data {
maxV = max(maxV, v)
}

sdf.MutateParallel(func(x, y int, v float64) float64 {
return v / maxV
})

out.Set(sdf)
}

func edt1D(input, result []float64, n int) {
paras := make([]int, n)
ranges := make([]float64, n+1)

k := 0
ranges[0] = math.Inf(-1)
ranges[1] = math.Inf(1)

for q := 1; q < n; q++ {
q2 := float64(q * q)
s := ((input[q] + q2) -
(input[paras[k]] + float64(paras[k]*paras[k]))) /
float64(2*(q-paras[k]))

for s <= ranges[k] {
k--
s = ((input[q] + q2) -
(input[paras[k]] + float64(paras[k]*paras[k]))) /
float64(2*(q-paras[k]))
}

k++
paras[k] = q
ranges[k] = s
ranges[k+1] = math.Inf(1)
}

k = 0
for q := range n {
for ranges[k+1] < float64(q) {
k++
}
v := q - paras[k]
result[q] = float64(v*v) + input[paras[k]]
}
}

// Implementation of:
//
// Distance Transforms of Sampled Functions
// by Pedro F. Felzenszwalb and Daniel P. Huttenlocher
// https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf
func ToSDF(tex Texture[bool]) Texture[float64] {
w, h := tex.width, tex.height
n := w * h

// Output
sdf := Texture[float64]{
width: w,
height: h,
data: make([]float64, n),
}

// --- Boundary detection ---
isBoundary := func(x, y int) bool {
if !tex.data[y*w+x] {
return false
}
for dy := -1; dy <= 1; dy++ {
for dx := -1; dx <= 1; dx++ {
if dx == 0 && dy == 0 {
continue
}
nx, ny := x+dx, y+dy
if nx < 0 || ny < 0 || nx >= w || ny >= h {
return true
}
if !tex.data[ny*w+nx] {
return true
}
}
}
return false
}

// Distance buffers
inside := make([]float64, n)
for y := range h {
for x := range w {
i := y*w + x
if isBoundary(x, y) {
inside[i] = 0
} else {
inside[i] = math.MaxFloat64
}
}
}

// --- 2D EDT ---
edt2D := func(grid []float64) []float64 {
tmp := make([]float64, n)
out := make([]float64, n)

// Rows
for y := range h {
row := grid[y*w : (y+1)*w]
edt1D(row, tmp[y*w:], w)
}

// Columns
result := make([]float64, max(w, h))
col := make([]float64, h)
for x := range w {
for y := range h {
col[y] = tmp[y*w+x]
}
edt1D(col, result, h)
for y := range h {
out[y*w+x] = math.Sqrt(result[y])
}
}

return out
}

// Compute distances
dist := edt2D(inside)

// --- Combine into signed field ---
for i := range n {
if tex.data[i] {
sdf.data[i] = -dist[i]
} else {
sdf.data[i] = dist[i]
}
}

return sdf
}
69 changes: 61 additions & 8 deletions drawing/texturing/texture.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,29 +372,82 @@ func (n FromArrayNode[T]) Texture(out *nodes.StructOutput[Texture[T]]) {
}

// ============================================================================
type SelectNode[T any] struct {
Texture nodes.Output[Texture[T]]

func selectArray[T any](out *nodes.StructOutput[[]T], texture nodes.Output[Texture[T]]) {
if texture == nil {
return
}
out.Set(nodes.GetOutputValue(out, texture).data)
}

func selectWidth[T any](out *nodes.StructOutput[int], texture nodes.Output[Texture[T]]) {
if texture == nil {
return
}
out.Set(nodes.GetOutputValue(out, texture).Width())
}

func selectHeight[T any](out *nodes.StructOutput[int], texture nodes.Output[Texture[T]]) {
if texture == nil {
return
}
out.Set(nodes.GetOutputValue(out, texture).Height())
}

type SelectNode[T any] struct{ Texture nodes.Output[Texture[T]] }

func (n SelectNode[T]) Array(out *nodes.StructOutput[[]T]) { selectArray(out, n.Texture) }
func (n SelectNode[T]) Width(out *nodes.StructOutput[int]) { selectWidth(out, n.Texture) }
func (n SelectNode[T]) Height(out *nodes.StructOutput[int]) { selectHeight(out, n.Texture) }

type SelectColorNode struct {
Texture nodes.Output[Texture[coloring.Color]]
}

func (n SelectColorNode) Width(out *nodes.StructOutput[int]) { selectWidth(out, n.Texture) }
func (n SelectColorNode) Height(out *nodes.StructOutput[int]) { selectHeight(out, n.Texture) }
func (n SelectColorNode) Array(out *nodes.StructOutput[[]coloring.Color]) {
selectArray(out, n.Texture)
}

func (n SelectColorNode) R(out *nodes.StructOutput[Texture[float64]]) {
if n.Texture == nil {
return
}
tex := nodes.GetOutputValue(out, n.Texture)
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
return c.R
}))
}

func (n SelectNode[T]) Array(out *nodes.StructOutput[[]T]) {
func (n SelectColorNode) G(out *nodes.StructOutput[Texture[float64]]) {
if n.Texture == nil {
return
}
out.Set(nodes.GetOutputValue(out, n.Texture).data)
tex := nodes.GetOutputValue(out, n.Texture)
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
return c.G
}))
}

func (n SelectNode[T]) Width(out *nodes.StructOutput[int]) {
func (n SelectColorNode) B(out *nodes.StructOutput[Texture[float64]]) {
if n.Texture == nil {
return
}
out.Set(nodes.GetOutputValue(out, n.Texture).Width())
tex := nodes.GetOutputValue(out, n.Texture)
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
return c.B
}))
}

func (n SelectNode[T]) Height(out *nodes.StructOutput[int]) {
func (n SelectColorNode) A(out *nodes.StructOutput[Texture[float64]]) {
if n.Texture == nil {
return
}
out.Set(nodes.GetOutputValue(out, n.Texture).Height())
tex := nodes.GetOutputValue(out, n.Texture)
out.Set(Convert(tex, func(x, y int, c coloring.Color) float64 {
return c.A
}))
}

// ============================================================================
Expand Down
Loading
Loading