🎉 Welcome to Axiome Blog, a blog about creative development and vulgarization of 3D theory and more.
Articles
Exploded view of a 3D model using React-Three-Fiber

Exploded view of a 3D model using React-Three-Fiber

Ever marveled at the intricate details of a 3D model and wished to present it in a captivating way? Let me guide you through crafting an exploded view of a 3D engine model, utilizing straightforward mathematical principles. 💣 This technique not only adds visual depth to your presentation but also highlights the complexity and craftsmanship of your model.

For this endeavor, we'll leverage React-Three-Fiber to simplify our development process. However, the concepts we'll discuss are fully applicable within the native three.js environment, ensuring you can adapt this method regardless of your preferred framework.

The exploded view technique is an excellent way to dissect and display each part of your 3D model, making it particularly useful for educational content, product showcases, or even artistic presentations. By spacing out the components, viewers can appreciate the individual parts that make up the whole, offering a unique perspective on the engineering and design intricacies of your model.

Disclaimer: While this guide aims to demystify the process of creating an exploded view with React-Three-Fiber and three.js, my journey with these technologies is an ongoing adventure. I welcome your feedback and questions. Should you spot any areas of improvement, typos, or wish to share your thoughts, reaching out on Twitter (opens in a new tab) would be much appreciated. Your engagement and insights contribute greatly to the collective learning experience.

Thank you for embarking on this creative journey with me. Together, let's unlock new dimensions of 3D visualization. ✨

Prepare the model

First, open your favorite modeling tool to split your model (if needed) in several parts.

For instance, I am using this cool asset (opens in a new tab), which needs a bit of work before being able to use it.

  1. Select the model 1.png
  2. Go to Edit mode 2.png
  3. Select a triangle that is part of the element you want to separate. 3.png
  4. Press Ctrl + L, that will select all the linked triangles. (Ensure that it selects the part you want or select all the triangles manually) 4.png
  5. Press F3 which enable the Quick Actions panel to show up, and begin to write Separate. Select the first option. 5.png
  6. Congratulations, you have now two separate meshes. Be sure to rename it in an understandable manner. Repeat the process ad nauseam, until you get all of the separate parts.
  7. Back in Object mode (press TAB), select all the elements (press A), press F3 and type center and select the option Origin to Center of Mass (Volume). It will basically set the center of each mesh to its center of mass, (some average point of the matter).
  8. Export to GLB and you are good to go. 8.png 9.png

Exploded View Effect in React-Three-Fiber

Here is what becomes to be interesting. Let's see with my incredible drawings, how we can achieve somewhat of an automatic exploded view effect. First the theory, then the implementation.

(Boring?) Theory 📐

Here is the drill in 2D. 10.png The most naive way of thinking is to take the world center (0,0,0) as the reference of our exploded view. For the drill, it will not be that pretty considering the shape. We want the epicenter to be higher so the rotating part that are on the same rotating axis, are moving forward. But anyway it is the same principle whatever is your epicenter point.

  1. You want to find the direction of the displacement. "What gives us a direction?", you will ask. A vector. (I prefer to explain from scratch, so everyone can eat at the table). 11.png The formula is quite simple: vector BA = point A - point B

So in our context, we will say displacement vector = center of mass of the piece - epicenter We can normalize this vector so it is unit vector (it means that the length is 1). It will be easier for us that way to control the strength of displacement along side each vectors.

  1. We need a criteria to displace the part of our drill A simple criteria to take is the distance between the part's center and the epicenter of the explosion. The more you are far from the epicenter, the more we expect you to go away in our exploded view. Let's call this the magnitude of the vector we calculated in the previous step.

On the drill, like I said previously, we will take an higher (in Y axis) point for the epicenter, so the rotating element are kept along the same axis.

12.png 13.png

Here you go, let's implement this.

The cool part: Implementation 💥

1. Load the model

Load the model using useGLTF from the library drei (opens in a new tab) (or use the GLTFLoader if you are using three.js native)

Drill.tsx

_10
export const Drill = () => {
_10
const gltf = useGLTF("drill-corrected.glb");
_10
return (
_10
<>
_10
<primitive object={gltf.scene}></primitive>
_10
</>
_10
);
_10
};
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10

2. Displacement calculation

Traverse the scene to calculate each displacement. (You can discard the main object if you want to freeze it like me).

  • object.position.distanceTo(explosionCenter) is to calculate the distance from the epicenter as I explained it previously. The explosion factor is a way to control uniformly the expansion of each part. You can tweak it as you want.

  • const vector = object.position.clone().sub(explosionCenter).normalize(); is the direction we need to calculate to expand around the epicenter. We normalize it as you remember, to have a unit vector per part (length of 1), that way we can control uniformly through the explosion factor, the magnitude.

