summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-12 20:55:18 -0800
committeryum <yum.food.vr@gmail.com>2026-01-12 20:55:18 -0800
commitbd4d3aa6537cb3121cd1eca4a8ea4eb65eb28522 (patch)
tree021f4296a91ba890a45b7d9cd920bf473c7bb216
parent6d86c9663bab3ec1ef95ba455dfa7281415b7f44 (diff)
Fold: update UI, add plane -> hemioctahedron
-rw-r--r--Scripts/Fold/Editor/FoldPipelineBuilder.cs270
-rw-r--r--Scripts/Fold/Editor/README.md83
-rw-r--r--Scripts/Fold/Editor/VertexDeformationBuilder.cs339
-rw-r--r--Scripts/Fold/Editor/VertexDeformationExamples.cs114
-rw-r--r--vertex.cginc23
-rw-r--r--vertex_deformation.slang71
6 files changed, 416 insertions, 484 deletions
diff --git a/Scripts/Fold/Editor/FoldPipelineBuilder.cs b/Scripts/Fold/Editor/FoldPipelineBuilder.cs
new file mode 100644
index 0000000..64032a5
--- /dev/null
+++ b/Scripts/Fold/Editor/FoldPipelineBuilder.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+public class FoldSlot
+{
+ public int opcode;
+ public float float0, float1, float2, float3;
+ public Vector4 vec0, vec1, vec2, vec3;
+
+ public void ApplyToMaterial(Material mat, int slotIndex)
+ {
+ var prefix = $"_Vertex_Deformation_Slot_{slotIndex}_";
+ mat.SetFloat(prefix + "Enabled", 1f);
+ mat.SetInteger(prefix + "Opcode", opcode);
+ mat.SetFloat(prefix + "Float_0", float0);
+ mat.SetFloat(prefix + "Float_1", float1);
+ mat.SetFloat(prefix + "Float_2", float2);
+ mat.SetFloat(prefix + "Float_3", float3);
+ mat.SetVector(prefix + "Vector_0", vec0);
+ mat.SetVector(prefix + "Vector_1", vec1);
+ mat.SetVector(prefix + "Vector_2", vec2);
+ mat.SetVector(prefix + "Vector_3", vec3);
+ }
+
+ public static void ClearSlot(Material mat, int slotIndex)
+ {
+ var prefix = $"_Vertex_Deformation_Slot_{slotIndex}_";
+ mat.SetFloat(prefix + "Enabled", 0f);
+ mat.SetInteger(prefix + "Opcode", 0);
+ mat.SetFloat(prefix + "Float_0", 0f);
+ mat.SetFloat(prefix + "Float_1", 0f);
+ mat.SetFloat(prefix + "Float_2", 0f);
+ mat.SetFloat(prefix + "Float_3", 0f);
+ mat.SetVector(prefix + "Vector_0", Vector4.zero);
+ mat.SetVector(prefix + "Vector_1", Vector4.zero);
+ mat.SetVector(prefix + "Vector_2", Vector4.zero);
+ mat.SetVector(prefix + "Vector_3", Vector4.zero);
+ }
+}
+
+public class FoldPipelineBuilder
+{
+ readonly List<FoldSlot> slots = new();
+ Material targetMaterial;
+
+ public static class Opcodes
+ {
+ public const int None = 0;
+ public const int TubeToPlane = 1;
+ public const int PlaneToTube = 2;
+ public const int PointAlign = 3;
+ public const int AxisAlign = 4;
+ public const int NormConversion = 5;
+ public const int Seal = 6;
+ public const int SineWaves = 7;
+ public const int FBM = 8;
+ public const int PlaneToHemiOctahedron = 9;
+ }
+
+ FoldPipelineBuilder() { }
+
+ public static FoldPipelineBuilder Create() => new();
+
+ public FoldPipelineBuilder For(Material material)
+ {
+ targetMaterial = material;
+ return this;
+ }
+
+ public FoldPipelineBuilder TubeToPlane(Vector3 p, Vector3 r, Vector3 s, float t)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = Opcodes.TubeToPlane,
+ vec0 = p,
+ vec1 = r,
+ vec2 = s,
+ float0 = t
+ });
+ return this;
+ }
+
+ public FoldPipelineBuilder PlaneToTube(Vector3 p, Vector3 r, Vector3 s, float t)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = Opcodes.PlaneToTube,
+ vec0 = p,
+ vec1 = r,
+ vec2 = s,
+ float0 = t
+ });
+ return this;
+ }
+
+ public FoldPipelineBuilder PlaneToHemiOctahedron(Vector3 p, Vector3 r, Vector3 s, float t)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = Opcodes.PlaneToHemiOctahedron,
+ vec0 = p,
+ vec1 = r,
+ vec2 = s,
+ float0 = t
+ });
+ return this;
+ }
+
+ public FoldPipelineBuilder PointAlign(Vector3 po, Vector3 pp, Vector3 r, float t)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = Opcodes.PointAlign,
+ vec0 = po,
+ vec1 = pp,
+ vec2 = r,
+ float0 = t
+ });
+ return this;
+ }
+
+ public FoldPipelineBuilder AxisAlign(Vector3 po, Vector3 pp, Vector3 r, float t)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = Opcodes.AxisAlign,
+ vec0 = po,
+ vec1 = pp,
+ vec2 = r,
+ float0 = t
+ });
+ return this;
+ }
+
+ public FoldPipelineBuilder NormConversion(float inputK, float outputK, float t)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = Opcodes.NormConversion,
+ float0 = inputK,
+ float1 = outputK,
+ float2 = t
+ });
+ return this;
+ }
+
+ public FoldPipelineBuilder Seal(float A, float k, float st, float t)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = Opcodes.Seal,
+ float0 = A,
+ float1 = k,
+ float2 = st,
+ float3 = t
+ });
+ return this;
+ }
+
+ public FoldPipelineBuilder SineWaves()
+ {
+ slots.Add(new FoldSlot { opcode = Opcodes.SineWaves });
+ return this;
+ }
+
+ public FoldPipelineBuilder FBM()
+ {
+ slots.Add(new FoldSlot { opcode = Opcodes.FBM });
+ return this;
+ }
+
+ public FoldPipelineBuilder Custom(int opcode,
+ float f0 = 0, float f1 = 0, float f2 = 0, float f3 = 0,
+ Vector4? v0 = null, Vector4? v1 = null, Vector4? v2 = null, Vector4? v3 = null)
+ {
+ slots.Add(new FoldSlot
+ {
+ opcode = opcode,
+ float0 = f0, float1 = f1, float2 = f2, float3 = f3,
+ vec0 = v0 ?? Vector4.zero,
+ vec1 = v1 ?? Vector4.zero,
+ vec2 = v2 ?? Vector4.zero,
+ vec3 = v3 ?? Vector4.zero
+ });
+ return this;
+ }
+
+ public void Apply()
+ {
+ if (targetMaterial == null)
+ {
+ Debug.LogError("No target material set. Use .For(material) before .Apply()");
+ return;
+ }
+
+ if (slots.Count > 16)
+ {
+ Debug.LogWarning($"Too many operations ({slots.Count}). Only the first 16 will be applied.");
+ }
+
+ Undo.RecordObject(targetMaterial, "Apply Vertex Deformation");
+
+ targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 1f);
+
+ // Clear all slots first to ensure clean state
+ for (int i = 0; i < 16; i++)
+ FoldSlot.ClearSlot(targetMaterial, i);
+
+ // Apply active slots
+ for (int i = 0; i < slots.Count && i < 16; i++)
+ slots[i].ApplyToMaterial(targetMaterial, i);
+
+ EditorUtility.SetDirty(targetMaterial);
+ }
+
+ public void Clear()
+ {
+ slots.Clear();
+ }
+
+ public int Count => slots.Count;
+}
+
+public static class FoldPresets
+{
+ public static void TubeToPlaneFull(Material mat) =>
+ FoldPipelineBuilder.Create()
+ .For(mat)
+ .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
+ .Apply();
+
+ public static void PlaneToTubeFull(Material mat) =>
+ FoldPipelineBuilder.Create()
+ .For(mat)
+ .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f)
+ .Apply();
+
+ public static void PlaneToHemiOctahedronFull(Material mat) =>
+ FoldPipelineBuilder.Create()
+ .For(mat)
+ .PlaneToHemiOctahedron(Vector3.zero, Vector3.right, Vector3.forward, 1f)
+ .Apply();
+
+ public static void TubeToPlaneThenNormConv(Material mat) =>
+ FoldPipelineBuilder.Create()
+ .For(mat)
+ .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
+ .NormConversion(2f, 1f, 1f)
+ .Apply();
+
+ public static void NormConvL1ToL2(Material mat) =>
+ FoldPipelineBuilder.Create()
+ .For(mat)
+ .NormConversion(1f, 2f, 1f)
+ .Apply();
+
+ public static void NormConvL2ToL1(Material mat) =>
+ FoldPipelineBuilder.Create()
+ .For(mat)
+ .NormConversion(2f, 1f, 1f)
+ .Apply();
+
+ public static void NormConvL2ToLinf(Material mat) =>
+ FoldPipelineBuilder.Create()
+ .For(mat)
+ .NormConversion(2f, float.PositiveInfinity, 1f)
+ .Apply();
+}
diff --git a/Scripts/Fold/Editor/README.md b/Scripts/Fold/Editor/README.md
index 8305abf..d2b7494 100644
--- a/Scripts/Fold/Editor/README.md
+++ b/Scripts/Fold/Editor/README.md
@@ -1,14 +1,28 @@
-# Vertex Deformation Fluent Builder
+# Fold - Vertex Deformation Pipeline Builder
-A simple, code-first API for building vertex deformation pipelines without visual node editors.
+A visual editor and fluent API for building vertex deformation pipelines.
## Quick Start
+### Using the Editor Window
+
+Open `Tools > yum_food > Fold` to access the dockable pipeline editor:
+
+1. Select a target material
+2. Click "Add Operation" to add deformation operations
+3. Configure parameters for each operation
+4. Reorder operations with ▲▼ buttons
+5. Click "Apply to Material" to write the pipeline to the material
+
+Use "Load Presets" for common pipelines.
+
+### Using the Fluent API
+
```csharp
using UnityEngine;
// Apply to a material
-VertexDeformationBuilder.Create()
+FoldPipelineBuilder.Create()
.For(material)
.TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
.NormConversion(2f, 1f, 1f)
@@ -28,6 +42,15 @@ Unfolds a tube into a plane.
Folds a plane into a tube.
- Same parameters as TubeToPlane
+### PlaneToHemiOctahedron(p, r, s, t)
+Maps a plane to a hemi-octahedron (half octahedron) shape.
+- `p`: Origin point (Vector3)
+- `r`: R axis direction (Vector3)
+- `s`: S axis direction (Vector3)
+- `t`: Interpolation factor (float, 0-1)
+
+Useful for creating dome-like or hemisphere projections from planar geometry.
+
### PointAlign(po, pp, r, t)
Aligns geometry to a point.
- `po`: Original point (Vector3)
@@ -66,47 +89,53 @@ Applies fractal Brownian motion deformation.
### Custom(opcode, f0-f3, v0-v3)
For advanced use cases or custom opcodes.
-## Presets
+## Presets (Code)
-Use built-in presets from anywhere:
+Use built-in presets from code:
```csharp
-VertexDeformationPresets.TubeToPlaneFull(material);
-VertexDeformationPresets.NormConvL2ToL1(material);
-VertexDeformationPresets.NormConvL2ToLinf(material);
+FoldPresets.TubeToPlaneFull(material);
+FoldPresets.NormConvL2ToL1(material);
+FoldPresets.NormConvL2ToLinf(material);
```
-## GUI Window
+## Adding Custom Presets
-Open `Window > Vertex Deformation Presets` for a GUI with buttons for common operations.
+Edit `FoldEditorWindow.cs` to add presets to the "Load Presets" menu:
-## Adding Custom Presets
+```csharp
+// In ShowPresetsMenu():
+menu.AddItem(new GUIContent("My Custom Effect"), false, () => LoadPreset_MyEffect());
+
+// Add the preset loader method:
+void LoadPreset_MyEffect()
+{
+ operations.Clear();
+ AddOperation(new TubeToPlaneOp());
+ var norm = new NormConversionOp();
+ norm.inputK = 2f;
+ norm.outputK = 1f;
+ AddOperation(norm);
+}
+```
-1. Edit `VertexDeformationPresets` class:
+Or use the fluent API in code:
```csharp
public static void MyCustomEffect(Material mat) =>
- VertexDeformationBuilder.Create()
+ FoldPipelineBuilder.Create()
.For(mat)
.TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
.NormConversion(2f, 1f, 0.5f)
- .Seal(0.1f, 2f, 0.8f, 1f)
.Apply();
```
-2. Add button in `VertexDeformationEditorWindow`:
-
-```csharp
-if (GUILayout.Button("My Custom Effect"))
- VertexDeformationPresets.MyCustomEffect(targetMaterial);
-```
-
## Pipeline Chaining
Operations are applied in the order they're chained:
```csharp
-VertexDeformationBuilder.Create()
+FoldPipelineBuilder.Create()
.For(material)
.TubeToPlane(...) // Step 1
.NormConversion(...) // Step 2
@@ -119,13 +148,9 @@ The shader supports up to 16 operations per material.
## Clearing Deformations
-```csharp
-// Via GUI: Use "Clear All Deformations" button
+In the Fold window, click "Clear All" to remove all operations from the pipeline.
-// Via code:
+Via code:
+```csharp
targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 0f);
```
-
-## Examples
-
-See `VertexDeformationExamples.cs` for menu items under `Tools > Vertex Deformation > Example: ...`
diff --git a/Scripts/Fold/Editor/VertexDeformationBuilder.cs b/Scripts/Fold/Editor/VertexDeformationBuilder.cs
deleted file mode 100644
index c04a66a..0000000
--- a/Scripts/Fold/Editor/VertexDeformationBuilder.cs
+++ /dev/null
@@ -1,339 +0,0 @@
-using System;
-using System.Collections.Generic;
-using UnityEditor;
-using UnityEngine;
-
-public class VertexDeformationSlot
-{
- public int opcode;
- public float float0, float1, float2, float3;
- public Vector4 vec0, vec1, vec2, vec3;
-
- public void ApplyToMaterial(Material mat, int slotIndex)
- {
- var prefix = $"_Vertex_Deformation_Slot_{slotIndex}_";
- mat.SetFloat(prefix + "Enabled", 1f);
- mat.SetInt(prefix + "Opcode", opcode);
- mat.SetFloat(prefix + "Float_0", float0);
- mat.SetFloat(prefix + "Float_1", float1);
- mat.SetFloat(prefix + "Float_2", float2);
- mat.SetFloat(prefix + "Float_3", float3);
- mat.SetVector(prefix + "Vector_0", vec0);
- mat.SetVector(prefix + "Vector_1", vec1);
- mat.SetVector(prefix + "Vector_2", vec2);
- mat.SetVector(prefix + "Vector_3", vec3);
- }
-}
-
-public class VertexDeformationBuilder
-{
- readonly List<VertexDeformationSlot> slots = new();
- Material targetMaterial;
-
- public static class Opcodes
- {
- public const int None = 0;
- public const int TubeToPlane = 1;
- public const int PlaneToTube = 2;
- public const int PointAlign = 3;
- public const int AxisAlign = 4;
- public const int NormConversion = 5;
- public const int Seal = 6;
- public const int SineWaves = 7;
- public const int FBM = 8;
- }
-
- VertexDeformationBuilder() { }
-
- public static VertexDeformationBuilder Create() => new();
-
- public VertexDeformationBuilder For(Material material)
- {
- targetMaterial = material;
- return this;
- }
-
- public VertexDeformationBuilder TubeToPlane(Vector3 p, Vector3 r, Vector3 s, float t)
- {
- slots.Add(new VertexDeformationSlot
- {
- opcode = Opcodes.TubeToPlane,
- vec0 = p,
- vec1 = r,
- vec2 = s,
- float0 = t
- });
- return this;
- }
-
- public VertexDeformationBuilder PlaneToTube(Vector3 p, Vector3 r, Vector3 s, float t)
- {
- slots.Add(new VertexDeformationSlot
- {
- opcode = Opcodes.PlaneToTube,
- vec0 = p,
- vec1 = r,
- vec2 = s,
- float0 = t
- });
- return this;
- }
-
- public VertexDeformationBuilder PointAlign(Vector3 po, Vector3 pp, Vector3 r, float t)
- {
- slots.Add(new VertexDeformationSlot
- {
- opcode = Opcodes.PointAlign,
- vec0 = po,
- vec1 = pp,
- vec2 = r,
- float0 = t
- });
- return this;
- }
-
- public VertexDeformationBuilder AxisAlign(Vector3 po, Vector3 pp, Vector3 r, float t)
- {
- slots.Add(new VertexDeformationSlot
- {
- opcode = Opcodes.AxisAlign,
- vec0 = po,
- vec1 = pp,
- vec2 = r,
- float0 = t
- });
- return this;
- }
-
- public VertexDeformationBuilder NormConversion(float inputK, float outputK, float t)
- {
- slots.Add(new VertexDeformationSlot
- {
- opcode = Opcodes.NormConversion,
- float0 = inputK,
- float1 = outputK,
- float2 = t
- });
- return this;
- }
-
- public VertexDeformationBuilder Seal(float A, float k, float st, float t)
- {
- slots.Add(new VertexDeformationSlot
- {
- opcode = Opcodes.Seal,
- float0 = A,
- float1 = k,
- float2 = st,
- float3 = t
- });
- return this;
- }
-
- public VertexDeformationBuilder SineWaves()
- {
- slots.Add(new VertexDeformationSlot { opcode = Opcodes.SineWaves });
- return this;
- }
-
- public VertexDeformationBuilder FBM()
- {
- slots.Add(new VertexDeformationSlot { opcode = Opcodes.FBM });
- return this;
- }
-
- public VertexDeformationBuilder Custom(int opcode,
- float f0 = 0, float f1 = 0, float f2 = 0, float f3 = 0,
- Vector4? v0 = null, Vector4? v1 = null, Vector4? v2 = null, Vector4? v3 = null)
- {
- slots.Add(new VertexDeformationSlot
- {
- opcode = opcode,
- float0 = f0, float1 = f1, float2 = f2, float3 = f3,
- vec0 = v0 ?? Vector4.zero,
- vec1 = v1 ?? Vector4.zero,
- vec2 = v2 ?? Vector4.zero,
- vec3 = v3 ?? Vector4.zero
- });
- return this;
- }
-
- public void Apply()
- {
- if (targetMaterial == null)
- {
- Debug.LogError("No target material set. Use .For(material) before .Apply()");
- return;
- }
-
- if (slots.Count > 16)
- {
- Debug.LogWarning($"Too many operations ({slots.Count}). Only the first 16 will be applied.");
- }
-
- Undo.RecordObject(targetMaterial, "Apply Vertex Deformation");
-
- targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 1f);
-
- for (int i = 0; i < 16; i++)
- {
- if (i < slots.Count)
- {
- slots[i].ApplyToMaterial(targetMaterial, i);
- }
- else
- {
- var prefix = $"_Vertex_Deformation_Slot_{i}_";
- targetMaterial.SetFloat(prefix + "Enabled", 0f);
- }
- }
-
- EditorUtility.SetDirty(targetMaterial);
- }
-
- public void Clear()
- {
- slots.Clear();
- }
-
- public int Count => slots.Count;
-}
-
-public static class VertexDeformationPresets
-{
- public static void TubeToPlaneFull(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
- .Apply();
-
- public static void PlaneToTubeFull(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f)
- .Apply();
-
- public static void TubeToPlaneThenNormConv(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
- .NormConversion(2f, 1f, 1f)
- .Apply();
-
- public static void NormConvL1ToL2(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .NormConversion(1f, 2f, 1f)
- .Apply();
-
- public static void NormConvL2ToL1(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .NormConversion(2f, 1f, 1f)
- .Apply();
-
- public static void NormConvL2ToLinf(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .NormConversion(2f, float.PositiveInfinity, 1f)
- .Apply();
-}
-
-public class VertexDeformationEditorWindow : EditorWindow
-{
- Material targetMaterial;
- Vector2 scrollPos;
-
- [MenuItem("Window/Vertex Deformation Presets")]
- static void ShowWindow()
- {
- GetWindow<VertexDeformationEditorWindow>("Vertex Deformation");
- }
-
- void OnGUI()
- {
- GUILayout.Label("Vertex Deformation Builder", EditorStyles.boldLabel);
-
- targetMaterial = EditorGUILayout.ObjectField("Target Material", targetMaterial, typeof(Material), false) as Material;
-
- if (targetMaterial == null)
- {
- EditorGUILayout.HelpBox("Select a material to apply vertex deformations", MessageType.Info);
- return;
- }
-
- scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
-
- DrawSection("Basic Operations", () =>
- {
- if (GUILayout.Button("Tube to Plane"))
- VertexDeformationPresets.TubeToPlaneFull(targetMaterial);
-
- if (GUILayout.Button("Plane to Tube"))
- VertexDeformationPresets.PlaneToTubeFull(targetMaterial);
- });
-
- DrawSection("Norm Conversions", () =>
- {
- if (GUILayout.Button("L1 to L2"))
- VertexDeformationPresets.NormConvL1ToL2(targetMaterial);
-
- if (GUILayout.Button("L2 to L1"))
- VertexDeformationPresets.NormConvL2ToL1(targetMaterial);
-
- if (GUILayout.Button("L2 to L∞"))
- VertexDeformationPresets.NormConvL2ToLinf(targetMaterial);
- });
-
- DrawSection("Complex Pipelines", () =>
- {
- if (GUILayout.Button("Tube to Plane + Norm Conv (L2→L1)"))
- VertexDeformationPresets.TubeToPlaneThenNormConv(targetMaterial);
- });
-
- DrawSection("Utilities", () =>
- {
- if (GUILayout.Button("Clear All Deformations"))
- {
- Undo.RecordObject(targetMaterial, "Clear Vertex Deformations");
- targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 0f);
- for (int i = 0; i < 16; i++)
- {
- var prefix = $"_Vertex_Deformation_Slot_{i}_";
- targetMaterial.SetFloat(prefix + "Enabled", 0f);
- }
- EditorUtility.SetDirty(targetMaterial);
- }
-
- if (GUILayout.Button("Show Code Example"))
- {
- ShowCodeExample();
- }
- });
-
- EditorGUILayout.EndScrollView();
- }
-
- void DrawSection(string title, Action content)
- {
- GUILayout.Space(10);
- GUILayout.Label(title, EditorStyles.boldLabel);
- content();
- }
-
- void ShowCodeExample()
- {
- EditorUtility.DisplayDialog("Code Example",
- "// Quick preset:\n" +
- "VertexDeformationPresets.TubeToPlaneFull(material);\n\n" +
- "// Custom pipeline:\n" +
- "VertexDeformationBuilder.Create()\n" +
- " .For(material)\n" +
- " .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)\n" +
- " .NormConversion(2f, 1f, 1f)\n" +
- " .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f)\n" +
- " .Apply();\n\n" +
- "// Add your own presets in VertexDeformationPresets class!",
- "OK");
- }
-}
diff --git a/Scripts/Fold/Editor/VertexDeformationExamples.cs b/Scripts/Fold/Editor/VertexDeformationExamples.cs
deleted file mode 100644
index 705cea9..0000000
--- a/Scripts/Fold/Editor/VertexDeformationExamples.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using UnityEditor;
-using UnityEngine;
-
-public static class VertexDeformationExamples
-{
- [MenuItem("Tools/Vertex Deformation/Example: Tube to Plane")]
- static void Example_TubeToPlane()
- {
- var mat = Selection.activeObject as Material;
- if (mat == null)
- {
- EditorUtility.DisplayDialog("Error", "Select a material in the Project window", "OK");
- return;
- }
-
- VertexDeformationBuilder.Create()
- .For(mat)
- .TubeToPlane(
- p: Vector3.zero,
- r: Vector3.right,
- s: Vector3.forward,
- t: 1f)
- .Apply();
- }
-
- [MenuItem("Tools/Vertex Deformation/Example: Complex Pipeline")]
- static void Example_ComplexPipeline()
- {
- var mat = Selection.activeObject as Material;
- if (mat == null)
- {
- EditorUtility.DisplayDialog("Error", "Select a material in the Project window", "OK");
- return;
- }
-
- VertexDeformationBuilder.Create()
- .For(mat)
- .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
- .NormConversion(inputK: 2f, outputK: 1f, t: 1f)
- .PointAlign(
- po: new Vector3(0, 0, 0),
- pp: new Vector3(0, 1, 0),
- r: Vector3.right,
- t: 0.5f)
- .Apply();
-
- Debug.Log("Applied 3-step deformation pipeline to " + mat.name);
- }
-
- [MenuItem("Tools/Vertex Deformation/Example: Add Custom Preset")]
- static void Example_ShowHowToAddPreset()
- {
- EditorUtility.DisplayDialog("How to Add Presets",
- "1. Open VertexDeformationPresets class\n\n" +
- "2. Add a new static method:\n\n" +
- "public static void MyCustomPreset(Material mat) =>\n" +
- " VertexDeformationBuilder.Create()\n" +
- " .For(mat)\n" +
- " .TubeToPlane(...)\n" +
- " .NormConversion(...)\n" +
- " .Apply();\n\n" +
- "3. Add a button in VertexDeformationEditorWindow:\n\n" +
- "if (GUILayout.Button(\"My Custom Preset\"))\n" +
- " VertexDeformationPresets.MyCustomPreset(targetMaterial);",
- "OK");
- }
-
- [MenuItem("Tools/Vertex Deformation/Example: All Opcodes")]
- static void Example_AllOpcodes()
- {
- EditorUtility.DisplayDialog("All Available Operations",
- "TubeToPlane(p, r, s, t)\n" +
- "PlaneToTube(p, r, s, t)\n" +
- "PointAlign(po, pp, r, t)\n" +
- "AxisAlign(po, pp, r, t)\n" +
- "NormConversion(inputK, outputK, t)\n" +
- "Seal(A, k, st, t)\n" +
- "SineWaves()\n" +
- "FBM()\n\n" +
- "Custom(opcode, f0, f1, f2, f3, v0, v1, v2, v3)\n\n" +
- "Chain them with method calls:\n" +
- ".TubeToPlane(...).NormConversion(...).Apply()",
- "OK");
- }
-
- public static class CustomPresetIdeas
- {
- public static void CylinderToPlane(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .TubeToPlane(Vector3.zero, Vector3.up, Vector3.right, 1f)
- .Apply();
-
- public static void SphereToBox(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .NormConversion(inputK: 2f, outputK: float.PositiveInfinity, t: 1f)
- .Apply();
-
- public static void BoxToSphere(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .NormConversion(inputK: float.PositiveInfinity, outputK: 2f, t: 1f)
- .Apply();
-
- public static void TubeUnfoldAndFlatten(Material mat) =>
- VertexDeformationBuilder.Create()
- .For(mat)
- .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)
- .NormConversion(inputK: 2f, outputK: 1f, t: 0.5f)
- .Seal(A: 0.1f, k: 2f, st: 0.8f, t: 1f)
- .Apply();
- }
-}
diff --git a/vertex.cginc b/vertex.cginc
index ac2c6aa..e59813c 100644
--- a/vertex.cginc
+++ b/vertex.cginc
@@ -13,6 +13,7 @@
#define OPCODE_SEAL 6
#define OPCODE_SINE_WAVES 7
#define OPCODE_FBM 8
+#define OPCODE_PLANE_TO_HEMI_OCTAHEDRON 9
#if defined(_VERTEX_DEFORMATION)
@@ -148,6 +149,22 @@ void apply_fbm_normal(inout float3 objPos, inout float3 objNorm, inout float3 ob
fbm_normal(objPos, objNorm, objTan, st, amplitude, gain, lacunarity, period, octaves, velocity);
}
+void apply_plane_to_hemi_octahedron(inout float3 objPos, float4 v0, float4 v1, float4 v2, float f0) {
+ float3 p = v0.xyz;
+ float3 r = v1.xyz;
+ float3 s = v2.xyz;
+ float t = f0;
+ objPos = plane_to_hemi_octahedron(objPos, p, r, s, t);
+}
+
+void apply_plane_to_hemi_octahedron_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTan, float4 v0, float4 v1, float4 v2, float f0) {
+ float3 p = v0.xyz;
+ float3 r = v1.xyz;
+ float3 s = v2.xyz;
+ float t = f0;
+ plane_to_hemi_octahedron_normal(objPos, objNorm, objTan, p, r, s, t);
+}
+
void deform(inout float3 objPos) {
const float t = getTime();
@@ -380,6 +397,9 @@ void deform(inout float3 objPos) {
case OPCODE_FBM:
apply_fbm(objPos, v0, v1, v2, f0, f1, f2, t);
break;
+ case OPCODE_PLANE_TO_HEMI_OCTAHEDRON:
+ apply_plane_to_hemi_octahedron(objPos, v0, v1, v2, f0);
+ break;
}
}
}
@@ -619,6 +639,9 @@ void deform_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTa
case OPCODE_FBM:
apply_fbm_normal(objPos, objNorm, objTan, v0, v1, v2, f0, f1, f2, t);
break;
+ case OPCODE_PLANE_TO_HEMI_OCTAHEDRON:
+ apply_plane_to_hemi_octahedron_normal(objPos, objNorm, objTan, v0, v1, v2, f0);
+ break;
}
}
}
diff --git a/vertex_deformation.slang b/vertex_deformation.slang
index ed514fc..50c252e 100644
--- a/vertex_deformation.slang
+++ b/vertex_deformation.slang
@@ -3,6 +3,8 @@
#define PI 3.14159265f
#define PI_RCP 0.31830988f
+#define SQRT_2 1.41421356f
+#define RCP_SQRT_2 0.70710678f
#define TAU (2.0f * PI)
#define HALF_PI (0.5f * PI)
#define RCP_PI (1.0f / PI)
@@ -11,8 +13,10 @@
#define glsl_mod(x,y) (((x)-(y)*floor((x)/(y))))
// Differentiable versions of common operators.
-#define dabs(x) sqrt((x) * (x) + 1e-6)
-#define dlerp(x, y, t) ((x) * (1-t) + (y) * t)
+#define dabs(x) sqrt((x) * (x) + 1e-6f)
+#define dmin(a, b) (0.5f * ((a) + (b) - dabs((a) - (b))))
+#define dmax(a, b) (0.5f * ((a) + (b) + dabs((a) - (b))))
+#define dlerp(x, y, t) ((x) * (1.0f-t) + (y) * t)
// This was derived using fourier analysis. See Scripts/approximate.py.
#define dfrac(x) \
4.997559e-01 - \
@@ -558,4 +562,67 @@ public void fbm_undeform_normal(float3 xyz, float t,
tangent = mul(inv_jac, tangent);
}
+// Maps a plane to a hemi-octahedron (half octahedron).
+// Uses octahedral parameterization consistent with pbrt's OctahedralVector.
+// Input: plane on [-1,1]² in the (r, rxs) plane
+// Output: unit hemisphere with pole at +s direction
+[Differentiable]
+public float3 plane_to_hemi_octahedron(float3 xyz,
+ no_diff float3 p, no_diff float3 r_cart, no_diff float3 s_cart,
+ no_diff float t) {
+ // Convert from cartesian to (r, s, r x s) space.
+ r_cart = normalize(r_cart);
+ s_cart = normalize(s_cart);
+ float3 rxs_cart = cross(s_cart, r_cart);
+ float3x3 to_rsrxs = float3x3(r_cart, s_cart, rxs_cart);
+ float3x3 to_cart = transpose(to_rsrxs);
+
+ // Translate origin to `p` then change into (r, s, r x s) basis.
+ xyz = mul(to_rsrxs, xyz - p);
+
+ float3 xyz0 = xyz;
+
+ // Extract planar coordinates: x and z form the 2D plane
+ float x = xyz.x;
+ float z = xyz.z;
+
+ // Rotate 45° and scale to fit square into diamond
+ float x_rot = (x + z) * 0.5;
+ float z_rot = (z - x) * 0.5;
+
+ // Octahedral decode: y = 1 - |x| - |z|, clamped to hemisphere
+ // Use differentiable abs and max for smooth autodiff
+ float y = dmax(0.0f, 1.0f - dabs(x_rot) - dabs(z_rot));
+
+ // Normalize to unit sphere (differentiable safe normalize)
+ float3 oct_pos = float3(x_rot, y, z_rot);
+ float len = dot(oct_pos, oct_pos);
+ oct_pos = oct_pos / sqrt(len);
+
+ // Rotate back by -45° around y to undo input rotation
+ float x_unrot = (oct_pos.x - oct_pos.z) * RCP_SQRT_2;
+ float z_unrot = (oct_pos.x + oct_pos.z) * RCP_SQRT_2;
+ oct_pos = float3(x_unrot, oct_pos.y, z_unrot);
+
+ // Interpolate between original position and sphere position
+ float3 result = dlerp(xyz0, oct_pos, dmin(t, 1.0f));
+
+ // Map back to cartesian basis
+ xyz = mul(to_cart, result) + p;
+
+ return xyz;
+}
+
+public void plane_to_hemi_octahedron_normal(inout float3 xyz, inout float3 normal,
+ inout float3 tangent, float3 p, float3 r, float3 s, float t) {
+ R3R3_NORMALS(xyz, normal, tangent, plane_to_hemi_octahedron, p, r, s, t);
+}
+
+public void plane_to_hemi_octahedron_undeform_normal(float3 xyz, inout float3 normal,
+ inout float3 tangent, float3 p, float3 r, float3 s, float t) {
+ R3R3_DECLARE_BASIS_VECTORS(xyz);
+ R3R3_AUTODIFF_BASIS_VECTORS(plane_to_hemi_octahedron, p, r, s, t);
+ R3R3_UNDEFORM_NORMAL_AND_TANGENT(normal, tangent);
+}
+
#endif // __CUSTOM31_INC