Skip to content

Commit e0b6352

Browse files
Refactor: Use 3D sphere globe
Replaced the 2D globe with a 3D sphere globe.
1 parent 1dee9d0 commit e0b6352

File tree

1 file changed

+145
-62
lines changed

1 file changed

+145
-62
lines changed

src/components/Globe3D.tsx

Lines changed: 145 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -15,124 +15,207 @@ const Globe3D = () => {
1515
const centerY = canvas.height / 2;
1616
const radius = Math.min(canvas.width, canvas.height) * 0.35;
1717

18-
let rotation = 0;
19-
const connections: Array<{ from: number; to: number; opacity: number }> = [];
20-
const points: Array<{ x: number; y: number; z: number; lat: number; lng: number }> = [];
18+
let rotationX = 0;
19+
let rotationY = 0;
20+
const connections: Array<{ from: number; to: number; opacity: number; pulse: number }> = [];
21+
const points: Array<{ x: number; y: number; z: number; lat: number; lng: number; pulse: number }> = [];
2122

22-
// Generate random points on sphere
23+
// Generate random points on sphere surface
2324
const generatePoints = () => {
24-
for (let i = 0; i < 50; i++) {
25+
for (let i = 0; i < 80; i++) {
2526
const lat = (Math.random() - 0.5) * Math.PI;
2627
const lng = Math.random() * 2 * Math.PI;
27-
const x = radius * Math.cos(lat) * Math.cos(lng);
28-
const y = radius * Math.cos(lat) * Math.sin(lng);
29-
const z = radius * Math.sin(lat);
30-
points.push({ x, y, z, lat, lng });
28+
points.push({
29+
x: 0, y: 0, z: 0,
30+
lat, lng,
31+
pulse: Math.random() * Math.PI * 2
32+
});
3133
}
3234
};
3335

34-
// Generate connections
36+
// Generate connections between points
3537
const generateConnections = () => {
36-
for (let i = 0; i < 30; i++) {
38+
for (let i = 0; i < 60; i++) {
3739
const from = Math.floor(Math.random() * points.length);
3840
let to = Math.floor(Math.random() * points.length);
3941
while (to === from) {
4042
to = Math.floor(Math.random() * points.length);
4143
}
42-
connections.push({ from, to, opacity: Math.random() * 0.5 + 0.1 });
44+
connections.push({
45+
from,
46+
to,
47+
opacity: Math.random() * 0.5 + 0.2,
48+
pulse: Math.random() * Math.PI * 2
49+
});
4350
}
4451
};
4552

53+
// Convert spherical coordinates to 3D cartesian
54+
const sphericalTo3D = (lat: number, lng: number) => {
55+
const x = radius * Math.cos(lat) * Math.cos(lng);
56+
const y = radius * Math.sin(lat);
57+
const z = radius * Math.cos(lat) * Math.sin(lng);
58+
return { x, y, z };
59+
};
60+
61+
// Rotate point in 3D space
62+
const rotate3D = (x: number, y: number, z: number) => {
63+
// Rotate around Y axis
64+
const cosY = Math.cos(rotationY);
65+
const sinY = Math.sin(rotationY);
66+
const x1 = x * cosY - z * sinY;
67+
const z1 = x * sinY + z * cosY;
68+
69+
// Rotate around X axis
70+
const cosX = Math.cos(rotationX);
71+
const sinX = Math.sin(rotationX);
72+
const y1 = y * cosX - z1 * sinX;
73+
const z2 = y * sinX + z1 * cosX;
74+
75+
return { x: x1, y: y1, z: z2 };
76+
};
77+
78+
// Project 3D point to 2D screen
79+
const project3D = (x: number, y: number, z: number) => {
80+
const perspective = 800;
81+
const scale = perspective / (perspective + z);
82+
return {
83+
x: centerX + x * scale,
84+
y: centerY + y * scale,
85+
scale: scale
86+
};
87+
};
88+
4689
generatePoints();
4790
generateConnections();
4891

4992
const draw = () => {
5093
ctx.clearRect(0, 0, canvas.width, canvas.height);
5194

52-
// Draw globe wireframe
53-
ctx.strokeStyle = 'rgba(0, 255, 0, 0.3)';
95+
// Update point positions
96+
points.forEach(point => {
97+
const pos3D = sphericalTo3D(point.lat, point.lng);
98+
const rotated = rotate3D(pos3D.x, pos3D.y, pos3D.z);
99+
point.x = rotated.x;
100+
point.y = rotated.y;
101+
point.z = rotated.z;
102+
point.pulse += 0.05;
103+
});
104+
105+
// Draw globe wireframe (latitude and longitude lines)
106+
ctx.strokeStyle = 'rgba(0, 255, 0, 0.15)';
54107
ctx.lineWidth = 1;
55108

56109
// Draw latitude lines
57-
for (let lat = -Math.PI/2; lat <= Math.PI/2; lat += Math.PI/6) {
110+
for (let lat = -Math.PI/2; lat <= Math.PI/2; lat += Math.PI/8) {
58111
ctx.beginPath();
112+
let firstPoint = true;
59113
for (let lng = 0; lng <= 2 * Math.PI; lng += 0.1) {
60-
const x = centerX + radius * Math.cos(lat) * Math.cos(lng + rotation);
61-
const y = centerY + radius * Math.cos(lat) * Math.sin(lng + rotation);
62-
const z = radius * Math.sin(lat);
114+
const pos3D = sphericalTo3D(lat, lng);
115+
const rotated = rotate3D(pos3D.x, pos3D.y, pos3D.z);
63116

64-
if (z > -radius * 0.5) { // Only draw front hemisphere
65-
if (lng === 0) {
66-
ctx.moveTo(x, y);
117+
if (rotated.z > -radius * 0.3) { // Only draw visible parts
118+
const projected = project3D(rotated.x, rotated.y, rotated.z);
119+
if (firstPoint) {
120+
ctx.moveTo(projected.x, projected.y);
121+
firstPoint = false;
67122
} else {
68-
ctx.lineTo(x, y);
123+
ctx.lineTo(projected.x, projected.y);
69124
}
125+
} else {
126+
firstPoint = true;
70127
}
71128
}
72129
ctx.stroke();
73130
}
74131

75132
// Draw longitude lines
76-
for (let lng = 0; lng < 2 * Math.PI; lng += Math.PI/6) {
133+
for (let lng = 0; lng < 2 * Math.PI; lng += Math.PI/8) {
77134
ctx.beginPath();
135+
let firstPoint = true;
78136
for (let lat = -Math.PI/2; lat <= Math.PI/2; lat += 0.1) {
79-
const x = centerX + radius * Math.cos(lat) * Math.cos(lng + rotation);
80-
const y = centerY + radius * Math.cos(lat) * Math.sin(lng + rotation);
81-
const z = radius * Math.sin(lat);
137+
const pos3D = sphericalTo3D(lat, lng);
138+
const rotated = rotate3D(pos3D.x, pos3D.y, pos3D.z);
82139

83-
if (z > -radius * 0.5) { // Only draw front hemisphere
84-
if (lat === -Math.PI/2) {
85-
ctx.moveTo(x, y);
140+
if (rotated.z > -radius * 0.3) { // Only draw visible parts
141+
const projected = project3D(rotated.x, rotated.y, rotated.z);
142+
if (firstPoint) {
143+
ctx.moveTo(projected.x, projected.y);
144+
firstPoint = false;
86145
} else {
87-
ctx.lineTo(x, y);
146+
ctx.lineTo(projected.x, projected.y);
88147
}
148+
} else {
149+
firstPoint = true;
89150
}
90151
}
91152
ctx.stroke();
92153
}
93154

94-
// Draw connection points
95-
points.forEach(point => {
96-
const x = centerX + point.x * Math.cos(rotation) - point.y * Math.sin(rotation);
97-
const y = centerY + point.y * Math.cos(rotation) + point.x * Math.sin(rotation);
98-
const z = point.z;
99-
100-
if (z > -radius * 0.5) {
101-
ctx.fillStyle = 'rgba(0, 255, 0, 0.8)';
102-
ctx.beginPath();
103-
ctx.arc(x, y, 2, 0, 2 * Math.PI);
104-
ctx.fill();
105-
}
106-
});
155+
// Sort points by z-depth for proper rendering
156+
const visiblePoints = points
157+
.map((point, index) => ({ ...point, index }))
158+
.filter(point => point.z > -radius * 0.5)
159+
.sort((a, b) => a.z - b.z);
107160

108-
// Draw connections
161+
// Draw connections first (behind points)
109162
connections.forEach(connection => {
110163
const fromPoint = points[connection.from];
111164
const toPoint = points[connection.to];
112165

113-
const fromX = centerX + fromPoint.x * Math.cos(rotation) - fromPoint.y * Math.sin(rotation);
114-
const fromY = centerY + fromPoint.y * Math.cos(rotation) + fromPoint.x * Math.sin(rotation);
115-
const fromZ = fromPoint.z;
166+
if (fromPoint.z > -radius * 0.5 && toPoint.z > -radius * 0.5) {
167+
const fromProjected = project3D(fromPoint.x, fromPoint.y, fromPoint.z);
168+
const toProjected = project3D(toPoint.x, toPoint.y, toPoint.z);
116169

117-
const toX = centerX + toPoint.x * Math.cos(rotation) - toPoint.y * Math.sin(rotation);
118-
const toY = centerY + toPoint.y * Math.cos(rotation) + toPoint.x * Math.sin(rotation);
119-
const toZ = toPoint.z;
170+
// Animate connection opacity with pulse effect
171+
connection.pulse += 0.03;
172+
const pulseOpacity = (Math.sin(connection.pulse) + 1) * 0.5;
173+
const opacity = connection.opacity * pulseOpacity * 0.6;
120174

121-
if (fromZ > -radius * 0.5 && toZ > -radius * 0.5) {
122-
ctx.strokeStyle = `rgba(0, 255, 0, ${connection.opacity})`;
123-
ctx.lineWidth = 1;
175+
ctx.strokeStyle = `rgba(0, 255, 0, ${opacity})`;
176+
ctx.lineWidth = 1.5;
124177
ctx.beginPath();
125-
ctx.moveTo(fromX, fromY);
126-
ctx.lineTo(toX, toY);
178+
ctx.moveTo(fromProjected.x, fromProjected.y);
179+
ctx.lineTo(toProjected.x, toProjected.y);
127180
ctx.stroke();
128181
}
182+
});
129183

130-
// Animate connection opacity
131-
connection.opacity += (Math.random() - 0.5) * 0.02;
132-
connection.opacity = Math.max(0.1, Math.min(0.6, connection.opacity));
184+
// Draw connection points
185+
visiblePoints.forEach(point => {
186+
const projected = project3D(point.x, point.y, point.z);
187+
188+
// Calculate depth-based brightness and size
189+
const depthFactor = (point.z + radius) / (2 * radius);
190+
const brightness = 0.3 + depthFactor * 0.7;
191+
const size = 1.5 + depthFactor * 2;
192+
193+
// Pulsing effect
194+
const pulseSize = Math.sin(point.pulse) * 0.5 + 1;
195+
196+
// Draw glow effect
197+
const gradient = ctx.createRadialGradient(
198+
projected.x, projected.y, 0,
199+
projected.x, projected.y, size * pulseSize * 3
200+
);
201+
gradient.addColorStop(0, `rgba(0, 255, 0, ${brightness * 0.8})`);
202+
gradient.addColorStop(1, 'rgba(0, 255, 0, 0)');
203+
204+
ctx.fillStyle = gradient;
205+
ctx.beginPath();
206+
ctx.arc(projected.x, projected.y, size * pulseSize * 3, 0, 2 * Math.PI);
207+
ctx.fill();
208+
209+
// Draw the point itself
210+
ctx.fillStyle = `rgba(0, 255, 0, ${brightness})`;
211+
ctx.beginPath();
212+
ctx.arc(projected.x, projected.y, size * pulseSize, 0, 2 * Math.PI);
213+
ctx.fill();
133214
});
134215

135-
rotation += 0.005;
216+
// Increment rotation for continuous animation
217+
rotationY += 0.005;
218+
rotationX += 0.002;
136219
};
137220

138221
const interval = setInterval(draw, 50);
@@ -143,9 +226,9 @@ const Globe3D = () => {
143226
return (
144227
<canvas
145228
ref={canvasRef}
146-
width={300}
147-
height={300}
148-
className="opacity-80"
229+
width={350}
230+
height={350}
231+
className="opacity-90"
149232
/>
150233
);
151234
};

0 commit comments

Comments
 (0)