diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-13 19:33:51 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-13 19:33:51 -0800 |
| commit | 8c3a05445f529c10ebbf5bfdc0eb220fe95c558c (patch) | |
| tree | cd15a6589e819f9a75de214335cda2bdf7492cab | |
| parent | b0982529d9e3d549106edd80a3e1246f3fb8cd2c (diff) | |
Fold: add hemi octahedron to plane operator
| -rw-r--r-- | Scripts/Fold/Editor/FoldEditorWindow.cs | 564 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/FoldPipelineBuilder.cs | 14 | ||||
| -rw-r--r-- | vertex.cginc | 23 | ||||
| -rw-r--r-- | vertex_deformation.slang | 58 |
4 files changed, 659 insertions, 0 deletions
diff --git a/Scripts/Fold/Editor/FoldEditorWindow.cs b/Scripts/Fold/Editor/FoldEditorWindow.cs new file mode 100644 index 0000000..ab02781 --- /dev/null +++ b/Scripts/Fold/Editor/FoldEditorWindow.cs @@ -0,0 +1,564 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +public class FoldEditorWindow : EditorWindow +{ + [SerializeField] Material targetMaterial; + [SerializeField] Vector2 scrollPos; + [SerializeReference] List<DeformOperation> operations = new(); + [SerializeField] List<int> expandedOps = new(); + [SerializeField] bool liveUpdate = true; + + [MenuItem("Tools/yum_food/Fold")] + static void ShowWindow() + { + var window = GetWindow<FoldEditorWindow>("Fold"); + window.minSize = new Vector2(400, 300); + } + + void OnEnable() + { + // Operations list is restored via SerializeReference + } + + void OnGUI() + { + EditorGUILayout.Space(5); + + DrawHeader(); + EditorGUILayout.Space(10); + + if (targetMaterial == null) + { + EditorGUILayout.HelpBox("Select a material to build deformation pipelines", MessageType.Info); + return; + } + + DrawToolbar(); + EditorGUILayout.Space(5); + + EditorGUI.BeginChangeCheck(); + + scrollPos = EditorGUILayout.BeginScrollView(scrollPos); + DrawOperationsList(); + EditorGUILayout.EndScrollView(); + + if (EditorGUI.EndChangeCheck() && liveUpdate && targetMaterial != null) + ApplyToMaterial(); + + EditorGUILayout.Space(5); + DrawFooter(); + } + + void DrawHeader() + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Target Material", EditorStyles.boldLabel, GUILayout.Width(100)); + + var newMat = EditorGUILayout.ObjectField(targetMaterial, typeof(Material), false) as Material; + if (newMat != targetMaterial) + { + targetMaterial = newMat; + if (targetMaterial != null) + LoadFromMaterial(); + } + + EditorGUILayout.EndHorizontal(); + } + + void DrawToolbar() + { + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + + bool pipelineFull = operations.Count >= 16; + using (new EditorGUI.DisabledScope(pipelineFull)) + { + var content = pipelineFull + ? new GUIContent("Add Operation", "Pipeline full (16/16)") + : new GUIContent("Add Operation"); + if (GUILayout.Button(content, EditorStyles.toolbarDropDown, GUILayout.Width(120))) + ShowAddOperationMenu(); + } + + GUILayout.FlexibleSpace(); + + liveUpdate = GUILayout.Toggle(liveUpdate, "Live", EditorStyles.toolbarButton, GUILayout.Width(40)); + + if (GUILayout.Button("Load Presets", EditorStyles.toolbarDropDown, GUILayout.Width(100))) + ShowPresetsMenu(); + + EditorGUILayout.EndHorizontal(); + } + + void DrawOperationsList() + { + if (operations.Count == 0) + { + EditorGUILayout.HelpBox("No operations. Click 'Add Operation' to begin.", MessageType.Info); + return; + } + + for (int i = 0; i < operations.Count; i++) + { + DrawOperation(i); + EditorGUILayout.Space(3); + } + } + + void DrawOperation(int index) + { + var op = operations[index]; + bool isExpanded = expandedOps.Contains(index); + + var bgColor = isExpanded ? new Color(0.3f, 0.5f, 0.8f, 0.3f) : new Color(0.2f, 0.2f, 0.2f, 0.3f); + + EditorGUILayout.BeginVertical(GUI.skin.box); + GUI.backgroundColor = bgColor; + + // Header with drag handle and delete button + EditorGUILayout.BeginHorizontal(); + + // Drag handle + GUILayout.Label($"#{index}", GUILayout.Width(30)); + + // Operation name (clickable to expand/collapse) + if (GUILayout.Button(op.GetDisplayName(), EditorStyles.boldLabel)) + { + if (isExpanded) expandedOps.Remove(index); + else expandedOps.Add(index); + } + + GUILayout.FlexibleSpace(); + + // Move up/down + GUI.enabled = index > 0; + if (GUILayout.Button("▲", GUILayout.Width(25))) + { + operations.RemoveAt(index); + operations.Insert(index - 1, op); + if (expandedOps.Contains(index)) + { + expandedOps.Remove(index); + expandedOps.Add(index - 1); + } + } + + GUI.enabled = index < operations.Count - 1; + if (GUILayout.Button("▼", GUILayout.Width(25))) + { + operations.RemoveAt(index); + operations.Insert(index + 1, op); + if (expandedOps.Contains(index)) + { + expandedOps.Remove(index); + expandedOps.Add(index + 1); + } + } + + GUI.enabled = true; + + // Delete button + if (GUILayout.Button("×", GUILayout.Width(25))) + { + operations.RemoveAt(index); + expandedOps.Remove(index); + // Adjust indices in expandedOps + for (int i = 0; i < expandedOps.Count; i++) + { + if (expandedOps[i] > index) expandedOps[i]--; + } + } + + EditorGUILayout.EndHorizontal(); + + // Draw parameters when expanded + if (isExpanded) + { + EditorGUILayout.Space(5); + EditorGUI.indentLevel++; + op.DrawParameters(); + EditorGUI.indentLevel--; + } + + GUI.backgroundColor = Color.white; + EditorGUILayout.EndVertical(); + } + + void DrawFooter() + { + EditorGUILayout.BeginHorizontal(); + + if (GUILayout.Button("Apply to Material", GUILayout.Height(30))) + ApplyToMaterial(); + + if (GUILayout.Button("Clear All", GUILayout.Height(30), GUILayout.Width(100))) + { + operations.Clear(); + expandedOps.Clear(); + } + + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(5); + EditorGUILayout.LabelField($"Operations: {operations.Count}/16", EditorStyles.miniLabel); + } + + void ShowAddOperationMenu() + { + var menu = new GenericMenu(); + + menu.AddItem(new GUIContent("Tube to Plane"), false, () => AddOperation(new TubeToPlaneOp())); + menu.AddItem(new GUIContent("Plane to Tube"), false, () => AddOperation(new PlaneToTubeOp())); + menu.AddItem(new GUIContent("Plane to Hemi-Octahedron"), false, () => AddOperation(new PlaneToHemiOctahedronOp())); + menu.AddItem(new GUIContent("Hemi-Octahedron to Plane"), false, () => AddOperation(new HemiOctahedronToPlaneOp())); + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Point Align"), false, () => AddOperation(new PointAlignOp())); + menu.AddItem(new GUIContent("Axis Align"), false, () => AddOperation(new AxisAlignOp())); + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Norm Conversion"), false, () => AddOperation(new NormConversionOp())); + menu.AddItem(new GUIContent("Seal"), false, () => AddOperation(new SealOp())); + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Sine Waves"), false, () => AddOperation(new SineWavesOp())); + menu.AddItem(new GUIContent("FBM"), false, () => AddOperation(new FBMOp())); + + menu.ShowAsContext(); + } + + void ShowPresetsMenu() + { + var menu = new GenericMenu(); + + menu.AddItem(new GUIContent("Tube to Plane"), false, () => LoadPreset_TubeToPlane()); + menu.AddItem(new GUIContent("Plane to Tube"), false, () => LoadPreset_PlaneToTube()); + menu.AddItem(new GUIContent("Plane to Hemi-Octahedron"), false, () => LoadPreset_PlaneToHemiOct()); + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Norm: L1→L2 (Diamond→Sphere)"), false, () => LoadPreset_NormL1L2()); + menu.AddItem(new GUIContent("Norm: L2→L1 (Sphere→Diamond)"), false, () => LoadPreset_NormL2L1()); + menu.AddItem(new GUIContent("Norm: L2→L∞ (Sphere→Cube)"), false, () => LoadPreset_NormL2Linf()); + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Complex/Tube→Plane + Norm Conv"), false, () => LoadPreset_TubePlaneNorm()); + + menu.ShowAsContext(); + } + + void AddOperation(DeformOperation op) + { + if (operations.Count >= 16) + return; + operations.Add(op); + expandedOps.Add(operations.Count - 1); + } + + void LoadPreset_TubeToPlane() + { + operations.Clear(); + expandedOps.Clear(); + AddOperation(new TubeToPlaneOp()); + } + + void LoadPreset_PlaneToTube() + { + operations.Clear(); + expandedOps.Clear(); + AddOperation(new PlaneToTubeOp()); + } + + void LoadPreset_PlaneToHemiOct() + { + operations.Clear(); + expandedOps.Clear(); + AddOperation(new PlaneToHemiOctahedronOp()); + } + + void LoadPreset_NormL1L2() + { + operations.Clear(); + expandedOps.Clear(); + var op = new NormConversionOp(); + op.inputK = 1f; + op.outputK = 2f; + AddOperation(op); + } + + void LoadPreset_NormL2L1() + { + operations.Clear(); + expandedOps.Clear(); + var op = new NormConversionOp(); + op.inputK = 2f; + op.outputK = 1f; + AddOperation(op); + } + + void LoadPreset_NormL2Linf() + { + operations.Clear(); + expandedOps.Clear(); + var op = new NormConversionOp(); + op.inputK = 2f; + op.outputK = float.PositiveInfinity; + AddOperation(op); + } + + void LoadPreset_TubePlaneNorm() + { + operations.Clear(); + expandedOps.Clear(); + AddOperation(new TubeToPlaneOp()); + var norm = new NormConversionOp(); + norm.inputK = 2f; + norm.outputK = 1f; + AddOperation(norm); + } + + void ApplyToMaterial() + { + var builder = FoldPipelineBuilder.Create().For(targetMaterial); + + foreach (var op in operations) + op.ApplyTo(builder); + + builder.Apply(); + } + + void LoadFromMaterial() + { + // Could potentially read back from material properties here + // For now, just clear the list + operations.Clear(); + expandedOps.Clear(); + } +} + +// Base class for deformation operations +[System.Serializable] +public abstract class DeformOperation +{ + public abstract string GetDisplayName(); + public abstract void DrawParameters(); + public abstract void ApplyTo(FoldPipelineBuilder builder); +} + +[System.Serializable] +public class TubeToPlaneOp : DeformOperation +{ + public Vector3 p = Vector3.zero; + public Vector3 r = Vector3.right; + public Vector3 s = Vector3.forward; + public float t = 1f; + + public override string GetDisplayName() => "Tube to Plane"; + + public override void DrawParameters() + { + p = EditorGUILayout.Vector3Field("Origin (p)", p); + r = EditorGUILayout.Vector3Field("R Axis", r); + s = EditorGUILayout.Vector3Field("S Axis", s); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.TubeToPlane(p, r, s, t); +} + +[System.Serializable] +public class PlaneToTubeOp : DeformOperation +{ + public Vector3 p = Vector3.zero; + public Vector3 r = Vector3.right; + public Vector3 s = Vector3.forward; + public float t = 1f; + + public override string GetDisplayName() => "Plane to Tube"; + + public override void DrawParameters() + { + p = EditorGUILayout.Vector3Field("Origin (p)", p); + r = EditorGUILayout.Vector3Field("R Axis", r); + s = EditorGUILayout.Vector3Field("S Axis", s); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.PlaneToTube(p, r, s, t); +} + +[System.Serializable] +public class PlaneToHemiOctahedronOp : DeformOperation +{ + public Vector3 p = Vector3.zero; + public Vector3 r = Vector3.right; + public Vector3 s = Vector3.forward; + public float t = 1f; + + public override string GetDisplayName() => "Plane to Hemi-Octahedron"; + + public override void DrawParameters() + { + p = EditorGUILayout.Vector3Field("Origin (p)", p); + r = EditorGUILayout.Vector3Field("R Axis", r); + s = EditorGUILayout.Vector3Field("S Axis", s); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.PlaneToHemiOctahedron(p, r, s, t); +} + +[System.Serializable] +public class HemiOctahedronToPlaneOp : DeformOperation +{ + public Vector3 p = Vector3.zero; + public Vector3 r = Vector3.right; + public Vector3 s = Vector3.forward; + public float t = 1f; + + public override string GetDisplayName() => "Hemi-Octahedron to Plane"; + + public override void DrawParameters() + { + p = EditorGUILayout.Vector3Field("Origin (p)", p); + r = EditorGUILayout.Vector3Field("R Axis", r); + s = EditorGUILayout.Vector3Field("S Axis", s); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.HemiOctahedronToPlane(p, r, s, t); +} + +[System.Serializable] +public class PointAlignOp : DeformOperation +{ + public Vector3 po = Vector3.zero; + public Vector3 pp = Vector3.up; + public Vector3 r = Vector3.right; + public float t = 1f; + + public override string GetDisplayName() => "Point Align"; + + public override void DrawParameters() + { + po = EditorGUILayout.Vector3Field("Origin Point (po)", po); + pp = EditorGUILayout.Vector3Field("Target Point (pp)", pp); + r = EditorGUILayout.Vector3Field("Rotation Axis (r)", r); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.PointAlign(po, pp, r, t); +} + +[System.Serializable] +public class AxisAlignOp : DeformOperation +{ + public Vector3 po = Vector3.zero; + public Vector3 pp = Vector3.up; + public Vector3 r = Vector3.right; + public float t = 1f; + + public override string GetDisplayName() => "Axis Align"; + + public override void DrawParameters() + { + po = EditorGUILayout.Vector3Field("Origin Point (po)", po); + pp = EditorGUILayout.Vector3Field("Target Point (pp)", pp); + r = EditorGUILayout.Vector3Field("Rotation Axis (r)", r); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.AxisAlign(po, pp, r, t); +} + +[System.Serializable] +public class NormConversionOp : DeformOperation +{ + public float inputK = 2f; + public float outputK = 1f; + public float t = 1f; + + public override string GetDisplayName() => $"Norm Conversion (L{FormatNorm(inputK)}→L{FormatNorm(outputK)})"; + + string FormatNorm(float k) + { + if (float.IsPositiveInfinity(k)) return "∞"; + return k.ToString("F1"); + } + + public override void DrawParameters() + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Input Norm (k)", GUILayout.Width(120)); + inputK = EditorGUILayout.FloatField(inputK); + if (GUILayout.Button("L∞", GUILayout.Width(30))) inputK = float.PositiveInfinity; + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Output Norm (k)", GUILayout.Width(120)); + outputK = EditorGUILayout.FloatField(outputK); + if (GUILayout.Button("L∞", GUILayout.Width(30))) outputK = float.PositiveInfinity; + EditorGUILayout.EndHorizontal(); + + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + + EditorGUILayout.HelpBox( + "Common: L1=diamond, L2=sphere, L∞=cube", + MessageType.Info); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.NormConversion(inputK, outputK, t); +} + +[System.Serializable] +public class SealOp : DeformOperation +{ + public float A = 0.1f; + public float k = 2f; + public float st = 0.8f; + public float t = 1f; + + public override string GetDisplayName() => "Seal"; + + public override void DrawParameters() + { + A = EditorGUILayout.FloatField("Amplitude (A)", A); + k = EditorGUILayout.FloatField("Smoothness (k)", k); + st = EditorGUILayout.FloatField("Scale (st)", st); + t = EditorGUILayout.Slider("Interpolation (t)", t, 0f, 1f); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.Seal(A, k, st, t); +} + +[System.Serializable] +public class SineWavesOp : DeformOperation +{ + public override string GetDisplayName() => "Sine Waves"; + + public override void DrawParameters() + { + EditorGUILayout.HelpBox("Sine waves use shader-side parameters", MessageType.Info); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.SineWaves(); +} + +[System.Serializable] +public class FBMOp : DeformOperation +{ + public override string GetDisplayName() => "FBM (Fractal Brownian Motion)"; + + public override void DrawParameters() + { + EditorGUILayout.HelpBox("FBM uses shader-side parameters", MessageType.Info); + } + + public override void ApplyTo(FoldPipelineBuilder builder) => + builder.FBM(); +} diff --git a/Scripts/Fold/Editor/FoldPipelineBuilder.cs b/Scripts/Fold/Editor/FoldPipelineBuilder.cs index 64032a5..3731cf8 100644 --- a/Scripts/Fold/Editor/FoldPipelineBuilder.cs +++ b/Scripts/Fold/Editor/FoldPipelineBuilder.cs @@ -57,6 +57,7 @@ public class FoldPipelineBuilder public const int SineWaves = 7; public const int FBM = 8; public const int PlaneToHemiOctahedron = 9; + public const int HemiOctahedronToPlane = 10; } FoldPipelineBuilder() { } @@ -108,6 +109,19 @@ public class FoldPipelineBuilder return this; } + public FoldPipelineBuilder HemiOctahedronToPlane(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.HemiOctahedronToPlane, + 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 diff --git a/vertex.cginc b/vertex.cginc index e59813c..77412a9 100644 --- a/vertex.cginc +++ b/vertex.cginc @@ -14,6 +14,7 @@ #define OPCODE_SINE_WAVES 7 #define OPCODE_FBM 8 #define OPCODE_PLANE_TO_HEMI_OCTAHEDRON 9 +#define OPCODE_HEMI_OCTAHEDRON_TO_PLANE 10 #if defined(_VERTEX_DEFORMATION) @@ -165,6 +166,22 @@ void apply_plane_to_hemi_octahedron_normal(inout float3 objPos, inout float3 obj plane_to_hemi_octahedron_normal(objPos, objNorm, objTan, p, r, s, t); } +void apply_hemi_octahedron_to_plane(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 = hemi_octahedron_to_plane(objPos, p, r, s, t); +} + +void apply_hemi_octahedron_to_plane_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; + hemi_octahedron_to_plane_normal(objPos, objNorm, objTan, p, r, s, t); +} + void deform(inout float3 objPos) { const float t = getTime(); @@ -400,6 +417,9 @@ void deform(inout float3 objPos) { case OPCODE_PLANE_TO_HEMI_OCTAHEDRON: apply_plane_to_hemi_octahedron(objPos, v0, v1, v2, f0); break; + case OPCODE_HEMI_OCTAHEDRON_TO_PLANE: + apply_hemi_octahedron_to_plane(objPos, v0, v1, v2, f0); + break; } } } @@ -642,6 +662,9 @@ void deform_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTa case OPCODE_PLANE_TO_HEMI_OCTAHEDRON: apply_plane_to_hemi_octahedron_normal(objPos, objNorm, objTan, v0, v1, v2, f0); break; + case OPCODE_HEMI_OCTAHEDRON_TO_PLANE: + apply_hemi_octahedron_to_plane_normal(objPos, objNorm, objTan, v0, v1, v2, f0); + break; } } } diff --git a/vertex_deformation.slang b/vertex_deformation.slang index 50c252e..6e916ed 100644 --- a/vertex_deformation.slang +++ b/vertex_deformation.slang @@ -625,4 +625,62 @@ public void plane_to_hemi_octahedron_undeform_normal(float3 xyz, inout float3 no R3R3_UNDEFORM_NORMAL_AND_TANGENT(normal, tangent); } +// Maps a hemi-octahedron to a plane (inverse of plane_to_hemi_octahedron). +// Input: unit hemisphere with pole at +s direction +// Output: plane on [-1,1]² in the (r, rxs) plane +[Differentiable] +public float3 hemi_octahedron_to_plane(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; + + // Undo the -45° unrotation from forward pass (rotate by +45°) + float x_rot = (xyz.x + xyz.z) * RCP_SQRT_2; + float z_rot = (xyz.z - xyz.x) * RCP_SQRT_2; + + // Octahedral encode: project normalized direction to plane + float3 N = normalize(float3(x_rot, xyz.y, z_rot)); + N.y = dmax(N.y, 1e-4f); + float L1 = dabs(N.x) + dabs(N.y) + dabs(N.z); + float x_oct = N.x / L1; + float z_oct = N.z / L1; + + // Undo the initial 45° rotation (x_rot = (x+z)*0.5, z_rot = (z-x)*0.5) + // Inverse: x = x_rot - z_rot, z = x_rot + z_rot + float x_plane = x_oct - z_oct; + float z_plane = x_oct + z_oct; + + float3 plane_pos = float3(x_plane, 0.0f, z_plane); + + // Interpolate between original position and plane position + float3 result = dlerp(xyz0, plane_pos, dmin(t, 1.0f)); + + // Map back to cartesian basis + xyz = mul(to_cart, result) + p; + + return xyz; +} + +public void hemi_octahedron_to_plane_normal(inout float3 xyz, inout float3 normal, + inout float3 tangent, float3 p, float3 r, float3 s, float t) { + R3R3_NORMALS(xyz, normal, tangent, hemi_octahedron_to_plane, p, r, s, t); +} + +public void hemi_octahedron_to_plane_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(hemi_octahedron_to_plane, p, r, s, t); + R3R3_UNDEFORM_NORMAL_AND_TANGENT(normal, tangent); +} + #endif // __CUSTOM31_INC |
