summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2025-08-13 16:51:23 -0700
committeryum <yum.food.vr@gmail.com>2025-08-13 17:03:46 -0700
commitf1fc3bd51a90432ddb6b7cbc86b8383c923b0dd8 (patch)
tree6ce39dd718221f5dad8ee29ecbbfeb7c511b2876
parent32781eb9ab83b504788198df552934ed6da08ee0 (diff)
add hypotrochoid gimmick
-rw-r--r--2ner.cginc25
-rw-r--r--2ner.shader27
-rw-r--r--features.cginc4
-rw-r--r--globals.cginc14
-rw-r--r--interpolators.cginc4
-rw-r--r--tessellation.cginc161
-rw-r--r--trochoid.cginc117
7 files changed, 231 insertions, 121 deletions
diff --git a/2ner.cginc b/2ner.cginc
index 1e6af31..0e6fa82 100644
--- a/2ner.cginc
+++ b/2ner.cginc
@@ -25,6 +25,7 @@
#include "ssao.cginc"
#include "ssfd.cginc"
#include "tessellation.cginc"
+#include "trochoid.cginc"
#include "unigram_letter_grid.cginc"
#include "vertex_domain_warping.cginc"
#include "yum_brdf.cginc"
@@ -117,6 +118,11 @@ v2f vert(appdata v) {
v.vertex.xyz = domainWarpVertexPosition(v.vertex.xyz);
#endif
+#if defined(_TROCHOID)
+ o.orig_pos = v.vertex.xyz;
+ v.vertex.xyz = trochoid_map(v.vertex.xyz);
+#endif
+
#if defined(OUTLINE_PASS)
[branch]
if (!_Outlines_Enabled_Dynamic) {
@@ -299,6 +305,7 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace
#if defined(_SHATTER_WAVE)
calcNormalInScreenSpace(i.normal, i.objPos);
+ i.normal = UnityObjectToWorldNormal(i.normal);
#endif
#if defined(_SHATTER_WAVE) || defined(_VERTEX_DOMAIN_WARPING) || defined(_TESSELLATION_HEIGHTMAP)
@@ -399,6 +406,24 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace
#endif
#endif
+#if defined(_TROCHOID)
+ float3 normal_obj = trochoid_normal(i.orig_pos);
+
+ // We need tangents that are perpendicular to the new normal.
+ // A common way to generate them is to cross with a fixed "up" vector.
+ float3 tangent_obj = normalize(cross(normal_obj, float3(0, 1, 0)));
+ float3 binormal_obj = cross(normal_obj, tangent_obj);
+
+ i.normal = UnityObjectToWorldNormal(normal_obj);
+ i.tangent = float4(normalize(mul((float3x3)unity_ObjectToWorld, tangent_obj)), 1);
+ i.binormal = normalize(mul((float3x3)unity_ObjectToWorld, binormal_obj));
+ i.normal *= facing ? 1 : -1;
+
+ float theta = 1 - atan2(i.orig_pos.y, i.orig_pos.x) / PI;
+ float3 color = _Trochoid_Color_Ramp.SampleLevel(linear_clamp_s, float2(theta, 0.5), 0).rgb;
+ pbr.albedo.xyz = color;
+#endif
+
#if defined(_HARNACK_TRACING)
HarnackTracingOutput harnack_output = HarnackTracing(i);
pbr.albedo = float4(1, 1, 1, 0.2);
diff --git a/2ner.shader b/2ner.shader
index 71ff21a..59c7bbe 100644
--- a/2ner.shader
+++ b/2ner.shader
@@ -1170,6 +1170,22 @@ Shader "yum_food/2ner"
[HideInInspector] m_end_SSAO("SSAO", Float) = 0
//endex
+ //ifex _Trochoid_Enabled==0
+ [HideInInspector] m_start_Trochoid("Trochoid", Float) = 0
+ [ThryToggle(_TROCHOID)] _Trochoid_Enabled("Enable", Float) = 0
+ _Trochoid_R("R", Range(0, 15)) = 1
+ _Trochoid_r("r", Range(0, 15)) = 1
+ _Trochoid_d("d", Range(0, 15)) = 1
+ _Trochoid_theta_k("Theta factor", Range(0, 64)) = 1
+ _Trochoid_t_k("Time factor", Range(0, 64)) = 1
+ _Trochoid_X_Scale("X scale", Range(0, 4)) = 1
+ _Trochoid_Y_Scale("Y scale", Range(0, 4)) = 1
+ _Trochoid_Z_Scale("Z scale", Range(0, 64)) = 1
+ _Trochoid_r_Power("r power", Range(0, 32)) = 1
+ [Gradient] _Trochoid_Color_Ramp("Color ramp", 2D) = "black" {}
+ [HideInInspector] m_end_Trochoid("Trochoid", Float) = 0
+ //endex
+
//ifex _Letter_Grid_Enabled==0
[HideInInspector] m_start_Letter_Grid("Letter grid", Float) = 0
[ThryToggle(_LETTER_GRID)] _Letter_Grid_Enabled("Enable", Float) = 0
@@ -1744,7 +1760,8 @@ Shader "yum_food/2ner"
//ifex _Tessellation_Enabled==0
[HideInInspector] m_start_Tessellation("Tessellation", Float) = 0
[ThryToggle(_TESSELLATION)] _Tessellation_Enabled("Enable", Float) = 0
- _Tessellation_Factor("Factor", Range(1, 64)) = 1
+ _Tessellation_Factor("Factor", Range(1, 256)) = 1
+ _Tessellation_Frustum_Culling_Bias("Frustum culling bias", Float) = 35
[HideInInspector] m_start_Tessellation_Heightmap("Heightmap", Float) = 0
[ThryToggle(_TESSELLATION_HEIGHTMAP_WORLD_SPACE)] _Tessellation_Heightmap_World_Space_Enabled("World space mode (RGB)", Float) = 0
[HideInInspector] m_start_Tessellation_Heightmap_0("Heightmap 0", Float) = 0
@@ -1800,14 +1817,6 @@ Shader "yum_food/2ner"
_Tessellation_Heightmap_Direction_Control_Vector("Direction (normal/tangent/binormal)", Vector) = (1, 0, 0)
[HideInInspector] m_end_Tessellation_Heightmap_Direction_Control("Direction control", Float) = 0
[HideInInspector] m_end_Tessellation_Heightmap("Heightmap", Float) = 0
- [HideInInspector] m_start_Tessellation_Range_Factor("Range-based factor", Float) = 0
- [ThryToggle(_TESSELLATION_RANGE_FACTOR)] _Tessellation_Range_Factor_Enabled("Enable", Float) = 0
- [Helpbox]_Tessellation_Range_Factor_Help("All distances are given in squared meters. For example, to set the far distance to 4 meters, enter 16.", Int) = 0
- _Tessellation_Range_Factor_Distance_Near("Distance (near)", Float) = 1
- _Tessellation_Range_Factor_Factor_Near("Factor (near)", Float) = 1
- _Tessellation_Range_Factor_Distance_Far("Distance (far)", Float) = 1
- _Tessellation_Range_Factor_Factor_Far("Factor (far)", Float) = 1
- [HideInInspector] m_end_Tessellation_Range_Factor("Range-based factor", Float) = 0
// Shit for thry
[HideInInspector] Tessellation_Enabled("Enabled", Float) = 1
[HideInInspector] Tessellation_EnabledForwardBase("Enabled (ForwardBase)", Float) = 1
diff --git a/features.cginc b/features.cginc
index 2489f54..a25bdce 100644
--- a/features.cginc
+++ b/features.cginc
@@ -464,5 +464,9 @@
#pragma shader_feature_local _SCREEN_SPACE_NORMALS
//endex
+//ifex _Trochoid_Enabled==0
+#pragma shader_feature_local _TROCHOID
+//endex
+
#endif // __FEATURES_INC
diff --git a/globals.cginc b/globals.cginc
index 9fdf417..4132e88 100644
--- a/globals.cginc
+++ b/globals.cginc
@@ -559,6 +559,7 @@ float4 _Shatter_Wave_Rotation_Strength;
#if defined(_TESSELLATION)
float _Tessellation_Factor;
+float _Tessellation_Frustum_Culling_Bias;
#endif // _TESSELLATION
#if defined(_TESSELLATION_HEIGHTMAP_0)
@@ -723,4 +724,17 @@ float _Oklab_Brightness_Clamp_Min;
float _Oklab_Brightness_Clamp_Max;
#endif // _OKLAB_BRIGHTNESS_CLAMP
+#if defined(_TROCHOID)
+float _Trochoid_R;
+float _Trochoid_r;
+float _Trochoid_d;
+float _Trochoid_theta_k;
+float _Trochoid_t_k;
+float _Trochoid_X_Scale;
+float _Trochoid_Y_Scale;
+float _Trochoid_Z_Scale;
+float _Trochoid_r_Power;
+texture2D _Trochoid_Color_Ramp;
+#endif // _TROCHOID
+
#endif // __GLOBALS_INC
diff --git a/interpolators.cginc b/interpolators.cginc
index e897e53..4e222e8 100644
--- a/interpolators.cginc
+++ b/interpolators.cginc
@@ -33,6 +33,10 @@ struct v2f {
float3 vertexLight : TEXCOORD9;
UNITY_LIGHTING_COORDS(10,11)
+#if defined(_TROCHOID)
+ float3 orig_pos : TEXCOORD12;
+#endif
+
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
diff --git a/tessellation.cginc b/tessellation.cginc
index a89ca66..fce10d4 100644
--- a/tessellation.cginc
+++ b/tessellation.cginc
@@ -5,6 +5,7 @@
#include "interpolators.cginc"
#include "math.cginc"
#include "shatter_wave.cginc"
+#include "trochoid.cginc"
//ifex _Tessellation_Enabled==0
@@ -13,127 +14,56 @@ struct tess_factors {
float inside : SV_InsideTessFactor;
};
-bool isInViewFrustum(float4 clipPos, float radius) {
- return -clipPos.w - radius < clipPos.x && clipPos.x < clipPos.w + radius &&
- -clipPos.w - radius < clipPos.y && clipPos.y < clipPos.w + radius &&
- -clipPos.w - radius < clipPos.z && clipPos.z < clipPos.w + radius;
+bool cullPatch(float4 p0, float4 p1, float4 p2, float bias) {
+ return
+ (p0.x < -p0.w - bias && p1.x < -p1.w - bias && p2.x < -p2.w - bias) ||
+ (p0.x > p0.w + bias && p1.x > p1.w + bias && p2.x > p2.w + bias) ||
+ (p0.y < -p0.w - bias && p1.y < -p1.w - bias && p2.y < -p2.w - bias) ||
+ (p0.y > p0.w + bias && p1.y > p1.w + bias && p2.y > p2.w + bias) ||
+ (p0.z < -p0.w - bias && p1.z < -p1.w - bias && p2.z < -p2.w - bias) ||
+ (p0.z > p0.w + bias && p1.z > p1.w + bias && p2.z > p2.w + bias);
}
-#if defined(_TESSELLATION_HEIGHTMAP_0) || defined(_TESSELLATION_HEIGHTMAP_1) || defined(_TESSELLATION_HEIGHTMAP_2) || defined(_TESSELLATION_HEIGHTMAP_3) || defined(_TESSELLATION_HEIGHTMAP_4) || defined(_TESSELLATION_HEIGHTMAP_5) || defined(_TESSELLATION_HEIGHTMAP_6) || defined(_TESSELLATION_HEIGHTMAP_7)
-#define _TESSELLATION_HEIGHTMAP
-#endif
+tess_factors patch_constant(InputPatch<v2f, 3> patch) {
+ tess_factors f;
-float4 applyHeightmap(float4 objPos, float2 uv, float3 normal, float3 tangent, float3 binormal) {
-#if defined(_TESSELLATION) && defined(_TESSELLATION_HEIGHTMAP)
- float3 height = 0;
-#if defined(_TESSELLATION_HEIGHTMAP_0)
- float3 heightmap_0_sample = _Tessellation_Heightmap_0.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_0_ST.xy, 0);
- height += heightmap_0_sample * _Tessellation_Heightmap_0_Scale + _Tessellation_Heightmap_0_Offset;
-#endif
-#if defined(_TESSELLATION_HEIGHTMAP_1)
- float3 heightmap_1_sample = _Tessellation_Heightmap_1.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_1_ST.xy, 0);
- height += heightmap_1_sample * _Tessellation_Heightmap_1_Scale + _Tessellation_Heightmap_1_Offset;
-#endif
-#if defined(_TESSELLATION_HEIGHTMAP_2)
- float3 heightmap_2_sample = _Tessellation_Heightmap_2.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_2_ST.xy, 0);
- height += heightmap_2_sample * _Tessellation_Heightmap_2_Scale + _Tessellation_Heightmap_2_Offset;
-#endif
-#if defined(_TESSELLATION_HEIGHTMAP_3)
- float3 heightmap_3_sample = _Tessellation_Heightmap_3.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_3_ST.xy, 0);
- height += heightmap_3_sample * _Tessellation_Heightmap_3_Scale + _Tessellation_Heightmap_3_Offset;
-#endif
-#if defined(_TESSELLATION_HEIGHTMAP_4)
- float3 heightmap_4_sample = _Tessellation_Heightmap_4.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_4_ST.xy, 0);
- height += heightmap_4_sample * _Tessellation_Heightmap_4_Scale + _Tessellation_Heightmap_4_Offset;
-#endif
-#if defined(_TESSELLATION_HEIGHTMAP_5)
- float3 heightmap_5_sample = _Tessellation_Heightmap_5.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_5_ST.xy, 0);
- height += heightmap_5_sample * _Tessellation_Heightmap_5_Scale + _Tessellation_Heightmap_5_Offset;
-#endif
-#if defined(_TESSELLATION_HEIGHTMAP_6)
- float3 heightmap_6_sample = _Tessellation_Heightmap_6.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_6_ST.xy, 0);
- height += heightmap_6_sample * _Tessellation_Heightmap_6_Scale + _Tessellation_Heightmap_6_Offset;
-#endif
-#if defined(_TESSELLATION_HEIGHTMAP_7)
- float3 heightmap_7_sample = _Tessellation_Heightmap_7.SampleLevel(bilinear_repeat_s,
- uv * _Tessellation_Heightmap_7_ST.xy, 0);
- height += heightmap_7_sample * _Tessellation_Heightmap_7_Scale + _Tessellation_Heightmap_7_Offset;
-#endif
+#if defined(_TESSELLATION)
+ float4 p0 = patch[0].pos;
+ float4 p1 = patch[1].pos;
+ float4 p2 = patch[2].pos;
+
+ [branch]
+ if (cullPatch(p0, p1, p2, _Tessellation_Frustum_Culling_Bias)) {
+ f.edge[0] = 1;
+ f.edge[1] = 1;
+ f.edge[2] = 1;
+ f.inside = 1;
+ return f;
+ }
-#if defined(_TESSELLATION_HEIGHTMAP_WORLD_SPACE)
- objPos.xyz += mul(unity_WorldToObject, height).xyz;
-#else
-#if defined(OUTLINE_PASS) && defined(_TESSELLATION_HEIGHTMAP_DIRECTION_CONTROL)
- float3 heightmap_direction = mul(transpose(-float3x3(normal, tangent, binormal)), _Tessellation_Heightmap_Direction_Control_Vector);
-#elif defined(OUTLINE_PASS) && !defined(_TESSELLATION_HEIGHTMAP_DIRECTION_CONTROL)
- float3 heightmap_direction = -normal;
-#elif !defined(OUTLINE_PASS) && defined(_TESSELLATION_HEIGHTMAP_DIRECTION_CONTROL)
- float3 heightmap_direction = mul(transpose(float3x3(normal, tangent, binormal)), _Tessellation_Heightmap_Direction_Control_Vector);
-#else
- float3 heightmap_direction = normal;
-#endif
- objPos.xyz += heightmap_direction * height;
-#endif
+ // https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
+ float2 p0_clip = p0.xy / p0.w;
+ float2 p1_clip = p1.xy / p1.w;
+ float2 p2_clip = p2.xy / p2.w;
-#endif // _TESSELLATION_HEIGHTMAP
- return objPos;
-}
+ float l01 = distance(p0_clip, p1_clip);
+ float l12 = distance(p1_clip, p2_clip);
+ float l20 = distance(p2_clip, p0_clip);
-tess_factors patch_constant(InputPatch<v2f, 3> patch) {
- tess_factors f;
-#if defined(_TESSELLATION)
-#if defined(_TESSELLATION_RANGE_FACTOR)
- float3 vec = getCenterCamPos() - patch[0].worldPos.xyz;
- float d2 = dot(vec, vec);
- float factor = lerp(
- _Tessellation_Range_Factor_Factor_Near,
- _Tessellation_Range_Factor_Factor_Far,
- smoothstep(
- _Tessellation_Range_Factor_Distance_Near,
- _Tessellation_Range_Factor_Distance_Far,
- d2));
-#else
- float factor = _Tessellation_Factor;
-#endif
-#else
- float factor = 1;
-#endif
+ float edgeLength = _Tessellation_Factor;
-#if defined(_TESSELLATION_HEIGHTMAP)
- [branch]
- if (factor > 3) {
- // Scuffed occlusion culling. Need to know what the position will be after displacement
- // in order to do it right. "Scuffed" because we repeat work :/ this same transform gets
- // applied in the domain shader. This isn't so bad if we assume that we're tessellating
- // at a relatively high factor.
- float3 p0newObjPos = applyHeightmap(patch[0].objPos, patch[0].uv01.xy, patch[0].normal, patch[0].tangent, patch[0].binormal);
- float3 p1newObjPos = applyHeightmap(patch[1].objPos, patch[1].uv01.xy, patch[1].normal, patch[1].tangent, patch[1].binormal);
- float3 p2newObjPos = applyHeightmap(patch[2].objPos, patch[2].uv01.xy, patch[2].normal, patch[2].tangent, patch[2].binormal);
- float4 p0newClipPos = UnityObjectToClipPos(p0newObjPos);
- float4 p1newClipPos = UnityObjectToClipPos(p1newObjPos);
- float4 p2newClipPos = UnityObjectToClipPos(p2newObjPos);
- // Dirty hack to prevent objects that are currently outside the view frustum,
- // but which are after displacement, from being culled.
- float radius = 0.3;
- bool inViewFrustum0 = isInViewFrustum(p0newClipPos, radius);
- bool inViewFrustum1 = isInViewFrustum(p1newClipPos, radius);
- bool inViewFrustum2 = isInViewFrustum(p2newClipPos, radius);
- bool inViewFrustum = inViewFrustum0 || inViewFrustum1 || inViewFrustum2;
- factor = inViewFrustum ? factor : 1;
- }
+ f.edge[2] = l01 * edgeLength;
+ f.edge[0] = l12 * edgeLength;
+ f.edge[1] = l20 * edgeLength;
+
+ f.inside = (f.edge[0] + f.edge[1] + f.edge[2]) * 0.333333f;
+#else
+ f.edge[0] = 1;
+ f.edge[1] = 1;
+ f.edge[2] = 1;
+ f.inside = 1;
#endif
- f.edge[0] = factor;
- f.edge[1] = factor;
- f.edge[2] = factor;
- f.inside = factor;
return f;
}
@@ -172,6 +102,13 @@ v2f domain(
o.uv23 = DOMAIN_INTERP(uv23);
o.color = DOMAIN_INTERP(color);
o.vertexLight = DOMAIN_INTERP(vertexLight);
+#if defined(_TROCHOID)
+ o.orig_pos = DOMAIN_INTERP(orig_pos);
+#endif
+
+#if defined(_TESSELLATION) && defined(_TROCHOID)
+ o.objPos.xyz = trochoid_map(o.orig_pos.xyz);
+#endif
#if defined(_TESSELLATION) && defined(_SHATTER_WAVE)
#if defined(OUTLINE_PASS)
diff --git a/trochoid.cginc b/trochoid.cginc
new file mode 100644
index 0000000..9408894
--- /dev/null
+++ b/trochoid.cginc
@@ -0,0 +1,117 @@
+#ifndef __TROCHOID_MATH
+#define __TROCHOID_MATH
+
+#if defined(_TROCHOID)
+
+#include "globals.cginc"
+
+#define PI 3.14159265
+#define TAU PI * 2.0
+
+float3 _trochoid_map(float theta, float r0, float vert_z)
+{
+ r0 = pow(r0, _Trochoid_r_Power);
+ r0 *= 100;
+
+ float R = _Trochoid_R;
+ float r = _Trochoid_r;
+ float d = _Trochoid_d;
+
+ theta *= max(R, r) * _Trochoid_theta_k;
+ float theta_t = theta + _Time[2] * _Trochoid_t_k;
+
+ float x = (R - r) * cos(theta_t) + d * cos((R - r) * theta_t / r);
+ float y = (R - r) * sin(theta_t) - d * sin((R - r) * theta_t / r);
+ float z = vert_z + cos(theta_t * 5) * .1 + theta * .0002;
+
+ float3 result = float3(x, y, z) * r0;
+ float3 trochoid_scale = float3(_Trochoid_X_Scale, _Trochoid_Y_Scale, _Trochoid_Z_Scale);
+ result *= trochoid_scale;
+ return result;
+}
+
+float3 trochoid_map(float3 pos)
+{
+ float theta = atan2(pos.y, pos.x);
+ float r0 = length(pos.xy) * theta;
+ float z = pos.z;
+ float3 result = _trochoid_map(theta, r0, z);
+ return result;
+}
+
+float3 trochoid_normal(float3 pos)
+{
+ // Parameters of the trochoid
+ float R = _Trochoid_R;
+ float r = _Trochoid_r;
+ float d = _Trochoid_d;
+ float theta_k = max(R, r) * _Trochoid_theta_k;
+ float t_k = _Trochoid_t_k;
+ float time = _Time[2];
+ float r_power = _Trochoid_r_Power;
+
+ // Intermediate variables based on input position
+ float r_orig = max(length(pos.xy), 1e-5); // Avoid division by zero
+ float theta = atan2(pos.y, pos.x);
+
+ float r0 = r_orig * theta;
+ float theta_scaled = theta * theta_k;
+ float theta_t = theta_scaled + time * t_k;
+
+ // Components of the trochoid function before final scaling
+ float C1 = cos(theta_t);
+ float S1 = sin(theta_t);
+ float common_factor = (R - r) / r;
+ float C2 = cos(common_factor * theta_t);
+ float S2 = sin(common_factor * theta_t);
+
+ float p_x = (R - r) * C1 + d * C2;
+ float p_y = (R - r) * S1 - d * S2;
+ float p_z = pos.z + cos(theta_t * 5.0) * 0.1 + theta * 0.0002;
+
+ // Partial derivatives of the components with respect to theta_t
+ float dp_x_dt = -(R - r) * S1 - d * common_factor * S2;
+ float dp_y_dt = (R - r) * C1 - d * common_factor * C2;
+ float dp_z_dt = -sin(theta_t * 5.0) * 5.0 * 0.1;
+
+ // Partial derivatives of intermediate variables w.r.t. pos.x and pos.y
+ float d_theta_dx = -pos.y / (r_orig * r_orig);
+ float d_theta_dy = pos.x / (r_orig * r_orig);
+ float d_r_orig_dx = pos.x / r_orig;
+ float d_r_orig_dy = pos.y / r_orig;
+
+ // The r0-dependent scaling factor and its derivative
+ float F = 100.0 * pow(r0, r_power);
+ float dF_dr0 = 100.0 * r_power * pow(r0, r_power - 1.0);
+
+ // Chain rule for tangents
+ // Tangent with respect to pos.x
+ float d_r0_dx = d_r_orig_dx * theta + r_orig * d_theta_dx;
+ float d_theta_t_dx = d_theta_dx * theta_k;
+ float dF_dx = dF_dr0 * d_r0_dx;
+
+ float dp_x_dx = dp_x_dt * d_theta_t_dx;
+ float dp_y_dx = dp_y_dt * d_theta_t_dx;
+ float dp_z_dx = dp_z_dt * d_theta_t_dx + 0.0002 * d_theta_dx;
+
+ float3 trochoid_scale = float3(_Trochoid_X_Scale, _Trochoid_Y_Scale, _Trochoid_Z_Scale);
+ float3 T_x = trochoid_scale * (dF_dx * float3(p_x, p_y, p_z) + F * float3(dp_x_dx, dp_y_dx, dp_z_dx));
+
+ // Tangent with respect to pos.y
+ float d_r0_dy = d_r_orig_dy * theta + r_orig * d_theta_dy;
+ float d_theta_t_dy = d_theta_dy * theta_k;
+ float dF_dy = dF_dr0 * d_r0_dy;
+
+ float dp_x_dy = dp_x_dt * d_theta_t_dy;
+ float dp_y_dy = dp_y_dt * d_theta_t_dy;
+ float dp_z_dy = dp_z_dt * d_theta_t_dy + 0.0002 * d_theta_dy;
+
+ float3 T_y = trochoid_scale * (dF_dy * float3(p_x, p_y, p_z) + F * float3(dp_x_dy, dp_y_dy, dp_z_dy));
+
+ return normalize(cross(T_x, T_y));
+}
+
+#endif // _TROCHOID
+
+#endif // __TROCHOID_MATH
+