iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Modern Web

從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作系列 第 12 篇

Day 12 - 行星公轉:讓八大行星開始繞太陽運行

  • 分享至 

  • xImage
  •  

Day 11 我們已經讓八大行星開始自轉,並暫時使用 Wireframe 網格,讓旋轉效果比較容易觀察。
今天要加入太陽系最重要的動畫之一 行星公轉
也就是讓行星不再固定在某個位置,而是開始繞著太陽移動。

一、今天完成目標

今天會完成:

  • 加入 orbitSpeed 公轉速度
  • 加入 angle 公轉角度
  • 使用 Math.cos() 計算 X 座標
  • 使用 Math.sin() 計算 Z 座標
  • 讓八大行星繞著太陽運行
  • 每顆行星使用不同公轉速度

二、完整程式碼

index.html

與 Day 11 相同,不需要修改。(完整在 Day 9)

css/style.css

與 Day 11 相同,不需要修改。(完整在 Day 9)

js/main.js

import * as THREE from
    "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js";


// ========================
// Scene
// ========================

const scene =
    new THREE.Scene();

scene.background =
    new THREE.Color(
        0x000000
    );


// ========================
// Camera
// ========================

const camera =
    new THREE.PerspectiveCamera(
        75,
        window.innerWidth /
        window.innerHeight,
        0.1,
        1000
    );

camera.position.set(
    0,
    20,
    36
);

camera.lookAt(
    0,
    0,
    0
);


// ========================
// Renderer
// ========================

const renderer =
    new THREE.WebGLRenderer({
        antialias: true
    });

renderer.setSize(
    window.innerWidth,
    window.innerHeight
);

renderer.setPixelRatio(
    Math.min(
        window.devicePixelRatio,
        2
    )
);

document
    .getElementById("scene")
    .appendChild(
        renderer.domElement
    );


// ========================
// Sun
// ========================

const sunGeometry =
    new THREE.SphereGeometry(
        2,
        32,
        16
    );

const sunMaterial =
    new THREE.MeshBasicMaterial({
        color: 0xffaa00
    });

const sun =
    new THREE.Mesh(
        sunGeometry,
        sunMaterial
    );

const sunwireMaterial =
    new THREE.MeshBasicMaterial({
        color: 0xffffff,
        wireframe: true
    });

const sunwire =
    new THREE.Mesh(
        sunGeometry,
        sunwireMaterial
    );

       sunwire.scale.set(
            1.03,
            1.03,
            1.03
        );

sun.add(
    sunwire
);

scene.add(
    sun
);


// ========================
// Planet Data
// ========================

const planetData = [

    {
        name: "Mercury",
        size: 0.45,
        color: 0xaaaaaa,
        distance: 4,
        rotationSpeed: 0.008,
        orbitSpeed: 0.04
    },

    {
        name: "Venus",
        size: 0.65,
        color: 0xd9a066,
        distance: 6,
        rotationSpeed: -0.003,
        orbitSpeed: 0.03
    },

    {
        name: "Earth",
        size: 0.7,
        color: 0x2277ff,
        distance: 8,
        rotationSpeed: 0.01,
        orbitSpeed: 0.025
    },

    {
        name: "Mars",
        size: 0.55,
        color: 0xcc4422,
        distance: 10,
        rotationSpeed: 0.009,
        orbitSpeed: 0.02
    },

    {
        name: "Jupiter",
        size: 1.3,
        color: 0xd8b38a,
        distance: 13,
        rotationSpeed: 0.02,
        orbitSpeed: 0.012
    },

    {
        name: "Saturn",
        size: 1.1,
        color: 0xe6c87a,
        distance: 16,
        rotationSpeed: 0.018,
        orbitSpeed: 0.009
    },

    {
        name: "Uranus",
        size: 0.9,
        color: 0x66ccdd,
        distance: 19,
        rotationSpeed: 0.012,
        orbitSpeed: 0.007
    },

    {
        name: "Neptune",
        size: 0.85,
        color: 0x3355ff,
        distance: 22,
        rotationSpeed: 0.011,
        orbitSpeed: 0.005
    }

];


// ========================
// Create Planets
// ========================

const planets = [];

planetData.forEach(
    (data, index) => {

        // 行星球體
        const geometry =
            new THREE.SphereGeometry(
                data.size,
                12,
                8
            );


        // 行星材質
        const material =
            new THREE.MeshBasicMaterial({
                color: data.color
            });


        // 建立行星
        const planet =
            new THREE.Mesh(
                geometry,
                material
            );


        // ========================
        // Wireframe
        // ========================

        const wireMaterial =
            new THREE.MeshBasicMaterial({
                color: 0xffffff,
                wireframe: true
            });

        const wire =
            new THREE.Mesh(
                geometry,
                wireMaterial
            );

        wire.scale.set(
            1.03,
            1.03,
            1.03
        );

        planet.add(
            wire
        );


        // 加入場景
        scene.add(
            planet
        );


        // 每顆行星使用不同起始角度
        const startAngle =
            index * 0.8;


        // 儲存行星資料
        planets.push({

            mesh: planet,

            distance:
                data.distance,

            rotationSpeed:
                data.rotationSpeed,

            orbitSpeed:
                data.orbitSpeed,

            angle:
                startAngle
        });

    }
);


