今天開始加入太陽系最重要的成員「八大行星」。
今天不會直接複製八份 SphereGeometry + Material + Mesh,而是開始把程式整理成「資料 + 迴圈」的方式,這會讓後面的自轉、公轉、速度控制更容易處理。
今天我們會完成:
index.html
與 Day 9 相同,不需要修改。
css/style.css
與 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,
18,
32
);
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,
64,
64
);
const sunMaterial =
new THREE.MeshBasicMaterial({
color: 0xffaa00
});
const sun =
new THREE.Mesh(
sunGeometry,
sunMaterial
);
scene.add(sun);
// ========================
// Planet Data
// ========================
const planetData = [
{
name: "Mercury",
size: 0.35,
color: 0xaaaaaa,
distance: 4
},
{
name: "Venus",
size: 0.55,
color: 0xd9a066,
distance: 6
},
{
name: "Earth",
size: 0.6,
color: 0x2277ff,
distance: 8
},
{
name: "Mars",
size: 0.45,
color: 0xcc4422,
distance: 10
},
{
name: "Jupiter",
size: 1.2,
color: 0xd8b38a,
distance: 13
},
{
name: "Saturn",
size: 1.0,
color: 0xe6c87a,
distance: 16
},
{
name: "Uranus",
size: 0.8,
color: 0x66ccdd,
distance: 19
},
{
name: "Neptune",
size: 0.75,
color: 0x3355ff,
distance: 22
}
];
// ========================
// Create Planets
// ========================
const planets = [];
planetData.forEach(
(data) => {
const geometry =
new THREE.SphereGeometry(
data.size,
32,
32
);
const material =
new THREE.MeshBasicMaterial({
color: data.color
});
const planet =
new THREE.Mesh(
geometry,
material
);
planet.position.x =
data.distance;
scene.add(
planet
);
planets.push(
planet
);
}
);
// ========================
// 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;
renderer.render(
scene,
camera
);
}
animate();
Day 9 我們使用 camera.position.z = 8; 因為當時只有一顆太陽。
但今天最遠的海王星已經放到 distance: 22 如果 Camera 還距離太近
就沒有辦法把整個太陽系看清楚,所以今天改成:
camera.position.set(
0,
18,
32
);
也就是相機稍微往上、往後移。
接著:
camera.lookAt(
0,
0,
0
);
告訴 Camera 看向太陽系中心,所以現在我們不是完全正面看,而是稍微從上方觀看。
這樣之後加入軌道時會更有 3D 感。
今天最重要的新東西是:
const planetData = [
{
name: "Mercury",
size: 0.35,
color: 0xaaaaaa,
distance: 4
},
...
];
以前如果要建立八顆行星,可能會寫:
const mercuryGeometry = ...
const mercuryMaterial = ...
const mercury = ...
const venusGeometry = ...
const venusMaterial = ...
const venus = ...
const earthGeometry = ...
const earthMaterial = ...
const earth = ...
一直重複八次,雖然做得到,但是程式會越來越長。
所以今天開始把行星資料整理成:
Planet
│
├── name
├── size
├── color
└── distance
例如地球:
{
name: "Earth",
size: 0.6,
color: 0x2277ff,
distance: 8
}
名稱:Earth
大小:0.6
顏色:藍色
距離:8
有了資料之後,就可以:
planetData.forEach(
(data) => {
...
}
);
把陣列裡的每一筆行星資料拿出來。
第一次 data = Mercury
第二次 data = Venus
第三次 data = Earth
一直執行到 data = Neptune。
大小:
const geometry =
new THREE.SphereGeometry(
data.size,
32,
32
);
這裡不再寫死而是使用 data.size。
new THREE.SphereGeometry(
0.5,
32,
32
);
顏色:
const material =
new THREE.MeshBasicMaterial({
color: data.color
});
planet.position.x =
data.distance;
就是把行星放在不同 X 座標像是我們現在:
Sun X = 0
Mercury X = 4
Venus X = 6
Earth X = 8
Mars X = 10
Jupiter X = 13
Saturn X = 16
Uranus X = 19
Neptune X = 22
要注意這些不是太陽系真實距離,我們現在採用的是適合 3D 網頁展示的比例。
為什麼還需要 planets?
我們另外建立 const planets = [];
每建立一顆行星
planets.push(
planet
);
最後這個陣列裡面會有:
planets
│
├── Mercury
├── Venus
├── Earth
├── Mars
├── Jupiter
├── Saturn
├── Uranus
└── Neptune
今天看起來好像沒有什麼作用,但其實這是在幫 Day 11 做準備。
因為下一天我們就可以直接:
planets.forEach(
(planet) => {
planet.rotation.y += 0.01;
}
);
讓八大行星全部開始旋轉,開始替後面的功能整理程式。
目前程式結構已經從 Day 9 的 只有一顆太陽
進化成:
而程式也開始從,一個物件寫一段程式
改成用 Planet Data 和 forEach 去快速把每個星球的資料建立好
這個改變很重要,因為之後我們還會在 planetData 裡加入:
rotationSpeed
orbitSpeed
texture
description
到最後,planetData 就會變成整個太陽系的核心資料。
「行星自轉:讓八大行星開始旋轉」
Day 11 會開始使用今天建立的 const planets = []; 讓八大行星開始自轉。
但我們不會讓所有行星都用 rotation.y += 0.01; 完全一樣的速度。
我們會把資料進一步改成:
{
name: "Earth",
size: 0.6,
color: 0x2277ff,
distance: 8,
rotationSpeed: 0.01
}
各自擁有自己的旋轉設定。