FDNavigate back to the homepage

Crafting a Spatially Expanding Blob in Three.js - Dynamic Geometry and Bounding Box Alignment

Rick
September 3rd, 2024 · 1 min read

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.

Gif of the expanding blob

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';
5
6function ExpandingDecahedron({ expand }) {
7 const meshRef = useRef();
8 const noise = new SimplexNoise();
9 const boxSize = 4;
10 const baseExpansionRate = 0.05; // Base expansion rate
11
12 const [geometry, setGeometry] = useState(() => new THREE.DodecahedronGeometry(1));
13
14 useEffect(() => {
15 if (expand) {
16 const vertices = geometry.attributes.position.array;
17
18 for (let i = 0; i < vertices.length; i += 3) {
19 // Calculate the direction vector from the center to the vertex
20 const vertex = new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);
21 const direction = vertex.clone().normalize(); // Normalize the direction vector
22
23 // Generate a noise-based expansion rate for this vertex
24 const noiseValue = noise.noise(Math.random(), Math.random(), Math.random()) * 1.1;
25 const expansionRate = baseExpansionRate + noiseValue * baseExpansionRate;
26
27 // Expand each vertex outward along the direction vector with added noise
28 vertices[i] += direction.x * expansionRate;
29 vertices[i + 1] += direction.y * expansionRate;
30 vertices[i + 2] += direction.z * expansionRate;
31
32 // Clamp the vertices if they hit the bounding box
33 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 }
43
44 geometry.attributes.position.needsUpdate = true; // Notify Three.js that the geometry has been updated
45 }
46 }, [expand, geometry, noise]);
47
48 return (
49 <mesh ref={meshRef} geometry={geometry}>
50 <meshBasicMaterial attach="material" color="blue" wireframe />
51 </mesh>
52 );
53}
54
55function App() {
56 const [expand, setExpand] = useState(false);
57
58 const handleExpandClick = () => {
59 setExpand((prev) => !prev);
60 };
61
62 return (
63 <>
64 <Canvas>
65 <ambientLight />
66 <pointLight position={[10, 10, 10]} />
67
68 {/* Bounding Box */}
69 <mesh>
70 <boxGeometry args={[4, 4, 4]} />
71 <meshBasicMaterial color="green" wireframe />
72 </mesh>
73
74 {/* Expanding Decahedron */}
75 <ExpandingDecahedron expand={expand} />
76 </Canvas>
77
78 {/* Button to toggle expansion */}
79 <button onClick={handleExpandClick} style={{ position: 'absolute', top: '20px', left: '20px' }}>
80 {expand ? 'Contract' : 'Expand'}
81 </button>
82 </>
83 );
84}
85
86export default App;

The interesting bit is in the useEffect.

1if (expand) {
2 const vertices = geometry.attributes.position.array;
3
4 for (let i = 0; i < vertices.length; i += 3) {
5 // Calculate the direction vector from the center to the vertex
6 const vertex = new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);
7 const direction = vertex.clone().normalize(); // Normalize the direction vector
8
9 // Generate a noise-based expansion rate for this vertex
10 const noiseValue = noise.noise(Math.random(), Math.random(), Math.random()) * 1.1;
11 const expansionRate = baseExpansionRate + noiseValue * baseExpansionRate;
12
13 // Expand each vertex outward along the direction vector with added noise
14 vertices[i] += direction.x * expansionRate;
15 vertices[i + 1] += direction.y * expansionRate;
16 vertices[i + 2] += direction.z * expansionRate;
17
18 // Clamp the vertices if they hit the bounding box
19 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 }
29
30 geometry.attributes.position.needsUpdate = true; // Notify Three.js that the geometry has been updated
31}

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:

Image of vector

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.

More articles from theFrontDev

Visualizing a 3D Sphere with Convex Hull and Wireframe Using R3F.

In our latest research and development project, we explored the creation of a custom 3D convex hull wireframe within a spherical dataset using React Three Fiber (R3F). This novel approach allows for precise visualization of the outermost points in a 3D space, avoiding the typical issues of wireframes intersecting the center. Our method offers new possibilities in rendering complex shapes, making it a valuable tool for innovative 3D data analysis and visualization.

August 31st, 2024 · 1 min read

Bridge Texture and Geometry Worlds by Mapping Patterns and Generating Vertices

Bridging Texture and Geometry Worlds by Mapping Patterns and Generating Vertices" explores a novel method to convert texture patterns into precise vertices, merging the realms of texture and geometry. Through advanced mathematical techniques, vertices are projected onto meshes, allowing for intricate buffer geometry manipulation. This research paves the way for new applications in gaming, animation, and architectural visualization, offering a deeper understanding of texture-geometry interaction and pushing the boundaries of 3D modeling

August 28th, 2024 · 2 min read
© 2021–2024 theFrontDev
Link to $https://twitter.com/TheFrontDevLink to $https://github.com/Richard-ThompsonLink to $https://www.linkedin.com/in/richard-thompson-248ba3111/