forked from breck7/ScrollHub
-
Notifications
You must be signed in to change notification settings - Fork 0
/
globe.js
182 lines (152 loc) · 5.3 KB
/
globe.js
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
180
181
182
class Globe {
constructor() {
this.renderer = null
this.scene = null
this.camera = null
this.earth = null
this.spikes = []
this.animationId = null // To store the current animation frame ID
}
createGlobe() {
// Ensure any previous globe and animation is removed
this.removeGlobe()
// Initialize scene, camera, renderer, etc.
this.scene = new THREE.Scene()
const height = window.innerHeight - 100
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / height, 0.1, 1000)
this.camera.position.z = 1
this.renderer = new THREE.WebGLRenderer()
this.renderer.setSize(window.innerWidth, height)
document.body.prepend(this.renderer.domElement)
// Create Earth
const geometry = new THREE.SphereGeometry(0.5, 32, 32)
const texture = new THREE.TextureLoader().load("earth_atmos_2048.jpg")
const material = new THREE.MeshBasicMaterial({ map: texture })
this.earth = new THREE.Mesh(geometry, material)
this.scene.add(this.earth)
// Start the animation loop
this.animate()
return this
}
removeGlobe() {
// Cancel the previous animation frame to stop multiple animations
if (this.animationId) {
cancelAnimationFrame(this.animationId)
}
// Dispose of the renderer, scene, and event listeners
if (this.renderer) {
this.renderer.dispose()
this.renderer.domElement.remove()
}
if (this.scene) {
while (this.scene.children.length > 0) {
const object = this.scene.children[0]
if (object.geometry) object.geometry.dispose()
if (object.material) object.material.dispose()
this.scene.remove(object)
}
}
this.spikes = []
this.renderer = null
this.scene = null
this.earth = null
}
listenToResize() {
window.addEventListener("resize", () => {
this.createGlobe() // Recreate the globe on resize
})
return this
}
listenForClicks() {
document.body.addEventListener("dblclick", () => {
if (!document.fullscreenElement) {
document.body.requestFullscreen().catch(err => {
console.log(`Error trying to go fullscreen: ${err.message} (${err.name})`)
})
} else {
document.exitFullscreen()
}
})
// Pause/Resume on single-click
document.body.addEventListener("click", () => (this.shouldRotate = !this.shouldRotate))
return this
}
shouldRotate = true
animate() {
if (!this.earth) return
const { earth, spikes, renderer, scene, camera } = this
// Store the animation frame ID so we can cancel it if needed
this.animationId = requestAnimationFrame(this.animate.bind(this))
// Rotate Earth
if (this.shouldRotate) earth.rotation.y += 0.001
// Update spikes
spikes.forEach((spike, index) => {
spike.scale.y *= 0.95
if (spike.scale.y < 0.01) {
earth.remove(spike)
spikes.splice(index, 1)
}
})
// Render the scene
renderer.render(scene, camera)
}
latLongToVector3(lat, lon) {
const phi = (90 - lat) * (Math.PI / 180)
const theta = (lon + 180) * (Math.PI / 180)
const x = -0.5 * Math.sin(phi) * Math.cos(theta)
const y = 0.5 * Math.cos(phi)
const z = 0.5 * Math.sin(phi) * Math.sin(theta)
return new THREE.Vector3(x, y, z)
}
visualizeHit(request) {
const { lat, long, type } = request
const position = this.latLongToVector3(lat, long)
const spikeGeometry = new THREE.ConeGeometry(0.005, 0.3, 8)
spikeGeometry.translate(0, 0.05, 0)
spikeGeometry.rotateX(Math.PI / 2)
const color = type === "read" ? 0xffffff : 0x00ff00
const spikeMaterial = new THREE.MeshBasicMaterial({ color })
const spike = new THREE.Mesh(spikeGeometry, spikeMaterial)
spike.position.set(position.x, position.y, position.z)
spike.lookAt(new THREE.Vector3(0, 0, 0))
this.earth.add(spike) // Add spike to earth instead of scene
this.spikes.push(spike)
}
bindToSSE() {
const logContainer = document.getElementById("log-container")
const urlParams = new URLSearchParams(window.location.search)
const folderName = urlParams.get("folderName")
const queryString = folderName ? `?folderName=${folderName}` : ""
if (folderName) document.querySelector("#summaryLink").href = "summarizeRequests.htm?folderName=" + folderName
else document.querySelector("#summaryLink").href = "requests.html"
const eventSource = new EventSource(`/requests.htm${queryString}`)
eventSource.onmessage = event => {
const data = JSON.parse(event.data)
const logEntry = document.createElement("div")
logEntry.textContent = data.log
logContainer.appendChild(logEntry)
logContainer.scrollTop = logContainer.scrollHeight
const parts = data.log.split(" ")
const long = parseFloat(parts.pop())
const lat = parseFloat(parts.pop())
const type = parts.shift()
const page = parts.shift()
const request = {
lat,
long,
type,
page
}
this.visualizeHit(request)
}
eventSource.onerror = error => {
console.error("EventSource failed:", error)
eventSource.close()
}
eventSource.onopen = event => {
console.log("SSE connection opened")
}
return this
}
}
window.globe = new Globe().createGlobe().bindToSSE().listenToResize().listenForClicks()