Skip to content

Commit 10b0557

Browse files
authored
GLTF Improvements (#120)
* fixing gltf tests to work on windows * Start of instancing refactor * GLTF read now is smart about not decoding textures twice * Automatic GPU instancing now collapsing children nodes * reader now properly interpretting children * GLTF Reader fixes * Can now expand GPU instances into proper gltf nodes using the Expand instancing strategy * nodes now have children array field * Start of loading extensions * Can now read all Material GLTF extensions
1 parent e9252f5 commit 10b0557

33 files changed

+2751
-834
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
- name: Push Nix-cached Release Artifacts
3131
run: |
3232
nix run .#sri-check-up --print-build-logs
33-
nix run .#release --print-build-logs
33+
nix run .#gh-release --print-build-logs
3434
3535
deploy:
3636
runs-on: ubuntu-latest

cmd/polyform/main.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,32 @@ func main() {
102102
}}
103103
})
104104

105+
serialize.Register(nodeSerializer, func(g coloring.Gradient[float64]) manifest.Entry {
106+
width := 100
107+
height := 20
108+
i := image.NewGray(image.Rect(0, 0, width, height))
109+
for x := range width {
110+
v := uint8(g.Sample(float64(x)/100.) * 255.)
111+
for y := range height {
112+
i.SetGray(x, y, color.Gray{Y: v})
113+
}
114+
}
115+
return manifest.Entry{Artifact: basics.Image{Image: i}}
116+
})
117+
118+
serialize.Register(nodeSerializer, func(g coloring.Gradient[coloring.Color]) manifest.Entry {
119+
width := 100
120+
height := 20
121+
i := image.NewRGBA(image.Rect(0, 0, width, height))
122+
for x := range width {
123+
v := g.Sample(float64(x) / 100.)
124+
for y := range height {
125+
i.Set(x, y, v)
126+
}
127+
}
128+
return manifest.Entry{Artifact: basics.Image{Image: i}}
129+
})
130+
105131
app := generator.App{
106132
Name: "Polyform",
107133
Description: "Immutable mesh processing pipelines",

drawing/coloring/operations.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"math"
66
)
77

