Content commit
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
248
js/constructors.js
Normal file
248
js/constructors.js
Normal file
@@ -0,0 +1,248 @@
|
||||
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("../assets/celestials/" + data.name + "/map.jpg"),
|
||||
bumpMap: loader.load("../assets/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("../assets/celestials/" + data.name + "/map.jpg"),
|
||||
emissiveMap: loader.load("../assets/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("../assets/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;
|
||||
};
|
Reference in New Issue
Block a user