Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ python -m http.server 8000

- `v0.5/` - Current version (plain HTML/JS, no build step required)
- `v0.1/`, `v0.2/` - Previous versions with build systems

## Features

- Dynamic geometry controls for paths, cross-sections, and modifiers
- Shape animation controls (`Animate Geometry`, amplitude, speed) that pulse the tube radius live without reloading the scene
14 changes: 13 additions & 1 deletion v0.5/src/geometry/tube.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,21 @@ function computePosition(u, v, rTube, params, isInner = false) {
radius = modifiers['taper'].apply(u, radius, params);
}

// Apply shape animation offset (breathing/pulsing effect)
const animationOffset = params.shapeAnimationOffset || 0;
if (animationOffset !== 0) {
radius *= (1 + animationOffset);
}

// Ensure radius stays within valid bounds
const wallThickness = params.wallThickness || 0;
const minRadius = params.rMin || 0.015;
const minOuterRadius = wallThickness > 0 ? minRadius + wallThickness : minRadius;
radius = Math.max(radius, minOuterRadius);

// Apply wall thickness for inner surface
if (isInner) {
radius -= params.wallThickness;
radius = Math.max(radius - wallThickness, minRadius);
}

// Apply twist modifier
Expand Down
6 changes: 6 additions & 0 deletions v0.5/src/params.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ const defaultParams = {
waveFrequency: 1.0, // Wave frequency
wavePhase: 0.0, // Wave phase

// Shape animation
animateShape: false, // Enable geometry animation
shapeAnimationSpeed: 0.5, // Cycles per second
shapeAnimationAmplitude: 0.15, // Scale factor applied to tube radius
shapeAnimationOffset: 0.0, // Internal state, updated at runtime

// Legacy parameters (for backward compatibility)
mode: 'Exponential', // 'Exponential' or 'Linear'
twist: 0.0, // Legacy twist (maps to twistAmount)
Expand Down
40 changes: 40 additions & 0 deletions v0.5/src/rendering/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ function setupAnimationLoop(renderer, scene, sceneState, params, updateMesh) {
let needsOutlineUpdate = false;
let lastUpdateTime = 0;
const updateThrottle = 200; // Update outline at most every 200ms
const animationFrameInterval = 1000 / 30; // Target ~30 mesh rebuilds per second during animation
let lastGeometryUpdate = 0;
let previousTimestamp = performance.now();
let shapeAnimationAngle = 0;
let animationWasActive = false;

// Update outline when camera changes significantly
function checkCameraChange() {
Expand All @@ -41,12 +46,47 @@ function setupAnimationLoop(renderer, scene, sceneState, params, updateMesh) {
function animate() {
requestAnimationFrame(animate);
sceneState.orbitControls.update();
const now = performance.now();
const deltaSeconds = (now - previousTimestamp) / 1000;
previousTimestamp = now;

let requiresMeshUpdate = false;
const amplitude = params.shapeAnimationAmplitude || 0;
const animationEnabled = params.animateShape && amplitude > 0;

if (animationEnabled) {
if (!animationWasActive) {
lastGeometryUpdate = 0; // Force immediate rebuild on activation
}
const speed = Math.max(params.shapeAnimationSpeed || 0, 0);
if (speed > 0) {
shapeAnimationAngle = (shapeAnimationAngle + deltaSeconds * speed * Math.PI * 2) % (Math.PI * 2);
}
const nextOffset = Math.sin(shapeAnimationAngle) * amplitude;
params.shapeAnimationOffset = nextOffset;

if ((now - lastGeometryUpdate) >= animationFrameInterval) {
requiresMeshUpdate = true;
}
} else {
const currentOffset = params.shapeAnimationOffset || 0;
if (Math.abs(currentOffset) > 1e-4) {
params.shapeAnimationOffset = 0;
requiresMeshUpdate = true;
}
}
animationWasActive = animationEnabled;

// Update outline if camera changed and outline is enabled (throttled)
checkCameraChange();
if (needsOutlineUpdate) {
requiresMeshUpdate = true;
}

if (requiresMeshUpdate) {
updateMesh(sceneState.meshGroup, params, sceneState.currentCamera);
needsOutlineUpdate = false;
lastGeometryUpdate = now;
}

renderer.render(scene, sceneState.currentCamera);
Expand Down
37 changes: 33 additions & 4 deletions v0.5/src/ui/dynamicControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
function createControlGroup(label, controlElement) {
const group = document.createElement('div');
group.className = 'control-group';

const labelEl = document.createElement('label');
labelEl.textContent = label;
if (controlElement.id) {
labelEl.setAttribute('for', controlElement.id);
}

group.appendChild(labelEl);
group.appendChild(controlElement);

return group;
}

Expand Down Expand Up @@ -238,7 +238,7 @@ function generateGeometryControls(params, onUpdate) {
toggleLabel.setAttribute('for', `modifier_${key}`);
toggleLabel.textContent = modifier.name;
toggleContainer.appendChild(toggleLabel);

const toggle = document.createElement('input');
toggle.type = 'checkbox';
toggle.id = `modifier_${key}`;
Expand Down Expand Up @@ -303,6 +303,35 @@ function generateGeometryControls(params, onUpdate) {
container.appendChild(controlsDiv);
});

// Shape animation controls
const animationHeader = document.createElement('h4');
animationHeader.textContent = 'Shape Animation';
animationHeader.style.marginTop = '20px';
animationHeader.style.marginBottom = '10px';
container.appendChild(animationHeader);

addControl(container, 'animateShape', {
type: 'checkbox',
label: 'Animate Geometry',
default: false
}, params, onUpdate);

addControl(container, 'shapeAnimationAmplitude', {
type: 'range',
min: 0,
max: 0.5,
step: 0.01,
label: 'Animation Amplitude'
}, params, onUpdate);

addControl(container, 'shapeAnimationSpeed', {
type: 'range',
min: 0.1,
max: 3,
step: 0.05,
label: 'Animation Speed (cycles/s)'
}, params, onUpdate);

// Mesh density controls
const densityHeader = document.createElement('h4');
densityHeader.textContent = 'Mesh Density';
Expand Down