This was a little experiment is seeing how an expanding 3D shape would fill a containing or bounding box.
The end result is shown in the GIF below.
The code is here.
1import React, { useRef, useState, useEffect } from 'react';2import { Canvas } from '@react-three/fiber';3import * as THREE from 'three';4import { SimplexNoise } from 'three/examples/jsm/math/SimplexNoise';56function ExpandingDecahedron({ expand }) {7 const meshRef = useRef();8 const noise = new SimplexNoise();9 const boxSize = 4;10 const baseExpansionRate = 0.05; // Base expansion rate1112 const [geometry, setGeometry] = useState(() => new THREE.DodecahedronGeometry(1));1314 useEffect(() => {15 if (expand) {16 const vertices = geometry.attributes.position.array;1718 for (let i = 0; i < vertices.length; i += 3) {19 // Calculate the direction vector from the center to the vertex20 const vertex = new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);21 const direction = vertex.clone().normalize(); // Normalize the direction vector2223 // Generate a noise-based expansion rate for this vertex24 const noiseValue = noise.noise(Math.random(), Math.random(), Math.random()) * 1.1;25 const expansionRate = baseExpansionRate + noiseValue * baseExpansionRate;2627 // Expand each vertex outward along the direction vector with added noise28 vertices[i] += direction.x * expansionRate;29 vertices[i + 1] += direction.y * expansionRate;30 vertices[i + 2] += direction.z * expansionRate;3132 // Clamp the vertices if they hit the bounding box33 if (Math.abs(vertices[i]) > boxSize / 2) {34 vertices[i] = boxSize / 2 * Math.sign(vertices[i]);35 }36 if (Math.abs(vertices[i + 1]) > boxSize / 2) {37 vertices[i + 1] = boxSize / 2 * Math.sign(vertices[i + 1]);38 }39 if (Math.abs(vertices[i + 2]) > boxSize / 2) {40 vertices[i + 2] = boxSize / 2 * Math.sign(vertices[i + 2]);41 }42 }4344 geometry.attributes.position.needsUpdate = true; // Notify Three.js that the geometry has been updated45 }46 }, [expand, geometry, noise]);4748 return (49 <mesh ref={meshRef} geometry={geometry}>50 <meshBasicMaterial attach="material" color="blue" wireframe />51 </mesh>52 );53}5455function App() {56 const [expand, setExpand] = useState(false);5758 const handleExpandClick = () => {59 setExpand((prev) => !prev);60 };6162 return (63 <>64 <Canvas>65 <ambientLight />66 <pointLight position={[10, 10, 10]} />6768 {/* Bounding Box */}69 <mesh>70 <boxGeometry args={[4, 4, 4]} />71 <meshBasicMaterial color="green" wireframe />72 </mesh>7374 {/* Expanding Decahedron */}75 <ExpandingDecahedron expand={expand} />76 </Canvas>7778 {/* Button to toggle expansion */}79 <button onClick={handleExpandClick} style={{ position: 'absolute', top: '20px', left: '20px' }}>80 {expand ? 'Contract' : 'Expand'}81 </button>82 </>83 );84}8586export default App;
The interesting bit is in the useEffect
.
1if (expand) {2 const vertices = geometry.attributes.position.array;34 for (let i = 0; i < vertices.length; i += 3) {5 // Calculate the direction vector from the center to the vertex6 const vertex = new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);7 const direction = vertex.clone().normalize(); // Normalize the direction vector89 // Generate a noise-based expansion rate for this vertex10 const noiseValue = noise.noise(Math.random(), Math.random(), Math.random()) * 1.1;11 const expansionRate = baseExpansionRate + noiseValue * baseExpansionRate;1213 // Expand each vertex outward along the direction vector with added noise14 vertices[i] += direction.x * expansionRate;15 vertices[i + 1] += direction.y * expansionRate;16 vertices[i + 2] += direction.z * expansionRate;1718 // Clamp the vertices if they hit the bounding box19 if (Math.abs(vertices[i]) > boxSize / 2) {20 vertices[i] = boxSize / 2 * Math.sign(vertices[i]);21 }22 if (Math.abs(vertices[i + 1]) > boxSize / 2) {23 vertices[i + 1] = boxSize / 2 * Math.sign(vertices[i + 1]);24 }25 if (Math.abs(vertices[i + 2]) > boxSize / 2) {26 vertices[i + 2] = boxSize / 2 * Math.sign(vertices[i + 2]);27 }28 }2930 geometry.attributes.position.needsUpdate = true; // Notify Three.js that the geometry has been updated31}
The direction bit is key:
1const vertex = new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);2const direction = vertex.clone().normalize(); // Normalize the direction vector
We can do the direction like this as we are doing it from the origin which is at the center of the expanding blob.
Consider the following:
The vector is a map to the origin of the scene. So when we have a direction we are saying from the origin how do we get to this vector?
And then to make it a direction we eliminate the magnitude of the vector by normalizing the vector.