// ========================
// Resize
// ========================

window.addEventListener(
    "resize",
    () => {

        camera.aspect =
            window.innerWidth /
            window.innerHeight;

        camera.updateProjectionMatrix();

        renderer.setSize(
            window.innerWidth,
            window.innerHeight
        );

        renderer.setPixelRatio(
            Math.min(
                window.devicePixelRatio,
                2
            )
        );

    }
);


// ========================
// Animation
// ========================

function animate() {

    requestAnimationFrame(
        animate
    );


    // 太陽自轉
    sun.rotation.y +=
        0.005;


    planets.forEach(
        (planet) => {

            // ========================
            // 自轉
            // ========================

            planet.mesh.rotation.y +=
                planet.rotationSpeed;


            // ========================
            // 公轉
            // ========================

            planet.angle +=
                planet.orbitSpeed;


            planet.mesh.position.x =
                Math.cos(
                    planet.angle
                ) *
                planet.distance;


            planet.mesh.position.z =
                Math.sin(
                    planet.angle
                ) *
                planet.distance;

        }
    );


    renderer.render(
        scene,
        camera
    );
}

animate();

三、加入公轉速度

Day 11 每顆行星只有 rotationSpeed,今天再加入一個 orbitSpeed
例如地球:

{
    name: "Earth",
    size: 0.7,
    color: 0x2277ff,
    distance: 8,
    rotationSpeed: 0.01,
    orbitSpeed: 0.025
}

現在一顆行星就同時有兩種速度。
rotationSpeed → 控制自己旋轉的速度
orbitSpeed → 控制繞太陽移動的速度

動畫裡原本只有:

planet.mesh.rotation.y +=
    planet.rotationSpeed;

今天再加入:

planet.angle +=
    planet.orbitSpeed;

angle 就會一直增加,代表行星在軌道上的位置一直改變。

四、程式碼簡單說明

  1. angle 是什麼?

今天在 planets.push() 裡面多加入:

angle:
    startAngle

angle 可以理解成目前這顆行星繞太陽走到哪個角度。
如果 angle 一直增加,行星的位置就會一直改變,所以動畫裡:

planet.angle +=
    planet.orbitSpeed;

就是讓行星持續往前走。

  1. 使用 Math.cos() 和 Math.sin()

真正讓行星繞太陽移動的是:

planet.mesh.position.x =
    Math.cos(
        planet.angle
    ) *
    planet.distance;

以及:

planet.mesh.position.z =
    Math.sin(
        planet.angle
    ) *
    planet.distance;

我們用 Math.cos() 控制 X 座標。
用 Math.sin() 控制 Z 座標。
這兩個數值一起變化後,行星就會形成一個圓形運動。
所以行星是在 X、Z 平面上繞太陽公轉。

  1. 為什麼要乘上 distance

例如地球:

distance: 8

計算方式:

Math.cos(angle) * 8
Math.sin(angle) * 8

代表地球會保持在距離太陽大約 8 的位置運行。
如果是木星:

distance: 13

就會形成更大的公轉範圍。

所以:

distance 越小越靠近太陽
distance 越大越遠離太陽

這也就是為什麼每顆行星可以在不同的位置繞太陽。

  1. 為什麼需要 startAngle ,

如果每顆行星一開始都設定 angle: 0 ,
那麼網頁剛開啟時會變成全部排在同一個方向。
所以今天加入:

const startAngle =
    index * 0.8;

index 就是目前第幾顆行星。

例如:

Mercury index = 0
Venus index = 1
Earth index = 2
Mars index = 3

所以最後:

Mercury → 0
Venus → 0.8
Earth → 1.6
Mars → 2.4

每顆行星一開始的位置都不同,看起來就不會全部擠成一直線。

五、小試身手

現在八大行星已經會繞著太陽運行,
大家可以試著修改 orbitSpeed。

例如把地球 orbitSpeed: 0.025 改成 orbitSpeed: 0.08
看看地球公轉速度會發生什麼變化。

也可以試著把海王星 orbitSpeed: 0.005 改成 orbitSpeed: 0.03
觀察原本跑得比較慢的外側行星變快之後,整個畫面會有什麼不同。

不過這裡先不要太在意速度是不是符合真實比例。
因為目前的數值主要是為了讓動畫容易觀察。
Day 14 我們還會再專門處理每顆行星不同的公轉速度。

六、今天小結

Day 11 我們完成的是行星自轉
Day 12 則加入行星公轉
目前我們已經完成:

全螢幕場景、太陽、八大行星、不同大小、顏色、距離、自轉速度、自轉、公轉速度、公轉動畫。

現在行星已經真的會繞著太陽跑了,不過還有一個問題,
雖然我們知道它們是在繞圓形軌道運行,但畫面上目前看不到軌道路線,
所以接下來就要把這些看不見的軌道畫出來。

補上完成的畫面:
Day 12

Day 13 預告

「行星軌道:把看不見的公轉路線畫出來」

Day 13 我們會使用:

THREE.BufferGeometry
THREE.Line

替八大行星畫出自己的軌道線。
到這一步之後,整個畫面就會更像完整的太陽系模型。


上一篇
Day 11 - 行星自轉:讓八大行星開始旋轉
下一篇
Day 13 - 行星軌道:把看不見的公轉路線畫出來
系列文
從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言