import React from 'react'; import type { MachineAnimationModule } from '../../../animations/types'; import { MachinePart, } from './primitives'; import { EngineeringBaseplate, PartLabels, getPartVisualProps, } from './proceduralSceneHelpers'; import type { MachinePartDefinition, ProceduralMachineDefinition, ProceduralMachineModule, ProceduralMachineSceneProps, } from './types'; const BALL_COUNT = 10; const BALL_ORBIT_RADIUS = 0.92; const ballParts: MachinePartDefinition[] = Array.from({ length: BALL_COUNT }, (_, index) => { const id = `ball-${String(index + 1).padStart(2, '0')}`; return { id, name: `Rolling ball ${index + 1}`, description: 'Hardened rolling element that carries load with low friction between the inner and outer races.', material: 'Through-hardened bearing steel', engineeringNote: 'Elastic contact patches create Hertzian stresses at the raceways.', labelPosition: [ Math.cos((index / BALL_COUNT) * Math.PI * 2) * 1.35, Math.sin((index / BALL_COUNT) * Math.PI * 2) * 1.35, 0.72, ], }; }); const definition: ProceduralMachineDefinition = { id: 'ball-bearing', slug: 'ball-bearing', title: 'Ball Bearing', subtitle: 'Rolling contact bearing with cage and load zone', category: 'Structural / Other', difficulty: 'Beginner', summary: 'A radial ball bearing showing inner race rotation, ball orbit, cage speed, and load distribution.', description: 'Ball bearings reduce friction by replacing sliding contact with rolling contact. The cage spaces the balls evenly while load transfers through small elastic contact patches between races and balls.', keywords: ['bearing', 'ball', 'race', 'cage', 'rolling contact', 'load'], complexity: 4, dateAdded: '2025-02-04', typicalRpm: 'Hundreds to >50,000 RPM depending on size', facts: [ { label: 'Contact type', value: 'Point / elliptical contact', detail: 'Under load, balls form small Hertzian contact ellipses with each race.', }, { label: 'Cage role', value: 'Spacing, not load carrying', detail: 'The cage prevents ball-to-ball rubbing and guides rolling elements.', }, { label: 'Friction', value: 'Very low rolling loss', detail: 'Losses come from lubricant shear, seals, contact deformation, and spin.', }, { label: 'Failure signals', value: 'Noise, heat, vibration', detail: 'Spalling or contamination quickly appears as vibration signatures.', }, ], parts: [ { id: 'inner-race', name: 'Inner race', description: 'Rotating raceway mounted to the shaft.', material: 'Hardened chrome steel', labelPosition: [-0.55, -0.52, 0.74], }, { id: 'outer-race', name: 'Outer race', description: 'Stationary outer ring mounted in a housing.', material: 'Hardened chrome steel', labelPosition: [-1.58, 1.03, 0.72], }, { id: 'cage', name: 'Cage / separator', description: 'Retainer that spaces rolling balls evenly around the bearing.', material: 'Stamped steel / polymer / brass', labelPosition: [1.56, 0.95, 0.72], }, ...ballParts, { id: 'load-zone', name: 'Load zone', description: 'Warm colored arc indicating where external radial load concentrates contact stress.', labelPosition: [0, -1.62, 0.72], }, { id: 'lubrication-film', name: 'Lubrication film', description: 'Thin blue film separating surfaces and reducing wear.', labelPosition: [1.55, -1.05, 0.72], defaultOpacity: 0.48, }, ], cameraPresets: [ { id: 'iso', label: 'Isometric', position: [3.2, 2.4, 4.0], target: [0, 0, 0], fov: 40 }, { id: 'front', label: 'Raceways', position: [0, 0, 4.8], target: [0, 0, 0], fov: 36 }, { id: 'load', label: 'Load zone', position: [2.8, 1.1, 3.6], target: [0, -0.3, 0], fov: 42 }, ], tour: { id: 'tour-ball-bearing', machineId: 'ball-bearing', title: 'Rolling contact and load transfer', description: 'See how the races, cage, and balls move relative to each other under load.', steps: [ { id: 'races', title: 'Inner and outer races', body: 'The inner race rotates with the shaft while the outer race remains fixed in the housing.', durationSeconds: 4, partIds: ['inner-race', 'outer-race'], camera: { position: [0.5, 1.2, 4.4], target: [0, 0, 0], duration: 1.1 }, rpm: 220, }, { id: 'rolling-elements', title: 'Balls orbit and spin', body: 'Balls orbit with the cage while also spinning from contact with the raceways.', durationSeconds: 5, partIds: ['ball-01', 'ball-02', 'ball-03', 'cage'], camera: { position: [2.9, 2.0, 3.5], target: [0, 0, 0], duration: 1.1 }, rpm: 260, }, { id: 'load-zone', title: 'Radial load zone', body: 'Contact stress concentrates over a loaded arc rather than all balls carrying equal load.', durationSeconds: 4.5, partIds: ['load-zone', 'ball-07', 'ball-08', 'ball-09'], camera: { position: [2.2, 1.2, 3.8], target: [0, -0.45, 0], duration: 1.1 }, rpm: 180, }, { id: 'lubrication', title: 'Lubrication film', body: 'A thin lubricant film reduces wear, carries heat away, and damps vibration.', durationSeconds: 4, partIds: ['lubrication-film', 'cage'], camera: { position: [-2.6, 1.7, 3.8], target: [0, 0, 0], duration: 1.1 }, rpm: 180, }, ], }, relatedMachineIds: ['differential-gear', 'planetary-gearbox', 'disc-brake-caliper'], }; function partProps( partId: string, props: ProceduralMachineSceneProps, explodeDirection: [number, number, number] = [0, 0, 0], ) { return { partId, registerPart: props.registerPart, explodeDirection, explodedDistance: props.explodedDistance ?? 0, onSelectPart: props.onSelectPart, onHoverPart: props.onHoverPart, ...getPartVisualProps(partId, props), }; } export const ballBearingAnimation: MachineAnimationModule = { id: 'ball-bearing-rolling-contact', machineId: 'ball-bearing', label: 'Rolling elements, cage speed, and load zone', version: '1.0.0', defaultRpm: 220, minRpm: 10, maxRpm: 4000, cycleRevolutions: 1, cycleSteps: 20, loop: true, supportsReverse: true, reducedMotionStrategy: 'slow', update(context) { const shaftAngle = context.shaftAngle; const cageAngle = shaftAngle * 0.42; const ballSpin = -shaftAngle * 1.9; const innerRace = context.getPart('inner-race'); if (innerRace) innerRace.rotation.z = -shaftAngle; const cage = context.getPart('cage'); if (cage) cage.rotation.z = -cageAngle; const film = context.getPart('lubrication-film'); if (film) { film.rotation.z = -shaftAngle * 0.18; film.scale.setScalar(1 + 0.025 * Math.sin(shaftAngle * 3)); } for (let index = 0; index < BALL_COUNT; index += 1) { const ball = context.getPart(`ball-${String(index + 1).padStart(2, '0')}`); if (!ball) continue; const angle = (index / BALL_COUNT) * Math.PI * 2 - cageAngle; ball.position.set(Math.cos(angle) * BALL_ORBIT_RADIUS, Math.sin(angle) * BALL_ORBIT_RADIUS, 0.05); ball.rotation.x = ballSpin + index; ball.rotation.y = shaftAngle * 0.35; const loadContribution = Math.max(0, -Math.sin(angle)); ball.scale.setScalar(1 + loadContribution * 0.06); } const loadZone = context.getPart('load-zone'); if (loadZone) { loadZone.scale.setScalar(1 + 0.04 * Math.sin(shaftAngle * 2)); } context.setPhaseLabel('Inner race drives ball orbit; cage rotates at a fraction of shaft speed'); context.clearHighlights(); if (context.cycleProgress < 0.42) context.highlightParts(['inner-race', 'cage'], 0.7); else context.highlightParts(['load-zone', 'ball-07', 'ball-08', 'ball-09'], 0.82); }, }; export function BallBearingScene(props: ProceduralMachineSceneProps): JSX.Element { return ( {Array.from({ length: BALL_COUNT }, (_, index) => { const angle = (index / BALL_COUNT) * Math.PI * 2; return ( ); })} {Array.from({ length: BALL_COUNT }, (_, index) => { const id = `ball-${String(index + 1).padStart(2, '0')}`; const angle = (index / BALL_COUNT) * Math.PI * 2; return ( ); })} ); } export const ballBearing: ProceduralMachineModule = { definition, animationModule: ballBearingAnimation, Scene: BallBearingScene, };