249 lines
7.8 KiB
JavaScript
249 lines
7.8 KiB
JavaScript
import * as THREE from "three";
|
|
|
|
export const createCelestialBody = function (data, parent, depth = 0, isLast = false) {
|
|
let geometry, material;
|
|
const detail = 12;
|
|
const loader = new THREE.TextureLoader();
|
|
|
|
if (data.detail === 2) {
|
|
geometry = new THREE.IcosahedronGeometry(data.radius, detail);
|
|
material = new THREE.MeshPhongMaterial({
|
|
map: loader.load("/celestials/" + data.name + "/map.jpg"),
|
|
bumpMap: loader.load("/celestials/" + data.name + "/bump.jpg"),
|
|
bumpScale: 10,
|
|
});
|
|
} else if (data.detail === 1 && data.emissive) {
|
|
geometry = new THREE.IcosahedronGeometry(data.radius, detail);
|
|
material = new THREE.MeshPhongMaterial({
|
|
map: loader.load("/celestials/" + data.name + "/map.jpg"),
|
|
emissiveMap: loader.load("/celestials/" + data.name + "/map.jpg"),
|
|
emissive: 0xffffff,
|
|
});
|
|
} else if (data.detail === 1) {
|
|
geometry = new THREE.IcosahedronGeometry(data.radius, detail);
|
|
material = new THREE.MeshPhongMaterial({
|
|
map: loader.load("/celestials/" + data.name + "/map.jpg"),
|
|
});
|
|
} else {
|
|
geometry = new THREE.SphereGeometry(data.radius, 64, 64);
|
|
material = new THREE.MeshPhysicalMaterial({
|
|
color: data.color,
|
|
emissive: data.emissive ? data.emissive.color : null,
|
|
});
|
|
}
|
|
|
|
const body = new THREE.Mesh(geometry, material);
|
|
|
|
// Stars
|
|
if (data.emissive) {
|
|
const light = new THREE.PointLight(data.emissive.color, data.emissive.intensity, data.emissive.distance, data.emissive.decay);
|
|
light.position.set(0, 0, 0);
|
|
light.castShadow = true;
|
|
body.add(light);
|
|
}
|
|
|
|
body.receiveShadow = true;
|
|
if (!data.emissive) {
|
|
body.castShadow = true;
|
|
}
|
|
|
|
// Flags
|
|
body.isCelestialBody = true;
|
|
|
|
body.position.set(data.orbitRadius || 0, 0, 0);
|
|
body.rotation.x = data.inclination || 0;
|
|
|
|
body.name = data.name;
|
|
body.content = data.content;
|
|
body.rotationSpeed = data.rotationSpeed;
|
|
body.orbitRadius = data.orbitRadius;
|
|
body.orbitSpeed = data.orbitSpeed;
|
|
body.orbitInclination = data.orbitInclination;
|
|
body.orbitInclinationAngle = data.orbitInclinationAngle;
|
|
|
|
// Parenting
|
|
if (parent) {
|
|
body.parent = parent;
|
|
parent.add(body);
|
|
|
|
// Orbit Line
|
|
if (data.orbitRadius) {
|
|
const points = [];
|
|
const colors = [];
|
|
|
|
const color1 = new THREE.Color(data.color);
|
|
const color2 = new THREE.Color(0x000000);
|
|
|
|
for (let i = 0; i <= 360; i++) {
|
|
const radians = (i * Math.PI) / 180;
|
|
points.push(new THREE.Vector3(data.orbitRadius * Math.cos(radians), 0, data.orbitRadius * -Math.sin(radians)));
|
|
|
|
const progress = i / 360;
|
|
const interpolation = progress < 0.8 ? progress / 0.8 : 1;
|
|
const color = color1.clone().lerp(color2, interpolation);
|
|
colors.push(color.r, color.g, color.b);
|
|
}
|
|
const orbitGeometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
orbitGeometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
|
|
|
|
const orbitMaterial = new THREE.LineBasicMaterial({
|
|
vertexColors: true,
|
|
linewidth: 2,
|
|
transparent: true,
|
|
opacity: 0.75,
|
|
});
|
|
|
|
const line = new THREE.LineLoop(orbitGeometry, orbitMaterial);
|
|
|
|
// Flags
|
|
line.isOrbit = true;
|
|
if (data.orbitInclination) line.rotation.x = data.orbitInclination;
|
|
|
|
parent.add(line);
|
|
body.orbit = line;
|
|
}
|
|
}
|
|
|
|
// Dropdown
|
|
const dropdown = document.getElementById("dropdown");
|
|
const option = document.createElement("option");
|
|
option.value = body.uuid;
|
|
|
|
let prefix = "";
|
|
if (depth > 0) {
|
|
prefix = "│ ".repeat(depth - 1) + (isLast ? "└─ " : "├─ ");
|
|
}
|
|
option.textContent = prefix + data.name;
|
|
dropdown.appendChild(option);
|
|
|
|
// Recursion
|
|
if (data.children) {
|
|
length = data.children.length;
|
|
|
|
data.children.forEach((data, index) => {
|
|
if (data.type && data.type === "particles") return createCelestialParticles(data, body);
|
|
else return createCelestialBody(data, body, depth + 1, index === length - 1);
|
|
});
|
|
}
|
|
|
|
return body;
|
|
};
|
|
|
|
export const createCelestialParticles = function (data, parent) {
|
|
const colors = [];
|
|
for (let i = 0; i < 10; i++) {
|
|
const color = new THREE.Color(data.color);
|
|
color.offsetHSL(Math.random() * 0.2 - 0.1, Math.random() * 0.2 - 0.1, Math.random() * 0.2 - 0.1);
|
|
colors.push(color);
|
|
}
|
|
|
|
const sizeWeights = [0.8, 0.9, 1, 1.1, 1.2];
|
|
const particleGroups = Array.from({ length: 5 }, () => ({
|
|
points: [],
|
|
colors: [],
|
|
}));
|
|
|
|
for (let i = 0; i < data.ringVolume; i++) {
|
|
const radius = Math.random() * (data.orbitOuterRadius - data.orbitInnerRadius) + data.orbitInnerRadius;
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const x = Math.cos(angle) * radius;
|
|
const y = Math.random() * data.orbitThickness - data.orbitThickness / 2;
|
|
const z = Math.sin(angle) * radius;
|
|
|
|
const groupIndex = Math.floor(Math.random() * 5);
|
|
particleGroups[groupIndex].points.push(new THREE.Vector3(x, y, z));
|
|
|
|
const color = colors[Math.floor(Math.random() * 10)];
|
|
particleGroups[groupIndex].colors.push(color.r, color.g, color.b);
|
|
}
|
|
|
|
const particleSystem = new THREE.Group();
|
|
|
|
particleGroups.forEach((group, index) => {
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(group.points);
|
|
geometry.setAttribute("color", new THREE.Float32BufferAttribute(group.colors, 3));
|
|
const material = new THREE.PointsMaterial({
|
|
size: sizeWeights[index] * data.radius,
|
|
sizeAttenuation: true,
|
|
vertexColors: true,
|
|
transparent: true,
|
|
opacity: 0.75,
|
|
});
|
|
|
|
const particles = new THREE.Points(geometry, material);
|
|
particleSystem.add(particles);
|
|
});
|
|
|
|
// Flags
|
|
particleSystem.isCelestialParticles = true;
|
|
|
|
if (data.orbitInclination) particleSystem.rotation.x = data.orbitInclination;
|
|
if (data.orbitInclinationAngle) particleSystem.rotation.y = data.orbitInclinationAngle;
|
|
particleSystem.rotationSpeed = data.orbitSpeed;
|
|
|
|
// Parenting
|
|
if (parent) parent.add(particleSystem);
|
|
|
|
return particleSystem;
|
|
};
|
|
|
|
export const createStarField = function (
|
|
count = 10000,
|
|
groups = 5,
|
|
innerRadius = 20000,
|
|
outerRadius = 25000,
|
|
colors = [
|
|
{ color: 0xfff8e7, weight: 5 },
|
|
{ color: 0xffffff, weight: 3 },
|
|
{ color: 0xa5d6ff, weight: 2 },
|
|
{ color: 0xffffbf, weight: 2 },
|
|
{ color: 0xffd6a5, weight: 2 },
|
|
{ color: 0xaaaaff, weight: 1 },
|
|
{ color: 0xffa5a5, weight: 1 },
|
|
{ color: 0xffa5d6, weight: 1 },
|
|
{ color: 0xa5a5ff, weight: 1 },
|
|
]
|
|
) {
|
|
const starGroups = Array.from({ length: groups }, () => ({
|
|
points: [],
|
|
colors: [],
|
|
}));
|
|
|
|
const totalWeight = colors.reduce((sum, color) => sum + color.weight, 0);
|
|
const weightedColors = colors.flatMap((color) => Array(color.weight).fill(color.color));
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const radius = Math.random() * (outerRadius - innerRadius) + innerRadius;
|
|
const theta = Math.random() * Math.PI * 2;
|
|
const phi = Math.acos(2 * Math.random() - 1);
|
|
const x = radius * Math.sin(phi) * Math.cos(theta);
|
|
const y = radius * Math.sin(phi) * Math.sin(theta);
|
|
const z = radius * Math.cos(phi);
|
|
|
|
const groupIndex = Math.floor(Math.random() * groups);
|
|
starGroups[groupIndex].points.push(new THREE.Vector3(x, y, z));
|
|
|
|
const color = new THREE.Color(weightedColors[Math.floor(Math.random() * totalWeight)]);
|
|
starGroups[groupIndex].colors.push(color.r, color.g, color.b);
|
|
}
|
|
|
|
const starField = new THREE.Group();
|
|
|
|
starGroups.forEach((group, index) => {
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(group.points);
|
|
geometry.setAttribute("color", new THREE.Float32BufferAttribute(group.colors, 3));
|
|
const material = new THREE.PointsMaterial({
|
|
size: 6 * index,
|
|
sizeAttenuation: true,
|
|
vertexColors: true,
|
|
transparent: true,
|
|
opacity: 0.8,
|
|
});
|
|
|
|
const stars = new THREE.Points(geometry, material);
|
|
starField.add(stars);
|
|
});
|
|
|
|
return starField;
|
|
};
|