<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tim Vučina</title>
    <description>The latest articles on Forem by Tim Vučina (@vucinatim).</description>
    <link>https://forem.com/vucinatim</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1520071%2F170b3b84-c95a-4a6f-a299-f6df32df757d.png</url>
      <title>Forem: Tim Vučina</title>
      <link>https://forem.com/vucinatim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vucinatim"/>
    <language>en</language>
    <item>
      <title>A Developer's Brush Stroke</title>
      <dc:creator>Tim Vučina</dc:creator>
      <pubDate>Mon, 09 Jun 2025 20:29:02 +0000</pubDate>
      <link>https://forem.com/vucinatim/a-developers-brush-stroke-nl7</link>
      <guid>https://forem.com/vucinatim/a-developers-brush-stroke-nl7</guid>
      <description>&lt;p&gt;Yo.&lt;/p&gt;

&lt;p&gt;Is it just me, or does it sometimes feel like we're artists... hired to paint by numbers?&lt;br&gt;
You sit down with a blank canvas, full of ideas, ready to create something meaningful, and then someone hands you a color-coded template and says, "Just stay inside the lines. Oh, and the sky has to be green because marketing thinks it'll stand out more." &lt;br&gt;
(I know It's not just me btw)&lt;/p&gt;

&lt;p&gt;At first, I thought software development was going to be like architecture with a heartbeat, something structured, sure, but still alive. Something you could feel proud of. And sometimes, it is. But too often, it becomes a process of carrying out instructions from people who don't understand the medium, or worse, don't care to.&lt;/p&gt;

&lt;p&gt;I'm not trying to sound bitter. I just want to talk about what it feels like to build things when you care, and what happens when the space to care slowly disappears.&lt;br&gt;
Not just for me. But for a lot of people I know who got into this field to make something real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Artist in a Factory
&lt;/h2&gt;

&lt;p&gt;There was a time when painters didn't really get to paint what they wanted. They worked under wealthy patrons (kings, popes, merchants), people who paid well, but also had very specific ideas about how their nose should be shaped in the portrait. You weren't hired to express yourself; you were hired to flatter the client and follow instructions, ideally without too much talking back.&lt;/p&gt;

&lt;p&gt;Sometimes, working in tech today feels like that.&lt;/p&gt;

&lt;p&gt;You're brought on for your skill, your ability to translate ideas into working systems, but more and more, it feels like the "ideas" part has already been done for you. The blueprint is drawn, the color palette decided, and your job is to fill in the pixels. Quickly. Quietly. Consistently.&lt;/p&gt;

&lt;p&gt;We like to say that building software is creative work, and on a good day, it really is. But a lot of the time, it feels more like assembling furniture with someone else's instructions, except the instructions are vague, incomplete, and being rewritten mid-build by someone who's never actually put furniture together.&lt;/p&gt;

&lt;p&gt;It's not that we don't want to collaborate. We do. But there's a difference between working with someone and working for someone in the "just do what I say" sense. Most developers I know didn't get into this to be efficient hands, they got into it to build things that matter. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of the Founder (and the Power of Care)
&lt;/h2&gt;

&lt;p&gt;If you're trying to build a good product, there's one thing that matters more than any framework, process, or project management tool: whether the people building it actually care.&lt;/p&gt;

&lt;p&gt;And here's the strange part: most of them want to. Most developers I know want to feel proud of what they're building. They want to get in flow, to push something forward, to make it good. But that only happens when they're given the room to care in the first place.&lt;/p&gt;

&lt;p&gt;That's where leadership comes in.&lt;br&gt;
I'm not talking about Gantt charts or sprint velocity, I'm talking about something simpler: creating the conditions for care. If you're a founder, a lead, a client, that's basically your main job. Not to micromanage every step, not to control every outcome, but to build an environment where people want to do their best work.&lt;/p&gt;

&lt;p&gt;And if you don't trust them to do that…&lt;br&gt;
well, maybe the honest move is to learn to code and build it yourself.&lt;/p&gt;

&lt;p&gt;Because otherwise, what are we doing here? Hiring talented people just to override them at every turn feels less like leadership and more like ego with a budget. &lt;/p&gt;

&lt;h2&gt;
  
  
  Chasing the Right Kind of Win
&lt;/h2&gt;

&lt;p&gt;There's this idea floating around that the whole point of building something (especially a product) is to win. To scale, to raise, to exit. To hit the charts, the KPIs, the milestone deck your investors clap for.&lt;/p&gt;

&lt;p&gt;And sure, I get it. Success matters. Money keeps the lights on. But when that becomes the main thing, the actual reason you're doing this, it starts to feel like you're playing a different game entirely.&lt;/p&gt;

&lt;p&gt;Some people build like gamblers.&lt;br&gt;
They watch the market, make their bets, optimize for attention, and hope the numbers line up. And sometimes, they win. Fast. Big. Loud.&lt;/p&gt;

&lt;p&gt;But I don't want to play that game.&lt;/p&gt;

&lt;p&gt;For me, it's more like cooking for someone you love.&lt;br&gt;
Not a plate you're trying to sell to a million people. Just one you care about. You pick the ingredients. You burn a few. You taste as you go. And when someone takes a bite and says, "Damn, this is exactly what I needed"—that hits different. That's the kind of win that lingers.&lt;/p&gt;

&lt;p&gt;It's not about ignoring results. It's about asking which results matter.&lt;/p&gt;

&lt;p&gt;Chasing money might get you a quick win.&lt;br&gt;
Chasing meaning builds things that actually last. &lt;/p&gt;

&lt;h2&gt;
  
  
  On Passion (Without Saying 'Passion')
&lt;/h2&gt;

&lt;p&gt;The word passion gets tossed around a lot. So much that it barely means anything anymore. It shows up in job postings and startup manifestos like parsley on a plate—decorative, expected, mostly ignored.&lt;/p&gt;

&lt;p&gt;But when you've actually felt it, you know it's not fluff. It's fuel.&lt;/p&gt;

&lt;p&gt;When I care about what I'm building (not just the outcome, but the process), I don't work harder because I'm told to. I work harder because I want to. I think clearer. I solve faster. I obsess in a good way. I come up with ideas in the shower, on walks, while brushing my teeth. It's not a grind, it's a hum in the back of your mind that says, this matters.&lt;/p&gt;

&lt;p&gt;And I know not everyone gets that feeling every day. Life's messy, work is work. But when you do get it (even once) you remember why you got into this in the first place.&lt;/p&gt;

&lt;p&gt;It's not about being "motivated." It's about being connected. About building something you're not just getting paid for, but actually believe in (even a little). That belief is worth more than ten checklists and a roadmap. Because that's where the real velocity comes from, not pressure, not deadlines, care. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Myth of Roles and the Power of Human Wholeness
&lt;/h2&gt;

&lt;p&gt;Sometimes people ask, "So what's your role on the project?"&lt;br&gt;
And honestly, I don't want one.&lt;/p&gt;

&lt;p&gt;Not because I'm trying to be difficult, or special, or some "multidisciplinary visionary" or whatever. I just think roles are often a polite way of saying "stay in your lane." And I've never met a great product that was built by people staying in their lanes.&lt;/p&gt;

&lt;p&gt;I don't want to just write code.&lt;br&gt;
I want to shape the idea, question the assumptions, care about the details, collaborate on the design, debug the emotion in a moment, not just the logic. And I think most people (if you let them) want that too.&lt;/p&gt;

&lt;p&gt;We're not machines with clean-cut inputs and outputs. We're messy, curious, intuitive people. And the best products I've ever seen didn't come from a process, they came from people who were allowed to show up fully. People whose personal instincts, weird quirks, random late-night thoughts, and gut feelings made it in.&lt;/p&gt;

&lt;p&gt;That's where richness comes from.&lt;br&gt;
Not from role clarity, but from human overlap. From the places where our perspectives collide and blend and sharpen each other. That's where the soul of a product starts to form.&lt;/p&gt;

&lt;p&gt;I don't want a title.&lt;br&gt;
I want to be part of something that feels like it matters. &lt;/p&gt;

&lt;h2&gt;
  
  
  On Chaos (and Why I Don't Fear It)
&lt;/h2&gt;

&lt;p&gt;One of the most common pushbacks to everything I've said is some version of:&lt;br&gt;
"But you can't just let people run around doing whatever they want. It'll be chaos."&lt;/p&gt;

&lt;p&gt;And yeah, it might.&lt;br&gt;
But not the kind of chaos people are afraid of.&lt;/p&gt;

&lt;p&gt;When people say "chaos," they usually mean lack of control. Disorganization. Delays. Waste. But I don't think that's real chaos. I think real chaos is when you have a team of people who've stopped caring, who are just following instructions, staying in their lanes, hitting the metrics, and checking out mentally the moment their part is done. It might look neat on the outside. But on the inside? It's dead.&lt;/p&gt;

&lt;p&gt;The good kind of chaos (the human kind) comes from people actually bringing their full selves to the table. It's a little noisy. It's full of ideas, experiments, wrong turns, and weird brilliance. It's harder to manage on paper. But it's how great things happen.&lt;/p&gt;

&lt;p&gt;And the best way to navigate that chaos isn't with stricter rules or more oversight.&lt;br&gt;
It's with trust. With shared goals. With a team that actually likes each other.&lt;br&gt;
When you're building something alongside people you respect and care about, you don't need to be watched. You want it to work. You self-correct. You collaborate. You clean up after yourselves, not because someone told you to, but because it matters to you.&lt;/p&gt;

&lt;p&gt;That kind of harmony doesn't come from modern management frameworks. It comes from letting people be people, and trusting that if the goal is clear and the culture is right, they'll do more than you ever could by forcing it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Closing – A Quiet Note
&lt;/h2&gt;

&lt;p&gt;I don't have a grand solution. I'm not claiming to know the one right way to build software, or teams, or companies. I just know what it feels like to care deeply about the work—and how rare it is to be in an environment that actually allows for that.&lt;/p&gt;

&lt;p&gt;Most of the time, it's the opposite.&lt;br&gt;
You care, and the system shrugs. You try, and the process trims your edges. You want to make something real, but the layers between you and the thing keep getting thicker.&lt;/p&gt;

&lt;p&gt;And still, I don't think it has to be this way.&lt;/p&gt;

&lt;p&gt;I've seen moments (rare, but unmistakable) when it works.&lt;br&gt;
When a group of people trust each other enough to be honest, to push each other, to share the weight and the weirdness of building something from nothing.&lt;br&gt;
When care isn't micromanaged or incentivized, it's just there, because the people are.&lt;/p&gt;

&lt;p&gt;And that's all I'm really saying.&lt;br&gt;
That maybe we don't need more control. Maybe we need more belief, in the work, in the people doing it, and in the strange, unpredictable, beautiful things that can happen when you stop painting by numbers... and finally let the brush breathe.&lt;/p&gt;

&lt;p&gt;Just maybe. ❤️&lt;/p&gt;

</description>
      <category>creativity</category>
      <category>softwaredevelopment</category>
      <category>devlive</category>
      <category>reflection</category>
    </item>
    <item>
      <title>🧩 Beyond the Popup: Crafting Next-Level Chrome Extensions with CRXJS</title>
      <dc:creator>Tim Vučina</dc:creator>
      <pubDate>Wed, 19 Feb 2025 10:21:50 +0000</pubDate>
      <link>https://forem.com/zerodays/beyond-the-popup-crafting-next-level-chrome-extensions-with-crxjs-3elg</link>
      <guid>https://forem.com/zerodays/beyond-the-popup-crafting-next-level-chrome-extensions-with-crxjs-3elg</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;1. The Magic of Chrome Extensions&lt;/strong&gt; ✨
&lt;/h2&gt;

&lt;p&gt;Chrome extensions are more than just handy little tools - they’re like superpowers for your browser. From automating workflows to enhancing user experience, they can transform the way people interact with the web. But the real beauty? Extensions can seamlessly integrate into websites, modify content on the fly, and even leverage AI for mind-blowing results (like &lt;strong&gt;&lt;a href="https://www.classmate.study/" rel="noopener noreferrer"&gt;Classmate&lt;/a&gt;&lt;/strong&gt; does).  &lt;/p&gt;

&lt;p&gt;Instead of a dull definition, let’s highlight what makes extensions exciting:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They live inside your browser&lt;/strong&gt; – meaning instant access and control over webpages.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They hook into powerful APIs&lt;/strong&gt; – like the Chrome Storage API, Context Menus, Side Panels, and even WebRTC.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They enable automation, AI, and interaction enhancements&lt;/strong&gt; – basically, they let you mold the web to your will.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Choosing Your Stack: The Right Tools for the Job&lt;/strong&gt; 🛠️
&lt;/h2&gt;

&lt;p&gt;Unlike traditional web apps, building a Chrome extension usually requires a carefull balancing of performance, compatibility, and maintainability. There are multiple ways to approach extension development, from vanilla JavaScript to full-blown frameworks.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Popular Choices for Extension Development:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plain JavaScript&lt;/strong&gt; – Lightweight but messy for scaling.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React (or Preact)&lt;/strong&gt; – Great for UI-heavy extensions but requires careful bundling.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; – A must-have for maintainability.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite + ESBuild&lt;/strong&gt; – Super fast and developer-friendly for modern extensions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TailwindCSS&lt;/strong&gt; – Ideal for crafting polished UIs quickly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase/Firebase&lt;/strong&gt; – Cloud storage, authentication, and real-time syncing made easy.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;Classmate&lt;/strong&gt;, we went with &lt;strong&gt;React, TypeScript, Vite, Tailwind, and Supabase&lt;/strong&gt;, ensuring a modern, scalable architecture with minimal bloat.  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. The Unique Challenges &amp;amp; Superpowers of Extension Development&lt;/strong&gt; 🔥
&lt;/h2&gt;

&lt;p&gt;Chrome extensions offer &lt;strong&gt;incredible browser APIs&lt;/strong&gt; but also introduce quirks and gotchas that developers need to navigate.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Good: Browser APIs That Supercharge Your Extension&lt;/strong&gt; 🚀
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;chrome.tabs&lt;/strong&gt; – Interact with and modify webpages dynamically.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.scripting&lt;/strong&gt; – Inject JavaScript and CSS into any page.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.contextMenus&lt;/strong&gt; – Create custom right-click options for quick actions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.storage&lt;/strong&gt; – Persist user settings without a backend.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chrome.identity&lt;/strong&gt; – Handle authentication seamlessly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Bad: Common Pitfalls &amp;amp; Limitations&lt;/strong&gt; 😬
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manifest V2 → V3 transition&lt;/strong&gt; – Service workers replace background scripts, breaking some old workflows.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restricted access&lt;/strong&gt; – Some APIs are off-limits (e.g., network requests need special permissions).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No direct database storage&lt;/strong&gt; – Requires external storage like Firebase or Supabase.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance concerns&lt;/strong&gt; – Extensions should be lightweight to avoid slowing down the browser.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Ugly: Bad Practices That Can Doom Your Extension&lt;/strong&gt; ❌
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over-permissioning&lt;/strong&gt; – Requesting too many permissions leads to rejection by the Chrome Web Store.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not optimizing storage&lt;/strong&gt; – Extensions with large local storage can slow down performance.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocking the main thread&lt;/strong&gt; – Keep content scripts efficient to avoid laggy pages.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Bridging the Gap: Messaging Between Popup &amp;amp; Content Scripts&lt;/strong&gt; 🔄
&lt;/h3&gt;

&lt;p&gt;One of the biggest challenges in Chrome extension development is getting different parts of the extension – popup, background scripts, and content scripts – to talk to each other efficiently. Unlike a traditional web app, where everything runs in a single execution context, Chrome extensions are split into &lt;strong&gt;isolated environments&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;This means:&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;The popup doesn’t directly control the webpage&lt;/strong&gt; – it must send messages.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;The content script can interact with the page but lacks access to extension APIs&lt;/strong&gt; – it needs to communicate with the background.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;The background service worker acts as a central hub&lt;/strong&gt;, receiving messages and dispatching commands.  &lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;How We Handle Messaging in Classmate&lt;/strong&gt; ⚡
&lt;/h4&gt;

&lt;p&gt;To keep our messaging system &lt;strong&gt;structured and scalable&lt;/strong&gt;, we use &lt;strong&gt;typed messages&lt;/strong&gt; with TypeScript, ensuring every communication has a clear format.  &lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Message Structure &amp;amp; Commands&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We define a set of commands, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;START_SCREENSHOT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ASK_AI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TRIGGER_AUTOSOLVE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OPEN_SIDEBAR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each message follows a strict format, using interfaces like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AskAI&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ASK_AI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;autoSolveData&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;AutoSolveQuestion&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures every part of our extension knows &lt;strong&gt;exactly&lt;/strong&gt; what kind of messages to expect, reducing errors.  &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Sending Messages to the Active Tab&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We use a helper function to &lt;strong&gt;find and message the active tab&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendMessageToActiveTab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getActiveTab&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No active tab found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendMessageToTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes sure we’re always targeting the &lt;strong&gt;currently open webpage&lt;/strong&gt;, whether it’s to &lt;strong&gt;trigger AI processing&lt;/strong&gt; or &lt;strong&gt;capture screenshots&lt;/strong&gt;.  &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Listening for Messages&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We use React hooks to set up &lt;strong&gt;message listeners&lt;/strong&gt; inside components. This makes handling commands like &lt;code&gt;"START_SCREENSHOT"&lt;/code&gt; &lt;strong&gt;declarative and efficient&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useMessageListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;START_SCREENSHOT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;area-picker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, the UI updates reactively whenever the popup or background script sends a command.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Result? A Seamless Experience&lt;/strong&gt; 🎯
&lt;/h3&gt;

&lt;p&gt;With &lt;strong&gt;typed messages, structured commands, and well-defined listeners&lt;/strong&gt;, our extension avoids race conditions, unexpected behavior, and debugging nightmares. Instead, &lt;strong&gt;every part of the extension stays in sync&lt;/strong&gt;, making interactions smooth and &lt;strong&gt;instantaneous&lt;/strong&gt;. 🚀  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. CRXJS: The Modern Way to Build Chrome Extensions&lt;/strong&gt; ⚡
&lt;/h2&gt;

&lt;p&gt;If you’re building Chrome extensions with modern web frameworks, CRXJS is a game-changer. It simplifies the development process by integrating seamlessly with Vite, allowing you to use React (or other frameworks) with minimal hassle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why CRXJS?
&lt;/h3&gt;

&lt;p&gt;Traditional Chrome extension development often involves complex configurations, dealing with manifest quirks, and managing different extension environments (background, content scripts, popup). CRXJS abstracts much of this complexity while staying lightweight and performant.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Zero-config setup&lt;/strong&gt; – Works out of the box with Vite.&lt;br&gt;
✅ &lt;strong&gt;Automatic bundling&lt;/strong&gt; – No need to manually manage content scripts and background scripts.&lt;br&gt;
✅ &lt;strong&gt;HMR (Hot Module Replacement) for popups &amp;amp; options pages&lt;/strong&gt; – Develop faster without needing to reload the extension manually.&lt;br&gt;
✅ &lt;strong&gt;TypeScript &amp;amp; React support&lt;/strong&gt; – Makes modern development workflows much easier.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up CRXJS in Your Project 🚀
&lt;/h3&gt;

&lt;p&gt;To get started, install CRXJS Vite plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;npm install @crxjs/vite-plugin -D
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, configure it in &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;crx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@crxjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;crx&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup ensures that your extension files are properly processed, including background scripts, content scripts, and popup UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Efficiency and Simplicity 🏆
&lt;/h3&gt;

&lt;p&gt;With CRXJS, we eliminated the boilerplate, improved the development speed with HMR, and kept our extension lean and maintainable. It’s the perfect tool for modern Chrome extension development, allowing us to focus on building features instead of wrestling with configuration files.&lt;/p&gt;

&lt;p&gt;If you're working with React, Vite, or TypeScript, CRXJS is a no-brainer! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;6. Our Journey: How We Built Classmate&lt;/strong&gt; 🏆
&lt;/h2&gt;

&lt;p&gt;When developing &lt;strong&gt;Classmate&lt;/strong&gt; - AI-powered quiz assistant, we faced all the classic extension challenges:&lt;br&gt;&lt;br&gt;
✅ Ensuring a seamless user experience with &lt;strong&gt;React + Vite&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ Handling authentication with &lt;strong&gt;Supabase&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ Managing API calls efficiently with &lt;strong&gt;Zodios&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✅ Avoiding Chrome Web Store rejection by keeping &lt;strong&gt;permissions minimal&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Through smart architecture choices and the right stack, we turned a simple idea into a powerful AI assistant for online learners. 🚀  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt; 💡&lt;br&gt;&lt;br&gt;
Chrome extension development is an exciting playground where you can experiment with browser APIs, automation, and AI. Whether you’re building productivity tools, AI-driven helpers, or something entirely unique, there’s no limit to what you can create.  &lt;/p&gt;

&lt;p&gt;Got an idea? Time to &lt;strong&gt;ship it!&lt;/strong&gt; 🚢💻  &lt;/p&gt;

</description>
      <category>chromeextension</category>
      <category>extensions</category>
      <category>crxjs</category>
      <category>aiextension</category>
    </item>
    <item>
      <title>🚀 Building an Interactive 3D Rocket Easter Egg with React Three Fiber</title>
      <dc:creator>Tim Vučina</dc:creator>
      <pubDate>Tue, 17 Sep 2024 09:16:23 +0000</pubDate>
      <link>https://forem.com/zerodays/building-an-interactive-3d-rocket-easter-egg-with-react-three-fiber-4pc</link>
      <guid>https://forem.com/zerodays/building-an-interactive-3d-rocket-easter-egg-with-react-three-fiber-4pc</guid>
      <description>&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMGkwcjhnejhkbmN4Y3AxdTNueXNoNHhiZ2IzZDJoZHZxYzV0bndjaCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/pVgDqx73n1ECZL5O4X/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMGkwcjhnejhkbmN4Y3AxdTNueXNoNHhiZ2IzZDJoZHZxYzV0bndjaCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/pVgDqx73n1ECZL5O4X/giphy.gif" width="480" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we hit 1000 followers on LinkedIn, we knew we had to celebrate in style! Inspired by Vercel's awesome post on &lt;a href="https://vercel.com/blog/building-an-interactive-3d-event-badge-with-react-three-fiber" rel="noopener noreferrer"&gt;Building an Interactive 3D Event Badge with React Three Fiber&lt;/a&gt;, we wanted to take that vibe and launch something fun of our own. So, what better way to celebrate than with a 3D rocket Easter egg on our shiny new website, &lt;strong&gt;&lt;a href="https://zerodays.dev" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;It’s one of those things that’s just &lt;strong&gt;fun to build&lt;/strong&gt;, like the digital equivalent of a fidget spinner, but way cooler. We had a blast working on it, and today we’re going to take you behind the scenes.&lt;/p&gt;

&lt;p&gt;Let's get straight into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Table of Contents:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;🏁 The Setup&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🛠️ Tools to Build Our 3D Rocket&lt;/li&gt;
&lt;li&gt;🎬 Setting Up the 3D Scene&lt;/li&gt;
&lt;li&gt;🎥 Camera Choice: Why We Went with Orthographic&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Handling Events with &lt;code&gt;eventSource&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🚀 The Main Actor: Our Interactive Rocket&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Features&lt;/li&gt;
&lt;li&gt;Rocket Following Movement: Keeping Track of the Mouse&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Handling Events on &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🚀 Summary - Rocket&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;💥 Particle System: Fueling the Rocket’s Exhaust&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Concepts&lt;/li&gt;
&lt;li&gt;💥 Summary - Particles&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🪨 SpaceRocks: Asteroids with Physics and Splitting&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Features&lt;/li&gt;
&lt;li&gt;🪨 Summary - SpaceRocks&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🌌 SpaceEnvironment: Immersive Space Backdrop&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key Features&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Using &lt;code&gt;window.devicePixelRatio&lt;/code&gt; for Consistent Shader Rendering&lt;/li&gt;
&lt;li&gt;🌌 Summary - SpaceEnvironment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🏎️ Performance Boosts: Optimizing for Smooth Rocket Experience&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 1. Frame Loop Control: "Always" vs. "Demand"&lt;/li&gt;
&lt;li&gt;🚀 2. Custom &lt;code&gt;useViewportSize&lt;/code&gt; Hook&lt;/li&gt;
&lt;li&gt;🚀 3. Mesh Instancing for Planets&lt;/li&gt;
&lt;li&gt;🚀 4. Using Refs for Non-Rerender Values&lt;/li&gt;
&lt;li&gt;🚀 5. General React-Three-Fiber Performance Tips&lt;/li&gt;
&lt;li&gt;💡 Quick Tip: Canvas Size Limits on Some Devices&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚀 Wrapping it Up: The Final Countdown&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🏁 The Setup:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🛠️ Tools to Build Our 3D Rocket
&lt;/h3&gt;

&lt;p&gt;We kept our tech stack lean but powerful to make this Easter egg &lt;em&gt;fly&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt;&lt;/strong&gt;: The core of our interactive UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/strong&gt;: Strong typing to keep our code bug-free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/pmndrs/react-three-fiber" rel="noopener noreferrer"&gt;react-three-fiber&lt;/a&gt;&lt;/strong&gt;: The magic behind our 3D scene, integrating &lt;strong&gt;&lt;a href="https://threejs.org/" rel="noopener noreferrer"&gt;three.js&lt;/a&gt;&lt;/strong&gt; with React.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/pmndrs/drei" rel="noopener noreferrer"&gt;react-three-drei&lt;/a&gt;&lt;/strong&gt;: Handy helpers for cameras, lighting, and effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/pmndrs/react-three-rapier" rel="noopener noreferrer"&gt;react-three-rapier&lt;/a&gt;&lt;/strong&gt;: Physics engine for realistic collisions and rocket interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎬 Setting Up the 3D Scene
&lt;/h3&gt;

&lt;p&gt;The first step was setting up the basic 3D scene for our rocket. We used &lt;strong&gt;react-three-fiber&lt;/strong&gt; to handle rendering the scene inside a React component, along with &lt;strong&gt;react-three-drei&lt;/strong&gt; for some helpers like the camera and lighting. We also integrated &lt;strong&gt;react-three-rapier&lt;/strong&gt; for physics and collision detection.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of the initial setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Canvas&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-three/fiber&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OrthographicCamera&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-three/drei&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Physics&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-three/rapier&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/three/rocket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SpaceEnvironment&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/three/space-environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SpaceRocks&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/three/space-rocks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RocketScene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrthographicCamera&lt;/span&gt; &lt;span class="na"&gt;makeDefault&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ambientLight&lt;/span&gt; &lt;span class="na"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;directionalLight&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Physics&lt;/span&gt; &lt;span class="na"&gt;gravity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Rocket&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SpaceRocks&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Physics&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SpaceEnvironment&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;RocketScene&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets up our 3D world with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rocket&lt;/strong&gt;: The star of the show.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SpaceEnvironment&lt;/strong&gt;: A cool space backdrop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SpaceRocks&lt;/strong&gt;: Floating space debris that adds extra flair—and danger! The rocket can collide with these rocks, causing them to split apart for a more dynamic and interactive experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighting&lt;/strong&gt;: Ambient and directional lights for a cosmic glow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physics&lt;/strong&gt;: Handles collisions and interactions between objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎥 Camera Choice: Why We Went with Orthographic
&lt;/h3&gt;

&lt;p&gt;We chose an &lt;strong&gt;Orthographic Camera&lt;/strong&gt; for our scene because it keeps everything proportional, no matter where the rocket is flying. Since our rocket scene spans the entire page (with height dictated by the scrollable content), an orthographic camera lets us fly the rocket up and down smoothly without any weird perspective distortions. It ensures a consistent, user-friendly experience as you scroll through the page and interact with the rocket.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Quick Tip: Handling Events with &lt;a href="https://r3f.docs.pmnd.rs/api/canvas#:~:text=react%2Dthree/fiber%22-,eventSource,-The%20source%20where" rel="noopener noreferrer"&gt;&lt;code&gt;eventSource&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;To ensure the 3D rocket scene can still respond to pointer events (e.g., mouse or touch interactions) even when other page content overlays it, we used the &lt;code&gt;eventSource&lt;/code&gt; prop in our &lt;strong&gt;Canvas&lt;/strong&gt; component. This tells &lt;strong&gt;react-three-fiber&lt;/strong&gt; where to listen for events. By setting &lt;code&gt;eventSource&lt;/code&gt; to the root element (&lt;code&gt;#root&lt;/code&gt;), the rocket can receive events while other content layers above it.&lt;/p&gt;

&lt;p&gt;Here's how we set it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Canvas&lt;/span&gt;
  &lt;span class="na"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// Specifies the DOM element to listen for pointer events&lt;/span&gt;
  &lt;span class="na"&gt;eventPrefix&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;         &lt;span class="c1"&gt;// The event prefix that is cast into canvas pointer x/y events&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 The Main Actor: Our Interactive Rocket
&lt;/h2&gt;

&lt;p&gt;The rocket is the star of the show—interacting with the environment, responding to mouse movements, and launching across the page. To make it truly dynamic, we used &lt;strong&gt;react-three-fiber&lt;/strong&gt; for rendering and &lt;strong&gt;react-three-rapier&lt;/strong&gt; for physics. Let’s break down the core functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State Machine&lt;/strong&gt;: Handles the rocket’s different states—idle, launching, following (tracking the mouse), and resetting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collision and Physics&lt;/strong&gt;: Using &lt;strong&gt;CapsuleCollider&lt;/strong&gt; and &lt;strong&gt;RoundConeCollider&lt;/strong&gt;, the rocket can collide with space rocks. Physics also helps simulate rocket movement and impulse on launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hover and Click Interaction&lt;/strong&gt;: The rocket responds to mouse hover with a scale-up effect, and clicking either launches it or resets its position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Movement&lt;/strong&gt;: The rocket tracks the mouse, using vectors to calculate distance and apply force, while also smoothly rotating to follow its flight direction in &lt;a href="https://r3f.docs.pmnd.rs/api/hooks#useframe" rel="noopener noreferrer"&gt;&lt;strong&gt;useFrame()&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particles&lt;/strong&gt;: Adds dynamic exhaust particles during the rocket’s movement.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Here’s a key snippet:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Rocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Refs and Hooks&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rocketHovered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="c1"&gt;// State machine&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rocketState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transitionTo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStateMachine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;states&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="na"&gt;launching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onEnter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;following&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="na"&gt;resetting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onEnter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;resetRocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Rocket reset logic&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resetRocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;// Reset rocket position, velocity, rotation&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Frame logic (executed every frame)&lt;/span&gt;
    &lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
      &lt;span class="c1"&gt;// Scale the rocket when hovered&lt;/span&gt;
      &lt;span class="nx"&gt;rocketHovered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;meshRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;meshRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Execute state-specific logic&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rocketState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resetRocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launching&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyImpulse&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Move rocket towards pointer, apply rotation&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RigidBody&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rigidBodyRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CapsuleCollider&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RoundConeCollider&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;
            &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meshRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;onPointerEnter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rocketHovered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;onPointerLeave&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rocketHovered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;rocketState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resetting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;transitionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launching&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RocketModel&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RigidBody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Our own particle effects component of the rocket exhaust clouds */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Particles&lt;/span&gt;
          &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rocketState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launching&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;rocketState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;following&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;objectRef&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;exhaustMeshRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Rocket Following Movement: Keeping Track of the Mouse
&lt;/h4&gt;

&lt;p&gt;One of the coolest parts of our rocket is its ability to follow the mouse as it flies through space. This is powered by the &lt;code&gt;useFrame&lt;/code&gt; hook in &lt;strong&gt;react-three-fiber&lt;/strong&gt;, which lets us update the rocket's position and rotation every frame. Here's how we handle the rocket's smooth movement and rotation while tracking the user's pointer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Points:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mouse Tracking&lt;/strong&gt;: We convert the normalized device coordinates (NDC) from the pointer into viewport coordinates and move the rocket towards that point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distance Calculation&lt;/strong&gt;: We compute the distance between the rocket's current position and the target, using that to apply a force in the correct direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotation&lt;/strong&gt;: The rocket smoothly rotates to face the direction it’s moving, giving a realistic flight experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Here’s a simplified version of how we achieve this:
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rigidBodyRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rocket&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Convert pointer coordinates to viewport space&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate distance between current rocket position and target&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distanceTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Apply impulse to move rocket toward target&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetPos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;multiplyScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyImpulse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate the rotation angle and smoothly rotate the rocket&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rotationAngle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;targetQuaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFromAxisAngle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationAxis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rotationAngle&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;slerpedQuaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slerpQuaternions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;targetQuaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slerpedQuaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  💡 Quick Tip: Handling Events on &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When dealing with &lt;strong&gt;three.js&lt;/strong&gt; and raycasting, it's common to have multiple meshes or submeshes within a &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt;. This can result in multiple &lt;code&gt;onClick&lt;/code&gt; or &lt;code&gt;onPointerEnter&lt;/code&gt; events being fired as each submesh gets hit by the raycaster. To avoid this, we can use &lt;code&gt;e.stopPropagation()&lt;/code&gt; to prevent multiple event triggers.&lt;/p&gt;

&lt;p&gt;Here’s a quick example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"rocket"&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Prevent multiple onClick calls&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RocketModel&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pointLight&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;mesh&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 Summary - Rocket:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State Machine&lt;/strong&gt;: Controls the rocket's different modes—&lt;strong&gt;idle&lt;/strong&gt;, &lt;strong&gt;launching&lt;/strong&gt;, &lt;strong&gt;following&lt;/strong&gt; (tracking the mouse), and &lt;strong&gt;resetting&lt;/strong&gt;. This ensures smooth transitions between states and interactions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Physics and Collisions&lt;/strong&gt;: Using &lt;strong&gt;CapsuleCollider&lt;/strong&gt; and &lt;strong&gt;RoundConeCollider&lt;/strong&gt;, the rocket can collide with objects like space rocks. Physics-based movement and impulse handling ensure realistic responses to collisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mouse Interaction&lt;/strong&gt;: The rocket grows when hovered over, thanks to scaling effects. Clicking it launches the rocket or resets it to its initial state, adding fun interaction for users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smooth Mouse Tracking&lt;/strong&gt;: The rocket tracks the mouse position smoothly, with calculated forces applied based on the distance to the pointer. It rotates toward the direction it’s moving, giving the feeling of true flight.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Particles&lt;/strong&gt;: The dynamic exhaust particles follow the rocket, adding visual flair as it launches and moves.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💥 Particle System: Fueling the Rocket’s Exhaust
&lt;/h2&gt;

&lt;p&gt;Our rocket wouldn't feel complete without some epic exhaust particles! We built a custom particle system using &lt;strong&gt;three.js&lt;/strong&gt; for rendering and shader-based control to give it that dynamic look. The system is fairly customizable, allowing us to control everything from particle lifetime to velocity and turbulence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWk2MHhucmh6Njg3amFsN2dlcjVxbGkwenp6eGd3aHJpeXFod21reSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/bkXqlyUk9KEV2QPEya/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYWk2MHhucmh6Njg3amFsN2dlcjVxbGkwenp6eGd3aHJpeXFod21reSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/bkXqlyUk9KEV2QPEya/giphy.gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Concepts:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom Shader Material&lt;/strong&gt;: We use a &lt;strong&gt;ShaderMaterial&lt;/strong&gt; to control particle appearance, such as size and color. The fragment shader includes logic to fade particles based on their age.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ParticleShaderMaterial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cyan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;pointSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;vertexShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    attribute float age;
    varying float vAge;
    void main() {
      vAge = age;
      gl_PointSize = ...;
      gl_Position = ...;
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fragmentShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    varying float vAge;
    void main() {
      float alpha = 1.0 - vAge; // Fade based on age
      gl_FragColor = vec4(color, alpha);
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Particle Initialization&lt;/strong&gt;: Particles are initialized with random positions and velocities, creating a realistic spread for the exhaust. Each particle has attributes like position, age, and size.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initializeParticles&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;velocities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;ages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// Set initial position&lt;/span&gt;
    &lt;span class="nx"&gt;velocities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(...));&lt;/span&gt; &lt;span class="c1"&gt;// Random velocity&lt;/span&gt;
    &lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;emissionRate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Stagger particle emission&lt;/span&gt;
    &lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sizeVariance&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;velocities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sizes&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frame Updates&lt;/strong&gt;: Each frame, we update particle positions based on their velocity and apply gravity and turbulence. As particles age, they fade out and are reset when they reach their lifetime limit.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update particle position and age every frame&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxParticles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;resetParticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Reset expired particles&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;updateParticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Particle Reset&lt;/strong&gt;: When a particle’s age reaches its limit, it’s reset to a new position, velocity, and age, making it ready for the next emission cycle.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resetParticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Reset particle to new random position and velocity&lt;/span&gt;
  &lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
  &lt;span class="nx"&gt;velocities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
  &lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Component Structure:
&lt;/h4&gt;

&lt;p&gt;The particle system is rendered as a &lt;code&gt;&amp;lt;points&amp;gt;&lt;/code&gt; mesh with buffer attributes for position, age, and size. A custom &lt;strong&gt;ShaderMaterial&lt;/strong&gt; controls particle appearance and behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;points&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;meshRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"geometry"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-position"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-age"&lt;/span&gt; &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;itemSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-particleSize"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;itemSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;primitive&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"material"&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ParticleShaderMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;transparent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;points&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  💥 Summary - Particles:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom Shader Material&lt;/strong&gt;: We use a &lt;strong&gt;ShaderMaterial&lt;/strong&gt; to manage particle appearance, handling size, color, and fade effects. The particles fade based on their age, providing a realistic exhaust effect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Particle Initialization&lt;/strong&gt;: Each particle is randomly initialized with a position, velocity, age, and size. This randomness gives the exhaust a natural spread as the rocket moves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frame Updates&lt;/strong&gt;: Every frame, the system updates particle positions based on their velocity and applies effects like gravity and turbulence. As particles age, they fade out and are reset when their lifetime ends.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic Particle Reset&lt;/strong&gt;: When a particle expires, it’s reset with new random properties (position, velocity, etc.), ensuring continuous emission without needing to generate new particles from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient Structure&lt;/strong&gt;: The system leverages a &lt;strong&gt;&lt;code&gt;&amp;lt;points&amp;gt;&lt;/code&gt;&lt;/strong&gt; mesh with buffer attributes for efficient handling of particle data. The &lt;strong&gt;ShaderMaterial&lt;/strong&gt; manages the rendering and visual effects, keeping everything performant.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🪨 SpaceRocks: Asteroids with Physics and Splitting
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;SpaceRocks&lt;/strong&gt; component brings extra life to our scene by generating asteroid-like rocks that float around and can be split upon collisions. Using &lt;strong&gt;Rapier&lt;/strong&gt; for physics, these rocks bounce around, interact with the rocket, and split dynamically when hit hard enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmM1cmVvcTkweGQzb2J5dG9yM3A2ZjQ2cHFvcTFkaWprYXRqOW12diZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Xw9qVN28AAyjHEyBBq/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmM1cmVvcTkweGQzb2J5dG9yM3A2ZjQ2cHFvcTFkaWprYXRqOW12diZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Xw9qVN28AAyjHEyBBq/giphy.gif" width="480" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rock Geometry&lt;/strong&gt;: Each rock is created using a &lt;strong&gt;&lt;a href="https://threejs.org/docs/#examples/en/geometries/ConvexGeometry" rel="noopener noreferrer"&gt;ConvexGeometry&lt;/a&gt;&lt;/strong&gt; made from random vertices. This results in irregular, rock-like shapes.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateRockGeometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rock Splitting&lt;/strong&gt;: When a rock collides with enough force, it's split into two smaller rocks. The splitting is handled by calculating the intersection points along a defined plane, then generating two new rocks from the original.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;splitRock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rockGeometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;verticesA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;verticesB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="c1"&gt;// Split the geometry along a plane&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plane&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Plane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometryA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verticesA&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;geometryB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConvexGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verticesB&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;geometryA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;geometryB&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collision Handling&lt;/strong&gt;: When a rock collides with the rocket or another object, we compute the force of the collision. If the force is above a threshold, the rock splits.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;onContactForce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forceMag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalForceMagnitude&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forceMag&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;handleCollision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceVec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Only split if the force is strong enough&lt;/span&gt;
&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Rock Generation:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Rocks are randomly positioned and given velocity in a grid. They are assigned attributes like velocity, angular velocity, and the ability to split on collisions.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rockId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idPrefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_rock_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;rockMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rockId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RapierRigidBody&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gridCells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
    &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateRockGeometry&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;canSplit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Component Structure:
&lt;/h4&gt;

&lt;p&gt;Each rock is a &lt;strong&gt;RigidBody&lt;/strong&gt; with properties such as restitution (bounciness) and friction. These rocks interact dynamically, bouncing off the environment and splitting upon impact.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RigidBody&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;linearVelocity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;restitution&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Bouncy collisions&lt;/span&gt;
    &lt;span class="na"&gt;friction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;onContactForce&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleCollision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceVec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Handle rock splitting&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Hull - Auto-generates mesh collider for convex geometries */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MeshCollider&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hull"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;mesh&lt;/span&gt; &lt;span class="na"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;primitive&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"material"&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rockMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;MeshCollider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;RigidBody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🪨 Summary - SpaceRocks:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Geometries&lt;/strong&gt;: Rocks are randomly generated with irregular shapes using &lt;strong&gt;ConvexGeometry&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collisions and Splitting&lt;/strong&gt;: Rocks split into smaller rocks upon high-force collisions, creating dynamic interactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physics Integration&lt;/strong&gt;: Using &lt;strong&gt;Rapier&lt;/strong&gt; for realistic movement, friction, and bouncy collisions makes the rocks behave convincingly in the scene.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This system adds an extra layer of interactivity and fun as the rocket navigates through space!&lt;/p&gt;

&lt;h2&gt;
  
  
  🌌 SpaceEnvironment: Immersive Space Backdrop
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;SpaceEnvironment&lt;/strong&gt; component brings a dynamic backdrop to our rocket scene. It includes a starfield and a collection of planets scattered throughout space. Here’s how we built it using &lt;strong&gt;three.js&lt;/strong&gt;, &lt;strong&gt;react-three-fiber&lt;/strong&gt;, and shaders for custom effects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExd3NvMnI2cjg2MTl3bXdqbzRiZzE4eTl1aXNpeDNzemt0ZzV4cWhuMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/GPdIgFzpid3iFDKD34/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExd3NvMnI2cjg2MTl3bXdqbzRiZzE4eTl1aXNpeDNzemt0ZzV4cWhuMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/GPdIgFzpid3iFDKD34/giphy.gif" width="960" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Starfield&lt;/strong&gt;: Stars are randomly generated in a cylindrical volume (height = pageHeight) and made to twinkle with a custom shader that controls size and opacity. The stars appear to blink and fade, simulating a living, dynamic space scene.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;}},&lt;/span&gt;
  &lt;span class="na"&gt;vertexShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    varying float vTwinkle; 
    uniform float time;
    void main() {
      vTwinkle = 0.5 + 0.5 * sin(time + position.x * 10.0);
      gl_PointSize = ...;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fragmentShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    varying float vTwinkle;
    void main() {
      gl_FragColor = vec4(color, opacity * vTwinkle);
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Instanced Planets&lt;/strong&gt;: We use instancing to efficiently render planets at random positions within the space. Each planet has a random color, size, and location, filling the scene with variety.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;planets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Instance&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getRandomInCylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerPadding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planetColors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;planetColors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerPadding&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rotating Space Environment&lt;/strong&gt;: The entire space environment rotates slowly, making the stars and planets appear to move in the background.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;environmentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.015&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElapsedTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  💡 Quick Tip: Using &lt;code&gt;window.devicePixelRatio&lt;/code&gt; for Consistent Shader Rendering
&lt;/h3&gt;

&lt;p&gt;When working with shaders that involve &lt;strong&gt;point size&lt;/strong&gt; (like stars in a starfield), it’s crucial to account for varying screen resolutions and pixel densities. By incorporating &lt;code&gt;window.devicePixelRatio&lt;/code&gt;, you ensure that your shader adjusts to different screens, maintaining consistent rendering across devices.&lt;/p&gt;

&lt;p&gt;Here's how we applied this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// Ensures consistent point size across devices&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;vertexShader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    uniform float dpr;
    void main() {
      gl_PointSize = gl_PointSize * dpr; // Adjust point size by device pixel ratio
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reminder:&lt;/strong&gt; Always update the &lt;code&gt;dpr&lt;/code&gt; value in your &lt;code&gt;useFrame&lt;/code&gt; or relevant hook when screens change, like dragging your window across monitors with different resolutions!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Component Structure:
&lt;/h4&gt;

&lt;p&gt;The stars and planets are rendered inside a &lt;code&gt;&amp;lt;group&amp;gt;&lt;/code&gt; element. Stars are rendered using &lt;code&gt;&amp;lt;points&amp;gt;&lt;/code&gt;, while planets are created using &lt;strong&gt;&lt;a href="https://drei.docs.pmnd.rs/performances/instances" rel="noopener noreferrer"&gt;Instances&lt;/a&gt;&lt;/strong&gt; for efficient rendering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Instanced stars */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;points&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bufferAttribute&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attributes-position"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;stars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;bufferGeometry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;primitive&lt;/span&gt; &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"material"&lt;/span&gt; &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;StarShaderMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;points&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Instanced planets */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Instances&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;material&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;PlanetMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;sphereGeometry&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planets&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Instances&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🌌 Summary - SpaceEnvironment:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Starfield&lt;/strong&gt;: Twinkling stars scattered randomly in a cylindrical space add life to the environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instanced Planets&lt;/strong&gt;: Randomly positioned and scaled planets provide variety and depth to the scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotating Environment&lt;/strong&gt;: Slow rotation of the entire backdrop makes space feel vast and alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Transitions&lt;/strong&gt;: Stars and planets fade in and out seamlessly when the environment becomes visible or hidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This space environment creates an immersive, dynamic backdrop for the rocket's adventure, contributing to the overall feeling of motion and depth in the scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏎️ Performance Boosts: Optimizing for Smooth Rocket Experience
&lt;/h2&gt;

&lt;p&gt;For an interactive experience like our rocket scene, keeping performance top-notch is critical—especially when dealing with 3D rendering and physics calculations. Let’s explore how we squeezed out those extra frames and kept things smooth:&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 1. Frame Loop Control: "Always" vs. "Demand"
&lt;/h3&gt;

&lt;p&gt;To prevent unnecessary GPU usage when the rocket isn’t in view, we created a trigger element just below the content on the webpage. This switch toggles the &lt;strong&gt;frame loop&lt;/strong&gt; between &lt;code&gt;"always"&lt;/code&gt; and &lt;code&gt;"demand"&lt;/code&gt;, ensuring the browser only re-renders when needed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Always"&lt;/strong&gt;: Used when the rocket is visible (scrolled into view) or animating, ensuring buttery-smooth performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Demand"&lt;/strong&gt;: Used when the rocket is idle and not in view, meaning the scene only re-renders when a specific event occurs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This technique helps reduce GPU strain and optimizes battery usage for mobile and laptop users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Canvas&lt;/span&gt; &lt;span class="na"&gt;frameloop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isRocketVisibleOrFlying&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 2. Custom &lt;code&gt;useViewportSize&lt;/code&gt; Hook
&lt;/h3&gt;

&lt;p&gt;We opted for a custom &lt;code&gt;useViewportSize()&lt;/code&gt; hook to track viewport changes without triggering excessive re-renders. While &lt;code&gt;useThree()&lt;/code&gt;'s &lt;code&gt;{ viewport/size }&lt;/code&gt; would cause rerenders on every scroll, we throttle viewport size checks to avoid performance hits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useViewportSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useFrame&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elapsedTime&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;throttleTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 3. Mesh Instancing for Planets
&lt;/h3&gt;

&lt;p&gt;For efficiency, we used &lt;strong&gt;mesh instancing&lt;/strong&gt; to handle the rendering of planets. Instancing allows you to render multiple copies of a geometry while reusing the same material, which drastically reduces the overhead of drawing each individual planet in the scene.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Instances&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;material&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;PlanetMaterial&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;sphereGeometry&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planets&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Instances&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🚀 4. Using Refs for Non-Rerender Values
&lt;/h3&gt;

&lt;p&gt;When dealing with dynamic updates inside the &lt;code&gt;useFrame()&lt;/code&gt; loop, we stored frequently changing values (like positions or velocities) in &lt;strong&gt;Refs&lt;/strong&gt;. This way, we can manipulate them directly without triggering component rerenders, which saves CPU cycles.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 5. General React-Three-Fiber Performance Tips
&lt;/h3&gt;

&lt;p&gt;Lastly, we adhered to best practices from the &lt;strong&gt;React Three Fiber docs&lt;/strong&gt; to avoid performance pitfalls. Some of these include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avoid excessive use of &lt;code&gt;useFrame()&lt;/code&gt;&lt;/strong&gt; unless absolutely necessary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch updates&lt;/strong&gt; by using &lt;code&gt;setState()&lt;/code&gt; sparingly inside animation loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimize new object creation&lt;/strong&gt; inside loops to prevent garbage collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more advanced performance tips, be sure to check out the official &lt;a href="https://r3f.docs.pmnd.rs/advanced/pitfalls" rel="noopener noreferrer"&gt;React Three Fiber pitfalls guide&lt;/a&gt; and &lt;a href="https://r3f.docs.pmnd.rs/advanced/scaling-performance" rel="noopener noreferrer"&gt;performance optimization tips&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Quick Tip: Canvas Size Limits on Some Devices
&lt;/h3&gt;

&lt;p&gt;When working with large canvases, remember that some devices (like &lt;strong&gt;Android Chrome&lt;/strong&gt; and &lt;strong&gt;Firefox on Desktop&lt;/strong&gt;) have size limitations that may not render properly if the canvas height exceeds 4096px. Always account for these limitations and consider dynamic scaling or feature disabling for large viewports!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;browserName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isDesktop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By implementing these performance tweaks, we ensured a smooth, immersive experience without bogging down users' devices—whether they’re on mobile or desktop. 🖥️📱&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Wrapping it Up: The Final Countdown
&lt;/h2&gt;

&lt;p&gt;Celebrating our &lt;strong&gt;1,000 followers on LinkedIn&lt;/strong&gt; with this interactive 3D rocket has been an absolute blast! From building a dynamic, mouse-controlled rocket to adding splitting space rocks, twinkling stars, and performance optimizations—this project has truly been a fun journey.&lt;/p&gt;

&lt;p&gt;It’s projects like these that remind us why we love what we do: blending creativity, tech, and a little bit of rocket science (okay, a lot of rocket science). We hope you enjoyed reading about how we put it all together and maybe even picked up a few tips for your own interactive web projects.&lt;/p&gt;

&lt;p&gt;Feel free to check out the live rocket Easter egg on &lt;strong&gt;&lt;a href="https://zerodays.dev" rel="noopener noreferrer"&gt;zerodays.dev&lt;/a&gt;&lt;/strong&gt; and, of course, keep an eye out for more interactive fun as we continue to build cool things and push the boundaries of what’s possible in web development.&lt;/p&gt;

&lt;p&gt;Here’s to the next milestone—and maybe, the next rocket! 🚀&lt;/p&gt;

&lt;p&gt;Thanks for reading and following along!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>tutorial</category>
      <category>threejs</category>
    </item>
  </channel>
</rss>
