Skip to content

Commit 7195bf9

Browse files
committed
add more shape types
1 parent 66ad2cd commit 7195bf9

File tree

3 files changed

+409
-43
lines changed

3 files changed

+409
-43
lines changed

chromatic-shader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const ChromaticAberrationShader = {
33
uniforms: {
44
"tDiffuse": { value: null },
55
"resolution": { value: new THREE.Vector2(1, 1) },
6-
"strength": { value: 0.2 } // Strength of the chromatic aberration effect
6+
"strength": { value: 0.5 } // Strength of the chromatic aberration effect
77
},
88

99
vertexShader: /* glsl */`

geometry.js

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,331 @@ function createSupernova(i, count) {
259259
Math.sin(phi) * Math.sin(theta) * radius,
260260
Math.cos(phi) * radius
261261
);
262+
}
263+
264+
function createKleinBottle(i, count) {
265+
// Klein Bottle parameters
266+
const a = 15; // Main radius
267+
const b = 4; // Tube radius
268+
const scale = 2.5; // Overall scale
269+
270+
// Use uniform distribution across the surface
271+
const lengthSteps = Math.ceil(Math.sqrt(count * 0.5));
272+
const circSteps = Math.ceil(count / lengthSteps);
273+
274+
// Calculate position in the parametric space
275+
const lengthIndex = i % lengthSteps;
276+
const circIndex = Math.floor(i / lengthSteps) % circSteps;
277+
278+
// Normalize to appropriate ranges
279+
const u = (lengthIndex / lengthSteps) * Math.PI * 2; // 0 to 2π
280+
const v = (circIndex / circSteps) * Math.PI * 2; // 0 to 2π
281+
282+
// Klein Bottle parametric equation
283+
let x, y, z;
284+
285+
// The Klein Bottle has different regions with different parametric equations
286+
if (u < Math.PI) {
287+
// First half (handle and transition region)
288+
x = scale * (a * (1 - Math.cos(u) / 2) * Math.cos(v) - b * Math.sin(u) / 2);
289+
y = scale * (a * (1 - Math.cos(u) / 2) * Math.sin(v));
290+
z = scale * (a * Math.sin(u) / 2 + b * Math.sin(u) * Math.cos(v));
291+
} else {
292+
// Second half (main bottle body)
293+
x = scale * (a * (1 + Math.cos(u) / 2) * Math.cos(v) + b * Math.sin(u) / 2);
294+
y = scale * (a * (1 + Math.cos(u) / 2) * Math.sin(v));
295+
z = scale * (-a * Math.sin(u) / 2 + b * Math.sin(u) * Math.cos(v));
296+
}
297+
298+
return new THREE.Vector3(x, y, z);
299+
}
300+
301+
function createFlower(i, count) {
302+
// Flower/Dandelion parameters
303+
const numPetals = 12; // Number of petals
304+
const petalLength = 25; // Length of petals
305+
const centerRadius = 10; // Radius of center sphere
306+
const petalWidth = 0.3; // Width of petals (0-1)
307+
const petalCurve = 0.6; // How much petals curve outward (0-1)
308+
309+
// Calculate whether this particle is in the center or on a petal
310+
const centerParticleCount = Math.floor(count * 0.3); // 30% of particles in center
311+
const isCenter = i < centerParticleCount;
312+
313+
if (isCenter) {
314+
// Center particles form a sphere
315+
const t = i / centerParticleCount;
316+
const phi = Math.acos(2 * t - 1);
317+
const theta = 2 * Math.PI * i * (1 + Math.sqrt(5)); // Golden ratio distribution
318+
319+
// Create a sphere for the center
320+
return new THREE.Vector3(
321+
Math.sin(phi) * Math.cos(theta) * centerRadius,
322+
Math.sin(phi) * Math.sin(theta) * centerRadius,
323+
Math.cos(phi) * centerRadius
324+
);
325+
} else {
326+
// Petal particles
327+
const petalParticleCount = count - centerParticleCount;
328+
const petalIndex = i - centerParticleCount;
329+
330+
// Determine which petal this particle belongs to
331+
const petalId = petalIndex % numPetals;
332+
const positionInPetal = Math.floor(petalIndex / numPetals) / Math.floor(petalParticleCount / numPetals);
333+
334+
// Calculate angle of this petal
335+
const petalAngle = (petalId / numPetals) * Math.PI * 2;
336+
337+
// Calculate radial distance from center
338+
// Use a curve so particles are denser at tip and base
339+
const radialT = Math.pow(positionInPetal, 0.7); // Adjust density along petal
340+
const radialDist = centerRadius + (petalLength * radialT);
341+
342+
// Calculate width displacement (thicker at base, thinner at tip)
343+
const widthFactor = petalWidth * (1 - radialT * 0.7);
344+
const randomWidth = (Math.random() * 2 - 1) * widthFactor * petalLength;
345+
346+
// Calculate curve displacement (petals curve outward)
347+
const curveFactor = petalCurve * Math.sin(positionInPetal * Math.PI);
348+
349+
// Convert to Cartesian coordinates
350+
// Main direction follows the petal angle
351+
const x = Math.cos(petalAngle) * radialDist +
352+
Math.cos(petalAngle + Math.PI/2) * randomWidth;
353+
354+
const y = Math.sin(petalAngle) * radialDist +
355+
Math.sin(petalAngle + Math.PI/2) * randomWidth;
356+
357+
// Z coordinate creates the upward curve of petals
358+
const z = curveFactor * petalLength * (1 - Math.cos(positionInPetal * Math.PI));
359+
360+
return new THREE.Vector3(x, y, z);
361+
}
362+
}
363+
364+
function createFractalTree(i, count) {
365+
// Fractal Tree parameters
366+
const trunkLength = 35; // Initial trunk length
367+
const branchRatio = 0.67; // Each branch is this ratio of parent length
368+
const maxDepth = 6; // Maximum branching depth
369+
const branchAngle = Math.PI / 5; // Angle between branches (36 degrees)
370+
371+
// Pre-calculate the total particles needed per depth level
372+
// Distribute particles more towards deeper levels
373+
const particlesPerLevel = [];
374+
let totalWeight = 0;
375+
376+
for (let depth = 0; depth <= maxDepth; depth++) {
377+
// More branches at deeper levels, distribute particles accordingly
378+
// Each level has 2^depth branches
379+
const branches = Math.pow(2, depth);
380+
const weight = branches * Math.pow(branchRatio, depth);
381+
totalWeight += weight;
382+
particlesPerLevel.push(weight);
383+
}
384+
385+
// Normalize to get actual count per level
386+
let cumulativeCount = 0;
387+
const particleCount = [];
388+
389+
for (let depth = 0; depth <= maxDepth; depth++) {
390+
const levelCount = Math.floor((particlesPerLevel[depth] / totalWeight) * count);
391+
particleCount.push(levelCount);
392+
cumulativeCount += levelCount;
393+
}
394+
395+
// Adjust the last level to ensure we use exactly count particles
396+
particleCount[maxDepth] += (count - cumulativeCount);
397+
398+
// Determine which depth level this particle belongs to
399+
let depth = 0;
400+
let levelStartIndex = 0;
401+
402+
while (depth < maxDepth && i >= levelStartIndex + particleCount[depth]) {
403+
levelStartIndex += particleCount[depth];
404+
depth++;
405+
}
406+
407+
// Calculate the relative index within this depth level
408+
const indexInLevel = i - levelStartIndex;
409+
const levelCount = particleCount[depth];
410+
411+
// Calculate position parameters
412+
const t = indexInLevel / (levelCount || 1); // Normalized position in level
413+
414+
// For the trunk (depth 0)
415+
if (depth === 0) {
416+
// Simple line for the trunk
417+
return new THREE.Vector3(
418+
(Math.random() * 2 - 1) * 0.5, // Small random spread for thickness
419+
-trunkLength / 2 + t * trunkLength,
420+
(Math.random() * 2 - 1) * 0.5 // Small random spread for thickness
421+
);
422+
}
423+
424+
// For branches at higher depths
425+
// Determine which branch in the current depth
426+
const branchCount = Math.pow(2, depth);
427+
const branchIndex = Math.floor(t * branchCount) % branchCount;
428+
const positionInBranch = (t * branchCount) % 1;
429+
430+
// Calculate the position based on branch path
431+
let x = 0, y = trunkLength / 2, z = 0; // Start at top of trunk
432+
let currentLength = trunkLength;
433+
let currentAngle = 0;
434+
435+
// For the first depth level (branching from trunk)
436+
if (depth >= 1) {
437+
currentLength *= branchRatio;
438+
// Determine left or right branch
439+
currentAngle = (branchIndex % 2 === 0) ? branchAngle : -branchAngle;
440+
441+
// Move up the branch
442+
x += Math.sin(currentAngle) * currentLength * positionInBranch;
443+
y += Math.cos(currentAngle) * currentLength * positionInBranch;
444+
}
445+
446+
// For higher depths, calculate the full path
447+
for (let d = 2; d <= depth; d++) {
448+
currentLength *= branchRatio;
449+
450+
// Determine branch direction at this depth
451+
// Use bit operations to determine left vs right at each branch
452+
const pathBit = (branchIndex >> (depth - d)) & 1;
453+
const nextAngle = pathBit === 0 ? branchAngle : -branchAngle;
454+
455+
// Only apply movement for the branches we've completed
456+
if (d < depth) {
457+
// Rotate the current direction and move full branch length
458+
currentAngle += nextAngle;
459+
x += Math.sin(currentAngle) * currentLength;
460+
y += Math.cos(currentAngle) * currentLength;
461+
} else {
462+
// For the final branch, move partially based on positionInBranch
463+
currentAngle += nextAngle;
464+
x += Math.sin(currentAngle) * currentLength * positionInBranch;
465+
y += Math.cos(currentAngle) * currentLength * positionInBranch;
466+
}
467+
}
468+
469+
// Add small random offsets for volume
470+
const randomSpread = 0.8 * (1 - Math.pow(branchRatio, depth));
471+
x += (Math.random() * 2 - 1) * randomSpread;
472+
z += (Math.random() * 2 - 1) * randomSpread;
473+
474+
return new THREE.Vector3(x, y, z);
475+
}
476+
477+
function createVoronoi(i, count) {
478+
// Voronoi parameters
479+
const radius = 30; // Maximum radius of the sphere to place points on
480+
const numSites = 25; // Number of Voronoi sites (cells)
481+
const cellThickness = 2.5; // Thickness of the cell boundaries
482+
const jitter = 0.5; // Random jitter to make edges look more natural
483+
484+
// First, we generate fixed pseudorandom Voronoi sites (cell centers)
485+
// We use a deterministic approach to ensure sites are the same for each call
486+
const sites = [];
487+
for (let s = 0; s < numSites; s++) {
488+
// Use a specific seed formula for each site to ensure repeatability
489+
const seed1 = Math.sin(s * 42.5) * 10000;
490+
const seed2 = Math.cos(s * 15.3) * 10000;
491+
const seed3 = Math.sin(s * 33.7) * 10000;
492+
493+
// Generate points on a sphere using spherical coordinates
494+
const theta = 2 * Math.PI * (seed1 - Math.floor(seed1));
495+
const phi = Math.acos(2 * (seed2 - Math.floor(seed2)) - 1);
496+
497+
sites.push(new THREE.Vector3(
498+
Math.sin(phi) * Math.cos(theta) * radius,
499+
Math.sin(phi) * Math.sin(theta) * radius,
500+
Math.cos(phi) * radius
501+
));
502+
}
503+
504+
// Now we generate points that lie primarily along the boundaries between Voronoi cells
505+
506+
// First, decide if this is a site point (center of a cell) or a boundary point
507+
const sitePoints = Math.min(numSites, Math.floor(count * 0.1)); // 10% of points are sites
508+
509+
if (i < sitePoints) {
510+
// Place this point at a Voronoi site center
511+
const siteIndex = i % sites.length;
512+
const site = sites[siteIndex];
513+
514+
// Return the site position with small random variation
515+
return new THREE.Vector3(
516+
site.x + (Math.random() * 2 - 1) * jitter,
517+
site.y + (Math.random() * 2 - 1) * jitter,
518+
site.z + (Math.random() * 2 - 1) * jitter
519+
);
520+
} else {
521+
// This is a boundary point
522+
// Generate a random point on the sphere
523+
const u = Math.random();
524+
const v = Math.random();
525+
const theta = 2 * Math.PI * u;
526+
const phi = Math.acos(2 * v - 1);
527+
528+
const point = new THREE.Vector3(
529+
Math.sin(phi) * Math.cos(theta) * radius,
530+
Math.sin(phi) * Math.sin(theta) * radius,
531+
Math.cos(phi) * radius
532+
);
533+
534+
// Find the two closest sites to this point
535+
let closestDist = Infinity;
536+
let secondClosestDist = Infinity;
537+
let closestSite = null;
538+
let secondClosestSite = null;
539+
540+
for (const site of sites) {
541+
const dist = point.distanceTo(site);
542+
543+
if (dist < closestDist) {
544+
secondClosestDist = closestDist;
545+
secondClosestSite = closestSite;
546+
closestDist = dist;
547+
closestSite = site;
548+
} else if (dist < secondClosestDist) {
549+
secondClosestDist = dist;
550+
secondClosestSite = site;
551+
}
552+
}
553+
554+
// Check if this point is near the boundary between the two closest cells
555+
const distDiff = Math.abs(closestDist - secondClosestDist);
556+
557+
if (distDiff < cellThickness) {
558+
// This point is on a boundary
559+
560+
// Add small random jitter to make the boundary look more natural
561+
point.x += (Math.random() * 2 - 1) * jitter;
562+
point.y += (Math.random() * 2 - 1) * jitter;
563+
point.z += (Math.random() * 2 - 1) * jitter;
564+
565+
// Project the point back onto the sphere
566+
point.normalize().multiplyScalar(radius);
567+
568+
return point;
569+
} else {
570+
// Not a boundary point, retry with a different approach
571+
// Move the point slightly toward the boundary
572+
const midpoint = new THREE.Vector3().addVectors(closestSite, secondClosestSite).multiplyScalar(0.5);
573+
const dirToMid = new THREE.Vector3().subVectors(midpoint, point).normalize();
574+
575+
// Move point toward the midpoint between cells
576+
point.add(dirToMid.multiplyScalar(distDiff * 0.7));
577+
578+
// Add small random jitter
579+
point.x += (Math.random() * 2 - 1) * jitter;
580+
point.y += (Math.random() * 2 - 1) * jitter;
581+
point.z += (Math.random() * 2 - 1) * jitter;
582+
583+
// Project back onto the sphere
584+
point.normalize().multiplyScalar(radius);
585+
586+
return point;
587+
}
588+
}
262589
}

0 commit comments

Comments
 (0)