8-
func MultiplyRGBByConstant(c color.Color, amount float64) color.Color {
8+
func ScaleRGB(c color.Color, amount float64) color.Color {
99
r, g, b, a := c.RGBA()
1010

1111
rVal := math.Round(float64(r>>8) * amount)
@@ -20,7 +20,7 @@ func MultiplyRGBByConstant(c color.Color, amount float64) color.Color {
2020
}
2121
}
2222

23-
func MultiplyColorByConstant(c color.Color, amount float64) color.Color {
23+
func ScaleColor(c color.Color, amount float64) color.Color {
2424
r, g, b, a := c.RGBA()
2525

2626
rVal := math.Round(float64(r>>8) * amount)

drawing/coloring/operations_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func TestAddRGB(t *testing.T) {
158158
}
159159

160160
func TestMultiplyRGBByConstant(t *testing.T) {
161-
scaled := coloring.MultiplyRGBByConstant(color.RGBA{R: 2, G: 100, B: 150, A: 200}, 0.5)
161+
scaled := coloring.ScaleRGB(color.RGBA{R: 2, G: 100, B: 150, A: 200}, 0.5)
162162

163163
assert.True(t, coloring.RedEqual(scaled, 1))
164164
assert.True(t, coloring.GreenEqual(scaled, 50))

drawing/texturing/blur.go

Lines changed: 150 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,32 @@
11
package texturing
22

33
import (
4+
"cmp"
45
"image"
56
"image/color"
7+
"math"
68

79
"github.com/EliCDavis/polyform/drawing/coloring"
10+
"github.com/EliCDavis/polyform/nodes"
11+
"github.com/EliCDavis/vector"
12+
"github.com/EliCDavis/vector/vector1"
13+
"github.com/EliCDavis/vector/vector2"
14+
"github.com/EliCDavis/vector/vector3"
815
)
916

10-
func GaussianBlur(src image.Image) image.Image {
11-
dst := image.NewRGBA(src.Bounds())
12-
ConvolveImage(src, func(x, y int, vals []color.Color) {
13-
x1y1 := coloring.MultiplyRGBByConstant(vals[0], 1./16)
14-
x2y1 := coloring.MultiplyRGBByConstant(vals[1], 2./16)
15-
x3y1 := coloring.MultiplyRGBByConstant(vals[2], 1./16)
16-
17-
x1y2 := coloring.MultiplyRGBByConstant(vals[3], 2./16)
18-
x2y2 := coloring.MultiplyRGBByConstant(vals[4], 4./16)
19-
x3y2 := coloring.MultiplyRGBByConstant(vals[5], 2./16)
20-
21-
x1y3 := coloring.MultiplyRGBByConstant(vals[6], 1./16)
22-
x2y3 := coloring.MultiplyRGBByConstant(vals[7], 2./16)
23-
x3y3 := coloring.MultiplyRGBByConstant(vals[8], 1./16)
24-
25-
dst.Set(x, y, coloring.AddRGB(
26-
x1y1, x2y1, x3y1,
27-
x1y2, x2y2, x3y2,
28-
x1y3, x2y3, x3y3,
29-
))
30-
})
31-
return dst
32-
}
33-
3417
func boxBlur(src image.Image, dst *image.RGBA) {
3518
ConvolveImage(src, func(x, y int, vals []color.Color) {
36-
x1y1 := coloring.MultiplyRGBByConstant(vals[0], 1./9)
37-
x2y1 := coloring.MultiplyRGBByConstant(vals[1], 1./9)
38-
x3y1 := coloring.MultiplyRGBByConstant(vals[2], 1./9)
19+
x1y1 := coloring.ScaleRGB(vals[0], 1./9)
20+
x2y1 := coloring.ScaleRGB(vals[1], 1./9)
21+
x3y1 := coloring.ScaleRGB(vals[2], 1./9)
3922

40-
x1y2 := coloring.MultiplyRGBByConstant(vals[3], 1./9)
41-
x2y2 := coloring.MultiplyRGBByConstant(vals[4], 1./9)
42-
x3y2 := coloring.MultiplyRGBByConstant(vals[5], 1./9)
23+
x1y2 := coloring.ScaleRGB(vals[3], 1./9)
24+
x2y2 := coloring.ScaleRGB(vals[4], 1./9)
25+
x3y2 := coloring.ScaleRGB(vals[5], 1./9)
4326

44-
x1y3 := coloring.MultiplyRGBByConstant(vals[6], 1./9)
45-
x2y3 := coloring.MultiplyRGBByConstant(vals[7], 1./9)
46-
x3y3 := coloring.MultiplyRGBByConstant(vals[8], 1./9)
27+
x1y3 := coloring.ScaleRGB(vals[6], 1./9)
28+
x2y3 := coloring.ScaleRGB(vals[7], 1./9)
29+
x3y3 := coloring.ScaleRGB(vals[8], 1./9)
4730

4831
dst.Set(x, y, coloring.AddRGB(
4932
x1y1, x2y1, x3y1,
@@ -82,3 +65,137 @@ func BoxBlurNTimes(src image.Image, iterations int) image.Image {
8265
}
8366
return dst
8467
}
68+
69+
// Generates a normalized 1D Gaussian kernel
70+
func gaussianKernel(radius int, sigma float64) []float64 {
71+
kernel := make([]float64, 2*radius+1)
72+
var sum float64
73+
74+
sigma2 := sigma * sigma * 2
75+
for i := -radius; i <= radius; i++ {
76+
v := math.Exp(-(float64(i * i)) / sigma2)
77+
kernel[i+radius] = v
78+
sum += v
79+
}
80+
for i := range kernel {
81+
kernel[i] /= sum
82+
}
83+
return kernel
84+
}
85+
86+
func clamp[T cmp.Ordered](i, minimum, maximum T) T {
87+
return max(min(i, maximum), minimum)
88+
}
89+
90+
// Applies a 1D convolution along x or y
91+
func convolve1DGaussian[T any](space vector.Space[T], src Texture[T], dst Texture[T], kernel []float64, horizontal bool) {
92+
radius := len(kernel) / 2
93+
94+
if horizontal {
95+
src.ScanParallel(func(x, y int, v T) {
96+
var accum T
97+
for k := -radius; k <= radius; k++ {
98+
sx := clamp(x+k, 0, src.width-1)
99+
weighted := space.Scale(src.Get(sx, y), kernel[k+radius])
100+
accum = space.Add(accum, weighted)
101+
}
102+
dst.Set(x, y, accum)
103+
})
104+
} else {
105+
src.ScanParallel(func(x, y int, v T) {
106+
var accum T
107+
for k := -radius; k <= radius; k++ {
108+
sy := clamp(y+k, 0, src.height-1)
109+
weighted := space.Scale(src.Get(x, sy), kernel[k+radius])
110+
accum = space.Add(accum, weighted)
111+
}
112+
dst.Set(x, y, accum)
113+
})
114+
}
115+
}
116+
117+
// GaussianBlur applies a Gaussian blur to the texture
118+
func RadialGaussianBlur[T any](space vector.Space[T], src Texture[T], radius int, sigma float64) Texture[T] {
119+
kernel := gaussianKernel(radius, sigma)
120+
121+
tmp := NewTexture[T](src.width, src.height)
122+
out := NewTexture[T](src.width, src.height)
123+
124+
// Horizontal then vertical
125+
convolve1DGaussian(space, src, tmp, kernel, true)
126+
convolve1DGaussian(space, tmp, out, kernel, false)
127+
128+
return out
129+
}
130+
131+
type GaussianBlurFloatNode struct {
132+
Texture nodes.Output[Texture[float64]] `description:"Texture to blur"`
133+
Radius nodes.Output[int] `description:"Size of the kernel in pixels"`
134+
Signma nodes.Output[float64] `description:"standard deviation of the gaussian"`
135+
}
136+
137+
func (n GaussianBlurFloatNode) Value(out *nodes.StructOutput[Texture[float64]]) {
138+
if n.Texture == nil {
139+
return
140+
}
141+
out.Set(RadialGaussianBlur(
142+
vector1.Space[float64]{},
143+
nodes.GetOutputValue(out, n.Texture),
144+
nodes.TryGetOutputValue(out, n.Radius, 1),
145+
nodes.TryGetOutputValue(out, n.Signma, 1),
146+
))
147+
}
148+
149+
type GaussianBlurFloat2Node struct {
150+
Texture nodes.Output[Texture[vector2.Float64]] `description:"Texture to blur"`
151+
Radius nodes.Output[int] `description:"Size of the kernel in pixels"`
152+
Signma nodes.Output[float64] `description:"standard deviation of the gaussian"`
153+
}
154+
155+
func (n GaussianBlurFloat2Node) Value(out *nodes.StructOutput[Texture[vector2.Float64]]) {
156+
if n.Texture == nil {
157+
return
158+
}
159+
out.Set(RadialGaussianBlur(
160+
vector2.Space[float64]{},
161+
nodes.GetOutputValue(out, n.Texture),
162+
nodes.TryGetOutputValue(out, n.Radius, 1),
163+
nodes.TryGetOutputValue(out, n.Signma, 1),
164+
))
165+
}
166+
167+
type GaussianBlurFloat3Node struct {
168+
Texture nodes.Output[Texture[vector3.Float64]] `description:"Texture to blur"`
169+
Radius nodes.Output[int] `description:"Size of the kernel in pixels"`
170+
Signma nodes.Output[float64] `description:"standard deviation of the gaussian"`
171+
}
172+
173+
func (n GaussianBlurFloat3Node) Value(out *nodes.StructOutput[Texture[vector3.Float64]]) {
174+
if n.Texture == nil {
175+
return
176+
}
177+
out.Set(RadialGaussianBlur(
178+
vector3.Space[float64]{},
179+
nodes.GetOutputValue(out, n.Texture),
180+
nodes.TryGetOutputValue(out, n.Radius, 1),
181+
nodes.TryGetOutputValue(out, n.Signma, 1),
182+
))
183+
}
184+
185+
type GaussianBlurColorNode struct {
186+
Texture nodes.Output[Texture[coloring.Color]] `description:"Texture to blur"`
187+
Radius nodes.Output[int] `description:"Size of the kernel in pixels"`
188+
Signma nodes.Output[float64] `description:"standard deviation of the gaussian"`
189+
}
190+
191+
func (n GaussianBlurColorNode) Value(out *nodes.StructOutput[Texture[coloring.Color]]) {
192+
if n.Texture == nil {
193+
return
194+
}
195+
out.Set(RadialGaussianBlur(
196+
coloring.Space{},
197+
nodes.GetOutputValue(out, n.Texture),
198+
nodes.TryGetOutputValue(out, n.Radius, 1),
199+
nodes.TryGetOutputValue(out, n.Signma, 1),
200+
))
201+
}

drawing/texturing/color.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package texturing
2+
3+
import (
4+
"github.com/EliCDavis/polyform/drawing/coloring"
5+
)
6+
7+
func DivideColor(a, b Texture[coloring.Color]) (*Texture[coloring.Color], error) {
8+
if !resolutionsMatch([]Texture[coloring.Color]{a, b}) {
9+
return nil, ErrMismatchDimensions
10+
}
11+
12+
result := NewTexture[coloring.Color](a.width, a.height)
13+
a.ScanParallel(func(x, y int, v coloring.Color) {
14+
other := b.Get(x, y)
15+
result.Set(x, y, coloring.Color{
16+
R: v.R / other.R,
17+
G: v.G / other.G,
18+
B: v.B / other.B,
19+
A: v.A / other.A,
20+
})
21+
})
22+
23+
return &result, nil
24+
}
25+
26+
func MaxColor(a Texture[coloring.Color], other float64) Texture[coloring.Color] {
27+
28+
result := NewTexture[coloring.Color](a.width, a.height)
29+
a.ScanParallel(func(x, y int, v coloring.Color) {
30+
result.Set(x, y, coloring.Color{
31+
R: max(v.R, other),
32+
G: max(v.G, other),
33+
B: max(v.B, other),
34+
A: max(v.A, other),
35+
})
36+
})
37+
38+
return result
39+
}
40+
41+
func ClampColor(a Texture[coloring.Color], minimum, maximum float64) Texture[coloring.Color] {
42+
43+
result := NewTexture[coloring.Color](a.width, a.height)
44+
a.ScanParallel(func(x, y int, v coloring.Color) {
45+
result.Set(x, y, coloring.Color{
46+
R: min(max(v.R, minimum), maximum),
47+
G: min(max(v.G, minimum), maximum),
48+
B: min(max(v.B, minimum), maximum),
49+
A: min(max(v.A, minimum), maximum),
50+
})
51+
})
52+
53+
return result
54+
}

drawing/texturing/errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package texturing
2+
3+
import "errors"
4+
5+
var ErrMismatchDimensions = errors.New("mismatch texture resolutions")

drawing/texturing/float.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ import (
66
"github.com/EliCDavis/polyform/nodes"
77
)
88

9+
func OneMinus(in Texture[float64]) Texture[float64] {
10+
result := NewTexture[float64](in.width, in.height)
11+
for y := range in.height {
12+
for x := range in.width {
13+
result.Set(x, y, 1-in.Get(x, y))
14+
}
15+
}
16+
return result
17+
}
18+
919
type OneMinusNode struct {
1020
Texture nodes.Output[Texture[float64]]
1121
}
@@ -14,17 +24,7 @@ func (n OneMinusNode) Result(out *nodes.StructOutput[Texture[float64]]) {
1424
if n.Texture == nil {
1525
return
1626
}
17-
18-
tex := nodes.GetOutputValue(out, n.Texture)
19-
result := NewTexture[float64](tex.width, tex.height)
20-
21-
for y := range tex.height {
22-
for x := range tex.width {
23-
result.Set(x, y, 1-tex.Get(x, y))
24-
}
25-
}
26-
27-
out.Set(result)
27+
out.Set(OneMinus(nodes.GetOutputValue(out, n.Texture)))
2828
}
2929

3030
type MultiplyFloat1Node struct {

drawing/texturing/nodes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,10 @@ func init() {
7979

8080
refutil.RegisterType[nodes.Struct[DotProductNode]](factory)
8181

82+
refutil.RegisterType[nodes.Struct[GaussianBlurFloatNode]](factory)
83+
refutil.RegisterType[nodes.Struct[GaussianBlurFloat2Node]](factory)
84+
refutil.RegisterType[nodes.Struct[GaussianBlurFloat3Node]](factory)
85+
refutil.RegisterType[nodes.Struct[GaussianBlurColorNode]](factory)
86+
8287
generator.RegisterTypes(factory)
8388
}

0 commit comments

Comments
 (0)