I store all of this in a Map, that will act as a dictionnary for each part. I want to use these displacement value later (with GSAP animation library for instance) to animate the explosion.

Drill.tsx

_30
export const Drill = () => {
_30
const gltf = useGLTF("drill-corrected.glb");
_30
_30
const mainObject = gltf.scene.getObjectByName("Main_") as Mesh;
_30
const targetMap: Map<string, Vector3> = new Map<string, Vector3>();
_30
const explosionCenter = new Vector3(0, 0.15, 0); // Higher epicenter for the drill
_30
const explosionFactor = 0.8; // Move uniformly along the direction for every part.
_30
_30
useEffect(() => {
_30
gltf.scene.traverse((object) => {
_30
if (object.uuid !== mainObject.uuid) { // Discard the main object so it is frozen.
_30
const vector = object.position.clone().sub(explosionCenter).normalize();
_30
const displacement = object.position
_30
.clone()
_30
.add(
_30
vector.multiplyScalar(
_30
object.position.distanceTo(explosionCenter) * explosionFactor
_30
)
_30
);
_30
targetMap.set(object.name, displacement); // Store it in a dictionnary for later use.
_30
}
_30
});
_30
}, []);
_30
_30
return (
_30
<>
_30
<primitive object={gltf.scene}></primitive>
_30
</>
_30
);
_30
};

3. Let's animate a bit

We are going to use GSAP but you can use your favorite animation library.

Through gsap.timeline().to(), we animate the position of the current object of the loop, using its displacement.

Drill.tsx

