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; };