Day 11 我們已經讓八大行星開始自轉,並暫時使用 Wireframe 網格,讓旋轉效果比較容易觀察。
今天要加入太陽系最重要的動畫之一 行星公轉
也就是讓行星不再固定在某個位置,而是開始繞著太陽移動。
今天會完成:
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 就會一直增加,代表行星在軌道上的位置一直改變。
今天在 planets.push() 裡面多加入:
angle:
startAngle
angle 可以理解成目前這顆行星繞太陽走到哪個角度。
如果 angle 一直增加,行星的位置就會一直改變,所以動畫裡:
planet.angle +=
planet.orbitSpeed;
就是讓行星持續往前走。
真正讓行星繞太陽移動的是:
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 平面上繞太陽公轉。
例如地球:
distance: 8
計算方式:
Math.cos(angle) * 8
Math.sin(angle) * 8
代表地球會保持在距離太陽大約 8 的位置運行。
如果是木星:
distance: 13
就會形成更大的公轉範圍。
所以:
distance 越小越靠近太陽
distance 越大越遠離太陽
這也就是為什麼每顆行星可以在不同的位置繞太陽。
如果每顆行星一開始都設定 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 13 我們會使用:
THREE.BufferGeometry
THREE.Line
替八大行星畫出自己的軌道線。
到這一步之後,整個畫面就會更像完整的太陽系模型。