Spaces:
Sleeping
Sleeping
File size: 5,128 Bytes
d2226ad |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
import * as THREE from "three";
import { useFrame } from "@react-three/fiber";
import { useRef, useMemo, useLayoutEffect } from "react";
const vertexShader = `
attribute float size;
attribute vec3 customColor;
attribute float random;
varying vec3 vColor;
varying float vRandom;
void main() {
vColor = customColor;
vRandom = random;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
// Scale size based on distance (perspective)
// 300.0 is an arbitrary scale factor to make them look good
gl_PointSize = size * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`;
const fragmentShader = `
uniform float uTime;
varying vec3 vColor;
varying float vRandom;
void main() {
// Make it a circular particle
vec2 uv = gl_PointCoord.xy - 0.5;
float dist = length(uv);
// Discard corners of the square point
if (dist > 0.5) discard;
// Soft edge (glow)
// gradient from center (0.0) to edge (0.5)
float glow = 1.0 - (dist * 2.0);
glow = pow(glow, 1.5); // sharpen the glow curve
// Twinkle Logic
// Use sine wave based on Time + Random offset
// vRandom makes sure every star blinks out of phase
float twinkleSpeed = 3.0 + (vRandom * 2.0); // varied speeds
float twinkle = sin((uTime * twinkleSpeed) + (vRandom * 100.0));
// Map sine (-1 to 1) to a brightness factor (e.g. 0.6 to 1.0)
float brightness = 0.7 + (0.3 * twinkle);
gl_FragColor = vec4(vColor, glow * brightness);
}
`;
export function Stars({ data, visible }) {
const meshRef = useRef();
const materialRef = useRef();
const { positions, colors, sizes, randoms } = useMemo(() => {
if (!data)
return {
positions: new Float32Array(0),
colors: new Float32Array(0),
sizes: new Float32Array(0),
randoms: new Float32Array(0),
};
const starCount = data.stars.ids.length;
const posArray = new Float32Array(starCount * 3);
const colArray = new Float32Array(starCount * 3);
const sizeArray = new Float32Array(starCount);
const randArray = new Float32Array(starCount);
const radius = 100;
const color = new THREE.Color();
for (let i = 0; i < starCount; i++) {
const alt = data.stars.altitude[i];
const az = data.stars.azimuth[i];
const mag = data.stars.magnitude[i];
const id = data.stars.ids[i];
const phi = (90 - alt) * (Math.PI / 180);
const theta = az * (Math.PI / 180);
posArray[i * 3] = radius * Math.sin(phi) * Math.sin(theta);
posArray[i * 3 + 1] = radius * Math.cos(phi);
posArray[i * 3 + 2] = -radius * Math.sin(phi) * Math.cos(theta);
const spectralType = id % 10;
if (mag < 1.0) {
color.setHex(id % 2 === 0 ? 0xaaccff : 0xffddaa); // White-Blue or Gold for very bright stars
} else if (spectralType < 2) {
color.setHex(0xaaccff); // Blueish
} else if (spectralType < 4) {
color.setHex(0xffffff); // White
} else if (spectralType < 7) {
color.setHex(0xffebcd); // Yellow/White
} else {
color.setHex(0xffccaa); // Orange/Reddish
}
colArray[i * 3] = color.r;
colArray[i * 3 + 1] = color.g;
colArray[i * 3 + 2] = color.b;
let s = 4.0 - mag * 0.5;
if (s < 1.5) s = 1.5;
sizeArray[i] = s;
randArray[i] = Math.random(); // Random value for twinkling efect
}
return {
positions: posArray,
colors: colArray,
sizes: sizeArray,
randoms: randArray,
};
}, [data]);
useFrame((state) => {
if (materialRef.current) {
materialRef.current.uniforms.uTime.value = state.clock.getElapsedTime();
}
});
useLayoutEffect(() => {
if (meshRef.current) {
const geo = meshRef.current.geometry;
geo.attributes.position.needsUpdate = true;
geo.attributes.customColor.needsUpdate = true;
geo.attributes.size.needsUpdate = true;
geo.attributes.random.needsUpdate = true;
}
}, [positions, colors, sizes, randoms]);
return (
<group visible={visible}>
<points ref={meshRef}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={positions.length / 3}
array={positions}
itemSize={3}
/>
<bufferAttribute
attach="attributes-customColor"
count={colors.length / 3}
array={colors}
itemSize={3}
/>
<bufferAttribute
attach="attributes-size"
count={sizes.length}
array={sizes}
itemSize={1}
/>
<bufferAttribute
attach="attributes-random"
count={randoms.length}
array={randoms}
itemSize={1}
/>
</bufferGeometry>
<shaderMaterial
ref={materialRef}
uniforms={{ uTime: { value: 0 } }}
vertexShader={vertexShader}
fragmentShader={fragmentShader}
transparent={true}
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
</group>
);
}
|