Files
cadSet/text-to-cad/models/implicits/planetary-gear.implicit.js
T
2026-07-22 19:38:36 +08:00

327 lines
12 KiB
JavaScript

const GLSL = `
const float PLANETARY_TAU = 6.283185307179586;
float planetaryRoundedSunTeeth() {
return clamp(floor(sunTeeth + 0.5), 12.0, 30.0);
}
float planetaryPlanetTeeth() {
return max(floor(planetTeeth + 0.5), 3.0);
}
float planetarySunTeeth() {
float sun = planetaryRoundedSunTeeth();
float planet = planetaryPlanetTeeth();
float remainder = mod(sun + planet, 3.0);
float adjusted = sun;
if (remainder > 0.5 && remainder < 1.5) {
adjusted -= 1.0;
} else if (remainder >= 1.5) {
adjusted += 1.0;
}
if (adjusted < 12.0) {
adjusted += 3.0;
}
if (adjusted > 30.0) {
adjusted -= 3.0;
}
return max(adjusted, 3.0);
}
float planetaryRingTeeth() {
return planetarySunTeeth() + planetaryPlanetTeeth() * 2.0;
}
float planetarySunPitch() {
return gearModule * planetarySunTeeth() * 0.5;
}
float planetaryPlanetPitch() {
return gearModule * planetaryPlanetTeeth() * 0.5;
}
float planetaryMeshSpread() {
return max(meshOffset, 0.0);
}
float planetaryRingPitch() {
return gearModule * planetaryRingTeeth() * 0.5 + planetaryMeshSpread();
}
float planetaryDriveRadians() {
return meshPhase * 0.017453292519943295;
}
float planetaryCarrierRadians() {
return carrierAngle * 0.017453292519943295;
}
float planetaryDrivenRingRadians() {
float sun = planetarySunTeeth();
float ring = planetaryRingTeeth();
float carrier = planetaryCarrierRadians();
return carrier - (sun / max(ring, 1.0)) * (planetaryDriveRadians() - carrier);
}
float planetaryRingRadians() {
float oddPlanetOffset = mod(planetaryPlanetTeeth(), 2.0) * PLANETARY_TAU * 0.5 / max(planetaryRingTeeth(), 1.0);
return ringPhase * 0.017453292519943295 + planetaryDrivenRingRadians() + oddPlanetOffset;
}
float planetaryToothWave(float angle, float teeth, float phase) {
float wave = 0.5 + 0.5 * cos((angle - phase) * teeth);
return wave * wave * (3.0 - 2.0 * wave);
}
float planetaryExtrude(float d2, float z, float halfHeight, float bevel) {
vec2 q = vec2(d2, abs(z) - halfHeight + bevel);
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - bevel;
}
float planetaryExternalGear2(vec2 p, float pitchRadius, float teeth, float phase) {
float theta = atan(p.y, p.x);
float tooth = planetaryToothWave(theta, teeth, phase);
float root = toothDepth * 0.72;
float addendum = toothDepth * 0.82;
float meshClearance = max(backlash, max(gearModule * 0.05, toothDepth * 0.18));
float boundary = pitchRadius - root + tooth * (root + addendum) - meshClearance * 0.52;
return length(p) - boundary;
}
float planetaryInternalRing2(vec2 p, float pitchRadius, float teeth, float phase) {
float theta = atan(p.y, p.x);
float tooth = planetaryToothWave(theta, teeth, phase);
float root = toothDepth * 0.78;
float addendum = toothDepth * 0.9;
float meshClearance = max(backlash, max(gearModule * 0.05, toothDepth * 0.18));
float innerBoundary = pitchRadius + root - tooth * (root + addendum) + meshClearance * 0.86;
float outerBoundary = pitchRadius + toothDepth * 2.55 + gearModule * 3.0;
return max(innerBoundary - length(p), length(p) - outerBoundary);
}
float planetaryExternalGear(vec3 p, vec2 center, float pitchRadius, float teeth, float phase) {
float gear = planetaryExternalGear2(p.xy - center, pitchRadius, teeth, phase);
return planetaryExtrude(gear, p.z, thickness * 0.5, min(gearModule * 0.16, 0.38));
}
float planetaryRingGear(vec3 p) {
float ring = planetaryInternalRing2(p.xy, planetaryRingPitch(), planetaryRingTeeth(), planetaryRingRadians());
return planetaryExtrude(ring, p.z, thickness * 0.5, min(gearModule * 0.14, 0.34));
}
float planetarySunGear(vec3 p) {
float gear = planetaryExternalGear(p, vec2(0.0), planetarySunPitch(), planetarySunTeeth(), planetaryDriveRadians());
float bore = implicit_cylinder_capped(
p,
vec3(0.0, 0.0, -thickness),
vec3(0.0, 0.0, thickness),
max(gearModule * 1.15, planetarySunPitch() * 0.19)
);
float hub = planetaryExtrude(length(p.xy) - planetarySunPitch() * 0.42, p.z, thickness * 0.58, min(gearModule * 0.18, 0.42));
return implicit_intersect_round(implicit_union_round(gear, hub, gearModule * 0.18), -bore, gearModule * 0.12);
}
vec2 planetaryPlanetCenter(float index) {
float angle = planetaryCarrierRadians() + index * PLANETARY_TAU / 3.0;
float orbit = planetarySunPitch() + planetaryPlanetPitch() + planetaryMeshSpread();
return orbit * vec2(cos(angle), sin(angle));
}
float planetaryPlanetPhase(float index) {
float planet = planetaryPlanetTeeth();
float centerAngle = planetaryCarrierRadians() + index * PLANETARY_TAU / 3.0;
float sunRatio = planetarySunTeeth() / max(planet, 1.0);
float carrierRatio = (planetarySunTeeth() + planet) / max(planet, 1.0);
float toothOffset = PLANETARY_TAU * 0.5 - PLANETARY_TAU * 0.5 / max(planet, 1.0);
return centerAngle * carrierRatio - planetaryDriveRadians() * sunRatio + toothOffset;
}
float planetaryPlanetGear(vec3 p, float index) {
vec2 center = planetaryPlanetCenter(index);
float gear = planetaryExternalGear(p, center, planetaryPlanetPitch(), planetaryPlanetTeeth(), planetaryPlanetPhase(index));
float bore = implicit_cylinder_capped(
p,
vec3(center, -thickness),
vec3(center, thickness),
max(gearModule * 0.86, planetaryPlanetPitch() * 0.2)
);
float pinBoss = planetaryExtrude(length(p.xy - center) - max(gearModule * 1.35, planetaryPlanetPitch() * 0.34), p.z, thickness * 0.54, min(gearModule * 0.16, 0.36));
return implicit_intersect_round(implicit_union_round(gear, pinBoss, gearModule * 0.15), -bore, gearModule * 0.1);
}
float planetaryCarrierArm(vec3 p, vec2 center) {
float zCenter = -thickness * 0.72;
float arm2 = implicit_line_segment2(p.xy, vec2(0.0), center) - gearModule * 0.95;
return planetaryExtrude(arm2, p.z - zCenter, thickness * 0.16, min(gearModule * 0.16, 0.35));
}
float planetaryCarrier(vec3 p) {
float hub = planetaryExtrude(length(p.xy) - max(gearModule * 2.1, planetarySunPitch() * 0.34), p.z + thickness * 0.72, thickness * 0.17, min(gearModule * 0.16, 0.35));
float carrier = hub;
for (int i = 0; i < 3; i += 1) {
vec2 center = planetaryPlanetCenter(float(i));
float arm = planetaryCarrierArm(p, center);
float pin = planetaryExtrude(length(p.xy - center) - max(gearModule * 1.05, planetaryPlanetPitch() * 0.26), p.z + thickness * 0.72, thickness * 0.33, min(gearModule * 0.16, 0.35));
carrier = implicit_union_round(carrier, arm, gearModule * 0.32);
carrier = implicit_union_round(carrier, pin, gearModule * 0.2);
}
return carrier;
}
float sdf(vec3 p) {
float result = planetaryRingGear(p);
result = min(result, planetarySunGear(p));
for (int i = 0; i < 3; i += 1) {
result = min(result, planetaryPlanetGear(p, float(i)));
}
if (showCarrier) {
result = min(result, planetaryCarrier(p));
}
return result;
}
vec3 planetaryPalette(float component, vec3 p, vec3 normal) {
vec3 color = ringColor;
if (component > 0.5) {
color = sunColor;
}
if (component >= 1.5) {
color = planetAColor;
}
if (component >= 2.33) {
color = planetBColor;
}
if (component >= 2.66) {
color = planetCColor;
}
if (component >= 3.5) {
color = carrierColor;
}
float toothLine = 0.5 + 0.5 * sin(atan(p.y, p.x) * planetaryRingTeeth());
float faceLight = smoothstep(0.22, 1.0, abs(normal.z));
color *= 0.92 + 0.15 * toothLine;
color = mix(color * 0.72, min(color * 1.18 + vec3(0.05), vec3(1.0)), faceLight);
return clamp(color, vec3(0.0), vec3(1.0));
}
vec3 color(vec3 p, vec3 normal) {
float best = planetaryRingGear(p);
float component = 0.0;
float sun = planetarySunGear(p);
if (sun < best) {
best = sun;
component = 1.0;
}
for (int i = 0; i < 3; i += 1) {
float planet = planetaryPlanetGear(p, float(i));
if (planet < best) {
best = planet;
component = 2.0 + float(i) * 0.34;
}
}
if (showCarrier) {
float carrier = planetaryCarrier(p);
if (carrier < best) {
component = 4.0;
}
}
return planetaryPalette(component, p, normal);
}
`;
const PLANETARY_MIN_SUN_TEETH = 12;
const PLANETARY_MAX_SUN_TEETH = 30;
function roundedTeeth(value, fallback) {
const numeric = Number(value);
return Math.round(Number.isFinite(numeric) ? numeric : fallback);
}
function compatibleSunTeeth(params) {
const planetTeeth = Math.max(3, roundedTeeth(params.planetTeeth, 12));
const rawSunTeeth = Math.min(
Math.max(roundedTeeth(params.sunTeeth, 18), PLANETARY_MIN_SUN_TEETH),
PLANETARY_MAX_SUN_TEETH
);
const candidates = [rawSunTeeth - 1, rawSunTeeth, rawSunTeeth + 1, rawSunTeeth - 2, rawSunTeeth + 2]
.filter((candidate) => candidate >= PLANETARY_MIN_SUN_TEETH && candidate <= PLANETARY_MAX_SUN_TEETH)
.filter((candidate) => (candidate + planetTeeth) % 3 === 0);
return candidates.length ? candidates[0] : rawSunTeeth;
}
export default {
schema: "implicit.js/0.1.0",
name: "planetary gear",
description: "A shader-parametric planetary gear set with a sun gear, three planet gears, internal ring teeth, and an under-carrier.",
units: "mm",
params: {
sunTeeth: { type: "number", label: "Sun teeth", min: 12, max: 30, step: 1, default: 18 },
planetTeeth: { type: "number", label: "Planet teeth", min: 8, max: 18, step: 1, default: 12 },
gearModule: { type: "number", label: "Gear module", min: 1.2, max: 3.2, step: 0.05, default: 2.0, unit: "mm" },
toothDepth: { type: "number", label: "Tooth depth", min: 0.6, max: 3.6, step: 0.05, default: 1.55, unit: "mm" },
thickness: { type: "number", label: "Gear thickness", min: 3, max: 12, step: 0.25, default: 6.5, unit: "mm" },
backlash: { type: "number", label: "Backlash", min: 0, max: 1.4, step: 0.02, default: 0.22, unit: "mm" },
meshOffset: { type: "number", label: "Mesh spread", min: 0, max: 5, step: 0.05, default: 0, unit: "mm" },
meshPhase: { type: "number", label: "Drive phase", min: 0, max: 360, step: 1, default: 16, unit: "deg" },
carrierAngle: { type: "number", label: "Carrier orbit", min: -180, max: 180, step: 1, default: 0, unit: "deg" },
ringPhase: { type: "number", label: "Ring phase", min: -180, max: 180, step: 1, default: 0, unit: "deg" },
showCarrier: { type: "boolean", label: "Show carrier", default: true },
ringColor: { type: "color", label: "Ring color", default: "#00a7ff" },
sunColor: { type: "color", label: "Sun color", default: "#ffe83d" },
planetAColor: { type: "color", label: "Planet A", default: "#ff5b2e" },
planetBColor: { type: "color", label: "Planet B", default: "#d94dff" },
planetCColor: { type: "color", label: "Planet C", default: "#7cff4f" },
carrierColor: { type: "color", label: "Carrier color", default: "#8b6cff" }
},
animations: {
meshCycle: {
label: "Mesh cycle",
duration: 8,
loop: true,
update({ progress, params, set }) {
const drive = progress * 360;
const sunTeeth = compatibleSunTeeth(params);
const planetTeeth = Math.max(3, roundedTeeth(params.planetTeeth, 12));
const ringTeeth = sunTeeth + planetTeeth * 2;
const carrier = drive * sunTeeth / Math.max(sunTeeth + ringTeeth, 1);
set("meshPhase", drive);
set("carrierAngle", ((carrier + 180) % 360) - 180);
set("ringPhase", 0);
}
},
explodeMesh: {
label: "Explode mesh",
duration: 5.5,
loop: true,
update({ progress, set }) {
const wave = 0.5 - 0.5 * Math.cos(progress * Math.PI * 2);
set("meshOffset", wave * 4.2);
set("carrierAngle", -28 + wave * 56);
}
}
},
bounds: ({ params }) => {
const sunTeeth = compatibleSunTeeth(params);
const planetTeeth = Math.max(3, roundedTeeth(params.planetTeeth, 12));
const ringTeeth = sunTeeth + planetTeeth * 2;
const spread = Math.max(params.meshOffset, 0);
const ringPitch = params.gearModule * ringTeeth * 0.5 + spread;
const ringOuter = ringPitch + params.toothDepth * 2.8 + params.gearModule * 3.4 + 2;
const z = params.thickness * 0.92 + 5;
return [[-ringOuter, -ringOuter, -z], [ringOuter, ringOuter, z]];
},
render: ({ params }) => ({
steps: 320,
epsilon: Math.max(0.005, params.gearModule * 0.0025),
normalEpsilon: Math.max(0.038, params.gearModule * 0.018),
stepScale: 0.5,
maxStep: Math.max(params.gearModule * 0.62, 0.65)
}),
glsl: GLSL
};