diff options
| author | yum <yum.food.vr@gmail.com> | 2025-08-13 16:51:23 -0700 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2025-08-13 17:03:46 -0700 |
| commit | f1fc3bd51a90432ddb6b7cbc86b8383c923b0dd8 (patch) | |
| tree | 6ce39dd718221f5dad8ee29ecbbfeb7c511b2876 | |
| parent | 32781eb9ab83b504788198df552934ed6da08ee0 (diff) | |
add hypotrochoid gimmick
| -rw-r--r-- | 2ner.cginc | 25 | ||||
| -rw-r--r-- | 2ner.shader | 27 | ||||
| -rw-r--r-- | features.cginc | 4 | ||||
| -rw-r--r-- | globals.cginc | 14 | ||||
| -rw-r--r-- | interpolators.cginc | 4 | ||||
| -rw-r--r-- | tessellation.cginc | 161 | ||||
| -rw-r--r-- | trochoid.cginc | 117 |
7 files changed, 231 insertions, 121 deletions
@@ -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 + |
