diff --git a/cmd/polyform/main.go b/cmd/polyform/main.go index 761efda6..e34017c4 100644 --- a/cmd/polyform/main.go +++ b/cmd/polyform/main.go @@ -63,6 +63,7 @@ import ( _ "github.com/EliCDavis/polyform/modeling/voxelize" _ "github.com/EliCDavis/polyform/nodes/experimental" + _ "github.com/EliCDavis/polyform/nodes/opearations" ) func main() { diff --git a/drawing/texturing/pattern/nodes.go b/drawing/texturing/pattern/nodes.go index 31c5511e..ae8f2518 100644 --- a/drawing/texturing/pattern/nodes.go +++ b/drawing/texturing/pattern/nodes.go @@ -30,5 +30,11 @@ func init() { refutil.RegisterType[nodes.Struct[CircleNode[bool]]](factory) refutil.RegisterType[nodes.Struct[CircleNode[coloring.Color]]](factory) + refutil.RegisterType[nodes.Struct[RectanglesNode[float64]]](factory) + refutil.RegisterType[nodes.Struct[RectanglesNode[vector2.Float64]]](factory) + refutil.RegisterType[nodes.Struct[RectanglesNode[vector3.Float64]]](factory) + refutil.RegisterType[nodes.Struct[RectanglesNode[bool]]](factory) + refutil.RegisterType[nodes.Struct[RectanglesNode[coloring.Color]]](factory) + generator.RegisterTypes(factory) } diff --git a/drawing/texturing/pattern/rectangle.go b/drawing/texturing/pattern/rectangle.go new file mode 100644 index 00000000..324c7a4e --- /dev/null +++ b/drawing/texturing/pattern/rectangle.go @@ -0,0 +1,56 @@ +package pattern + +import ( + "github.com/EliCDavis/polyform/drawing/texturing" + "github.com/EliCDavis/polyform/nodes" + "github.com/EliCDavis/vector/vector2" +) + +type RectanglesNode[T any] struct { + Positions nodes.Output[[]vector2.Float64] + Value nodes.Output[T] + Size nodes.Output[vector2.Float64] + In nodes.Output[texturing.Texture[T]] +} + +func (node RectanglesNode[T]) Out(out *nodes.StructOutput[texturing.Texture[T]]) { + if node.In == nil { + out.CaptureError(nodes.UnsetInputError{ + Input: node.In, + }) + return + } + + inputTex := nodes.GetOutputValue(out, node.In) + imgSize := vector2.New(inputTex.Width()-1, inputTex.Height()-1).ToFloat64() + size := nodes.TryGetOutputValue(out, node.Size, vector2.New(0.5, 0.5)).Scale(0.5) + + var val T + if node.Value != nil { + val = nodes.GetOutputValue(out, node.Value) + } + + outputTex := inputTex.Copy() + positions := nodes.GetOutputValue(out, node.Positions) + for _, pos := range positions { + start := pos. + Sub(size). + Clamp(0, 1). + MultByVector(imgSize). + RoundToInt() + + end := pos. + Add(size). + Clamp(0, 1). + MultByVector(imgSize). + RoundToInt() + + for y := start.Y(); y < end.Y(); y++ { + for x := start.X(); x < end.X(); x++ { + outputTex.Set(x, y, val) + } + } + } + + out.Set(outputTex) +} diff --git a/formats/gltf/nodes.go b/formats/gltf/nodes.go index 8a7f598a..57d7e67d 100644 --- a/formats/gltf/nodes.go +++ b/formats/gltf/nodes.go @@ -330,6 +330,7 @@ type MaterialNode struct { MetallicFactor nodes.Output[float64] `description:"The factor for the metalness of the material. This value defines a linear multiplier for the sampled metalness values of the metallic-roughness texture."` RoughnessFactor nodes.Output[float64] `description:"The factor for the roughness of the material. This value defines a linear multiplier for the sampled roughness values of the metallic-roughness texture."` MetallicRoughnessTexture nodes.Output[PolyformTexture] `description:"The metallic-roughness texture. The metalness values are sampled from the B channel. The roughness values are sampled from the G channel. These values MUST be encoded with a linear transfer function. If other channels are present (R or A), they MUST be ignored for metallic-roughness calculations. When undefined, the texture MUST be sampled as having 1.0 in G and B components."` + EmissiveTexture nodes.Output[PolyformTexture] `description:"The emissive texture. It controls the color and intensity of the light being emitted by the material. This texture contains RGB components encoded with the sRGB transfer function. If a fourth component (A) is present, it MUST be ignored. When undefined, the texture MUST be sampled as having 1.0 in RGB components."` EmissiveFactor nodes.Output[coloring.Color] `description:"The factors for the emissive color of the material. This value defines linear multipliers for the sampled texels of the emissive texture."` NormalTexture nodes.Output[PolyformNormal] `description:"The tangent space normal texture. The texture encodes RGB components with linear transfer function. Each texel represents the XYZ components of a normal vector in tangent space. The normal vectors use the convention +X is right and +Y is up. +Z points toward the viewer. If a fourth component (A) is present, it **MUST** be ignored. When undefined, the material does not have a tangent space normal texture."` @@ -360,6 +361,14 @@ func (gmnd MaterialNode) Out(out *nodes.StructOutput[PolyformMaterial]) { } } + var emissiveTexture *PolyformTexture + if gmnd.EmissiveTexture != nil { + tex := nodes.GetOutputReference(out, gmnd.EmissiveTexture) + if tex.canAddToGLTF() { + emissiveTexture = tex + } + } + if gmnd.MetallicRoughnessTexture != nil { tex := nodes.GetOutputReference(out, gmnd.MetallicRoughnessTexture) if tex.canAddToGLTF() { @@ -431,6 +440,7 @@ func (gmnd MaterialNode) Out(out *nodes.StructOutput[PolyformMaterial]) { Extensions: extensions, EmissiveFactor: emissiveFactor, NormalTexture: normalTex, + EmissiveTexture: emissiveTexture, }) } diff --git a/math/nodes.go b/math/nodes.go index fa20976c..58d6ea46 100644 --- a/math/nodes.go +++ b/math/nodes.go @@ -248,6 +248,11 @@ func (cn SquareNode) Out(out *nodes.StructOutput[float64]) { out.Set(v * v) } +func (cn SquareNode) Int(out *nodes.StructOutput[int]) { + v := nodes.TryGetOutputValue(out, cn.In, 0) + out.Set(int(math.Round(v * v))) +} + type SquareRootNode struct { In nodes.Output[float64] } diff --git a/math/sdf/rounded_cone.go b/math/sdf/rounded_cone.go index c6b4e551..974a0c91 100644 --- a/math/sdf/rounded_cone.go +++ b/math/sdf/rounded_cone.go @@ -31,6 +31,11 @@ func RoundedCone(a, b vector3.Float64, r1, r2 float64) sample.Vec3ToFloat { // sampling independent computations (only depend on shape) ba := b.Sub(a) l2 := ba.Dot(ba) + + if l2 == 0 { + return Sphere(a, max(r1, r2)) + } + rr := r1 - r2 rrr := rr * rr signRRR := sign(rr) * rrr diff --git a/math/sequence/nodes.go b/math/sequence/nodes.go index 2e2c2920..081083e0 100644 --- a/math/sequence/nodes.go +++ b/math/sequence/nodes.go @@ -10,6 +10,8 @@ func init() { factory := &refutil.TypeFactory{} refutil.RegisterType[nodes.Struct[LinearNode]](factory) + refutil.RegisterType[nodes.Struct[RandomFloatNode]](factory) + refutil.RegisterType[nodes.Struct[RandomBoolNode]](factory) generator.RegisterTypes(factory) } diff --git a/math/sequence/random.go b/math/sequence/random.go new file mode 100644 index 00000000..22b90f07 --- /dev/null +++ b/math/sequence/random.go @@ -0,0 +1,50 @@ +package sequence + +import ( + "math/rand/v2" + + "github.com/EliCDavis/polyform/nodes" +) + +type RandomFloatNode struct { + Min nodes.Output[float64] + Max nodes.Output[float64] + Samples nodes.Output[int] +} + +func (snd RandomFloatNode) Out(out *nodes.StructOutput[[]float64]) { + minV := nodes.TryGetOutputValue(out, snd.Min, 0.) + maxV := nodes.TryGetOutputValue(out, snd.Max, 1.) + rangeV := maxV - minV + + samples := max(nodes.TryGetOutputValue(out, snd.Samples, 0), 0) + + seed1 := uint64(12345) + seed2 := uint64(67890) + rnd := rand.New(rand.NewPCG(seed1, seed2)) + arr := make([]float64, samples) + for i := range samples { + v := minV + (rnd.Float64() * rangeV) + arr[i] = v + } + + out.Set(arr) +} + +type RandomBoolNode struct { + Samples nodes.Output[int] +} + +func (snd RandomBoolNode) Out(out *nodes.StructOutput[[]bool]) { + samples := max(nodes.TryGetOutputValue(out, snd.Samples, 0), 0) + + seed1 := uint64(12345) + seed2 := uint64(67890) + rnd := rand.New(rand.NewPCG(seed1, seed2)) + arr := make([]bool, samples) + for i := range samples { + arr[i] = rnd.Float64() > 0.5 + } + + out.Set(arr) +} diff --git a/math/vector2/nodes.go b/math/vector2/nodes.go index f7a01431..034a3f78 100644 --- a/math/vector2/nodes.go +++ b/math/vector2/nodes.go @@ -30,5 +30,13 @@ func init() { refutil.RegisterType[nodes.Struct[SelectArray[int]]](factory) refutil.RegisterType[nodes.Struct[SelectArray[float64]]](factory) + refutil.RegisterType[nodes.Struct[Subtract[int]]](factory) + refutil.RegisterType[nodes.Struct[Subtract[float64]]](factory) + refutil.RegisterType[nodes.Struct[SubtractToArrayNode[int]]](factory) + refutil.RegisterType[nodes.Struct[SubtractToArrayNode[float64]]](factory) + + refutil.RegisterType[nodes.Struct[Scale[int]]](factory) + refutil.RegisterType[nodes.Struct[Scale[float64]]](factory) + generator.RegisterTypes(factory) } diff --git a/math/vector2/scale.go b/math/vector2/scale.go new file mode 100644 index 00000000..e1c956f2 --- /dev/null +++ b/math/vector2/scale.go @@ -0,0 +1,22 @@ +package vector2 + +import ( + "github.com/EliCDavis/polyform/nodes" + "github.com/EliCDavis/vector" + "github.com/EliCDavis/vector/vector2" +) + +type Scale[T vector.Number] struct { + Vector nodes.Output[vector2.Vector[T]] `description:"The vector to scale"` + Amount nodes.Output[float64] `description:"The amount the scale by (defaults to 1.0)"` +} + +func (cn Scale[T]) Float64(out *nodes.StructOutput[vector2.Float64]) { + vec := nodes.TryGetOutputValue(out, cn.Vector, vector2.Zero[T]()) + out.Set(vec.ToFloat64().Scale(nodes.TryGetOutputValue(out, cn.Amount, 1))) +} + +func (cn Scale[T]) Int(out *nodes.StructOutput[vector2.Int]) { + vec := nodes.TryGetOutputValue(out, cn.Vector, vector2.Zero[T]()) + out.Set(vec.ToFloat64().Scale(nodes.TryGetOutputValue(out, cn.Amount, 1)).RoundToInt()) +} diff --git a/math/vector2/sub.go b/math/vector2/sub.go new file mode 100644 index 00000000..08627731 --- /dev/null +++ b/math/vector2/sub.go @@ -0,0 +1,42 @@ +package vector2 + +import ( + "github.com/EliCDavis/polyform/nodes" + "github.com/EliCDavis/vector" + "github.com/EliCDavis/vector/vector2" +) + +type Subtract[T vector.Number] struct { + A nodes.Output[vector2.Vector[T]] + B nodes.Output[vector2.Vector[T]] +} + +func (d Subtract[T]) Out(out *nodes.StructOutput[vector2.Vector[T]]) { + a := nodes.TryGetOutputValue(out, d.A, vector2.Zero[T]()) + b := nodes.TryGetOutputValue(out, d.B, vector2.Zero[T]()) + out.Set(a.Sub(b)) +} + +type SubtractToArrayNode[T vector.Number] struct { + Amount nodes.Output[vector2.Vector[T]] + Array nodes.Output[[]vector2.Vector[T]] +} + +func (cn SubtractToArrayNode[T]) Out(out *nodes.StructOutput[[]vector2.Vector[T]]) { + if cn.Array == nil { + return + } + + original := nodes.GetOutputValue(out, cn.Array) + if cn.Amount == nil { + out.Set(original) + return + } + + amount := nodes.GetOutputValue(out, cn.Amount) + total := make([]vector2.Vector[T], len(original)) + for i, v := range original { + total[i] = v.Sub(amount) + } + out.Set(total) +} diff --git a/modeling/marching/march.go b/modeling/marching/march.go index 7cfb871c..9b59043e 100644 --- a/modeling/marching/march.go +++ b/modeling/marching/march.go @@ -259,7 +259,7 @@ func marchRecurse(field sample.Vec3ToFloat, bounds geometry.AABB, cubeSize, surf // TODO: WE THIS IS OUR BIGGEST SPEEDUP, FIGURE OUT HOW TO PRUNE HARDER // The closest surface is not within the bounds - if math.Abs(fieldResult) > (diagonal/2)+(cubeSize)+center.Distance(recentered) { + if math.Abs(fieldResult) > (diagonal*2)+(cubeSize)+center.Distance(recentered) { return } diff --git a/modeling/primitives/circle.go b/modeling/primitives/circle.go index cd213849..7b2a0f4a 100644 --- a/modeling/primitives/circle.go +++ b/modeling/primitives/circle.go @@ -64,8 +64,12 @@ func (c Circle) ToMesh() modeling.Mesh { uvs := make([]vector2.Float64, c.Sides+1) for sideIndex := 0; sideIndex < c.Sides; sideIndex++ { angle := angleIncrement * float64(sideIndex) - uvs[sideIndex] = vector2.New(math.Cos(angle), math.Sin(angle)).Normalized().Scale(c.UVs.Radius) + uvs[sideIndex] = vector2.New(math.Cos(angle), math.Sin(angle)). + Normalized(). + Scale(c.UVs.Radius). + Add(c.UVs.Center) } + uvs[topMiddleVert] = c.UVs.Center meshV2Data[modeling.TexCoordAttribute] = uvs } diff --git a/modeling/primitives/cube.go b/modeling/primitives/cube.go index 20e85604..4d585a88 100644 --- a/modeling/primitives/cube.go +++ b/modeling/primitives/cube.go @@ -128,7 +128,7 @@ func (c Cube) UnweldedQuads() modeling.Mesh { halfW := c.Width / 2 halfH := c.Height / 2 halfD := c.Depth / 2 - var topUV, bottomUV, leftUV, rightUV, frontUV, backUV EuclideanUVSpace + var topUV, bottomUV, leftUV, rightUV, frontUV, backUV EuclideanUVSpace = nil, nil, nil, nil, nil, nil if c.UVs != nil { topUV = c.UVs.Top bottomUV = c.UVs.Bottom @@ -321,12 +321,31 @@ type CubeUVsNode struct { } func (cnd CubeUVsNode) Uv(out *nodes.StructOutput[CubeUVs]) { - out.Set(CubeUVs{ - Top: nodes.TryGetOutputReference(out, cnd.Top, nil), - Bottom: nodes.TryGetOutputReference(out, cnd.Bottom, nil), - Left: nodes.TryGetOutputReference(out, cnd.Left, nil), - Right: nodes.TryGetOutputReference(out, cnd.Right, nil), - Front: nodes.TryGetOutputReference(out, cnd.Front, nil), - Back: nodes.TryGetOutputReference(out, cnd.Back, nil), - }) + uvs := CubeUVs{} + + if cnd.Top != nil { + uvs.Top = nodes.GetOutputValue(out, cnd.Top) + } + + if cnd.Bottom != nil { + uvs.Bottom = nodes.GetOutputValue(out, cnd.Bottom) + } + + if cnd.Left != nil { + uvs.Left = nodes.GetOutputValue(out, cnd.Left) + } + + if cnd.Right != nil { + uvs.Right = nodes.GetOutputValue(out, cnd.Right) + } + + if cnd.Front != nil { + uvs.Front = nodes.GetOutputValue(out, cnd.Front) + } + + if cnd.Back != nil { + uvs.Back = nodes.GetOutputValue(out, cnd.Back) + } + + out.Set(uvs) } diff --git a/nodes/opearations/nodes.go b/nodes/opearations/nodes.go new file mode 100644 index 00000000..cf0f7517 --- /dev/null +++ b/nodes/opearations/nodes.go @@ -0,0 +1,23 @@ +package opearations + +import ( + "github.com/EliCDavis/polyform/generator" + "github.com/EliCDavis/polyform/math/quaternion" + "github.com/EliCDavis/polyform/math/trs" + "github.com/EliCDavis/polyform/nodes" + "github.com/EliCDavis/polyform/refutil" + "github.com/EliCDavis/vector/vector2" + "github.com/EliCDavis/vector/vector3" +) + +func init() { + factory := &refutil.TypeFactory{} + + refutil.RegisterType[nodes.Struct[SeperateNode[float64]]](factory) + refutil.RegisterType[nodes.Struct[SeperateNode[vector2.Float64]]](factory) + refutil.RegisterType[nodes.Struct[SeperateNode[vector3.Float64]]](factory) + refutil.RegisterType[nodes.Struct[SeperateNode[trs.TRS]]](factory) + refutil.RegisterType[nodes.Struct[SeperateNode[quaternion.Quaternion]]](factory) + + generator.RegisterTypes(factory) +} diff --git a/nodes/opearations/seperate.go b/nodes/opearations/seperate.go new file mode 100644 index 00000000..a5c0dc88 --- /dev/null +++ b/nodes/opearations/seperate.go @@ -0,0 +1,44 @@ +package opearations + +import "github.com/EliCDavis/polyform/nodes" + +func Seperate[T any](in []T, keep []bool) (kept, removed []T) { + keptLen := 0 + removedLen := 0 + seperated := make([]T, len(in)) + + for i, v := range in { + if i < len(keep) && keep[i] { + seperated[keptLen] = v + keptLen++ + } else { + seperated[len(in)-removedLen-1] = v + removedLen++ + } + } + + kept = seperated[:keptLen] + removed = seperated[keptLen:] + return +} + +type SeperateNode[T any] struct { + Array nodes.Output[[]T] + Selection nodes.Output[[]bool] +} + +func (node SeperateNode[T]) Selected(out *nodes.StructOutput[[]T]) { + kept, _ := Seperate( + nodes.TryGetOutputValue(out, node.Array, nil), + nodes.TryGetOutputValue(out, node.Selection, nil), + ) + out.Set(kept) +} + +func (node SeperateNode[T]) Removed(out *nodes.StructOutput[[]T]) { + _, removed := Seperate( + nodes.TryGetOutputValue(out, node.Array, nil), + nodes.TryGetOutputValue(out, node.Selection, nil), + ) + out.Set(removed) +} diff --git a/nodes/opearations/seperate_test.go b/nodes/opearations/seperate_test.go new file mode 100644 index 00000000..1c2bfe84 --- /dev/null +++ b/nodes/opearations/seperate_test.go @@ -0,0 +1,49 @@ +package opearations_test + +import ( + "testing" + + "github.com/EliCDavis/polyform/nodes/opearations" + "github.com/stretchr/testify/assert" +) + +func TestSeperate(t *testing.T) { + + tests := map[string]struct { + in []int + keep []bool + keptResult []int + removedResult []int + }{ + "basic": { + in: []int{1, 2, 3, 4}, + keep: []bool{true, false, true, false}, + keptResult: []int{1, 3}, + removedResult: []int{4, 2}, + }, + "lacking keep": { + in: []int{1, 2, 3, 4}, + keep: []bool{true, false}, + keptResult: []int{1}, + removedResult: []int{4, 3, 2}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + kept, removed := opearations.Seperate(tc.in, tc.keep) + if assert.Len(t, kept, len(tc.keptResult)) { + for i, v := range kept { + assert.Equal(t, tc.keptResult[i], v) + } + } + + if assert.Len(t, removed, len(tc.removedResult)) { + for i, v := range removed { + assert.Equal(t, tc.removedResult[i], v) + } + } + }) + } + +}