_64
export const Drill = () => {
_64
const gltf = useGLTF("drill-corrected.glb");
_64
_64
const mainObject = gltf.scene.getObjectByName("Main_") as Mesh;
_64
const targetMap: Map<string, Vector3> = new Map<string, Vector3>();
_64
const explosionCenter = new Vector3(0, 0.15, 0); // Higher epicenter for the drill
_64
const explosionFactor = 0.8; // Move uniformly along the direction for every part.
_64
_64
useEffect(() => {
_64
gltf.scene.traverse((object) => {
_64
if (object.uuid !== mainObject.uuid) { // Discard the main object so it is frozen.
_64
const vector = object.position.clone().sub(explosionCenter).normalize();
_64
const displacement = object.position
_64
.clone()
_64
.add(
_64
vector.multiplyScalar(
_64
object.position.distanceTo(explosionCenter) * explosionFactor
_64
)
_64
);
_64
targetMap.set(object.name, displacement); // Store it in a dictionnary for later use.
_64
}
_64
});
_64
}, []);
_64
_64
const [trigger, setTrigger] = useState(false);
_64
useEffect(() => {
_64
gltf.scene.traverse((object) => {
_64
if (object.uuid !== mainObject.uuid) {
_64
const vector = object.position.clone().sub(explosionCenter).normalize();
_64
const displacement = object.position

1. Load the model

Load the model using useGLTF from the library drei (opens in a new tab) (or use the GLTFLoader if you are using three.js native)

2. Displacement calculation

Traverse the scene to calculate each displacement. (You can discard the main object if you want to freeze it like me).

  • object.position.distanceTo(explosionCenter) is to calculate the distance from the epicenter as I explained it previously. The explosion factor is a way to control uniformly the expansion of each part. You can tweak it as you want.

  • const vector = object.position.clone().sub(explosionCenter).normalize(); is the direction we need to calculate to expand around the epicenter. We normalize it as you remember, to have a unit vector per part (length of 1), that way we can control uniformly through the explosion factor, the magnitude.

I store all of this in a Map, that will act as a dictionnary for each part. I want to use these displacement value later (with GSAP animation library for instance) to animate the explosion.

3. Let's animate a bit

We are going to use GSAP but you can use your favorite animation library.

Through gsap.timeline().to(), we animate the position of the current object of the loop, using its displacement.

Drill.tsx

_10
export const Drill = () => {
_10
const gltf = useGLTF("drill-corrected.glb");
_10
return (
_10
<>
_10
<primitive object={gltf.scene}></primitive>
_10
</>
_10
);
_10
};
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10
_10

Scroll based animation

Here is a little bonus gift for you that stayed until the end. By harnessing the power of scroll-based animation, you can trigger the expansion or transformation of objects and scenes right within the viewer's browser.

Imagine diving into a web page where each scroll unveils a part of the story you're telling. Whether it's a three.js-powered 3D model gradually assembling as your audience scrolls down, or a dynamic scene unfolding piece by piece, the ScrollTrigger plugin enables us to create web experiences that are not only visually engaging but also deeply immersive.

Here's the scoop: using ScrollTrigger isn't just about adding flair to your site; it's about redefining how users interact with content. By integrating this technique, you encourage visitors to explore your content more thoroughly, enhancing user engagement and providing a memorable journey through your digital landscape.

Whether you're showcasing a complex three.js scene, unveiling a new product piece by piece, or telling a compelling story through scrolling, the scrub property ensures a smooth, controlled animation that keeps users glued to their screens. This approach doesn't just display your content; it transforms your web page into an interactive adventure.

Ready ? Let's dig in:

Setting the scrub property to true, is binding the animation directly to the scroll. Using a value, it is adding some inertia to the animation, that I think is prettier. Based on the same animation than the previous section you just need to add the ScrollTrigger plugin with gsap.registerPlugin(ScrollTrigger); Then change the animation to:

Drill.tsx

_54
export const Drill = () => {
_54
const gltf = useGLTF("drill-corrected.glb");
_54
_54
const mainObject = gltf.scene.getObjectByName("Main_") as Mesh;
_54
const targetMap: Map<string, Vector3> = new Map<string, Vector3>();
_54
const explosionCenter = new Vector3(0, 0.15, 0); // Higher epicenter for the drill
_54
const explosionFactor = 0.8; // Move uniformly along the direction for every part.
_54
_54
useEffect(() => {
_54
gltf.scene.traverse((object) => {
_54
if (object.uuid !== mainObject.uuid) { // Discard the main object so it is frozen.
_54
const vector = object.position.clone().sub(explosionCenter).normalize();
_54
const displacement = object.position
_54
.clone()
_54
.add(
_54
vector.multiplyScalar(
_54
object.position.distanceTo(explosionCenter) * explosionFactor
_54
)
_54
);
_54
targetMap.set(object.name, displacement); // Store it in a dictionnary for later use.
_54
}
_54
});
_54
}, []);
_54
_54
let scrollTriggerParams = {
_54
toggleActions: "play none none reverse",
_54
scrub: 1.5,
_54
};
_54
_54
useEffect(() => {
_54
gltf.scene.traverse((object) => {
_54
if (object.uuid !== mainObject.uuid) {
_54
const displacement = targetMap.get(object.name) as Vector3;
_54
gsap
_54
.timeline({
_54
scrollTrigger: {
_54
...scrollTriggerParams,
_54
},
_54
})
_54
.to(object.position, {
_54
x: displacement.x,
_54
y: displacement.y,
_54
z: displacement.z,
_54
});
_54
}
_54
});
_54
}, []);
_54
_54
return (
_54
<>
_54
<primitive object={gltf.scene}></primitive>
_54
</>
_54
);
_54
};

Give some room for your page to scroll, set the canvas to position: fixed in CSS, add some placeholder sections that are like 100vh height.

Then when scrolling, you will see the model expanding.

Conclusion

As we wrap up this guide on creating an exploded view of a 3D model with React-Three-Fiber and three.js, I hope you've gained valuable insights and feel motivated to explore further. Visualizing the complex parts of 3D models in this way can significantly enhance your presentations, making them both more interactive and insightful.

Consider this tutorial a first step towards unlocking the potential that React-Three-Fiber and three.js hold for 3D modeling and web-based visualizations. My own fascination with the detailed exploded views of machinery, and how every piece comes together, led me here. It's exciting to think that we can now bring these concepts into the digital age, making them accessible and engaging through the power of modern web technologies.

React-Three-Fiber and three.js not only help us pay respect to the art of mechanical diagrams but also introduces new possibilities for creativity and design in the 3D space. It's about merging technical skill with creative vision to build models that are not just visually appealing but also interactive and informative. This is how we make our digital content not just seen, but experienced.

The evolution of 3D on the web is creating new opportunities for interactive experiences, transforming how we interact with content online.

I'm eager to hear your thoughts and any suggestions you might have. Your feedback is crucial for making guides like this better and more useful for everyone. If you've found new ways to apply these techniques or have questions on how to refine them, don't hesitate to share.

Thanks for joining me in this exploration. Together, let's explore the limitless possibilities of 3D web development.

Wishing you a great day and happy modeling!