summaryrefslogtreecommitdiffhomepage
path: root/derelict.js
blob: ff100e01b8c6ee5490f1db670e2a7ea812ada6c5 (plain)
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
import * as THREE from 'https://unpkg.com/three@0.126.1/build/three.module.js'
import { OrbitControls } from 'https://unpkg.com/three@0.126.1/examples/jsm/controls/OrbitControls.js'
import { CSS2DRenderer, CSS2DObject } from 'https://unpkg.com/three@0.126.1/examples/jsm/renderers/CSS2DRenderer.js'

let camera, scene, renderer, renderer2d, wreck

const SCALE = 10000

init()
animate()

function Wreck(vec3, km) {
	const point = new THREE.Object3D()
	point.position.copy(vec3)

	const div = document.createElement('div')
	div.className = 'wreck'
	const w = document.importNode(wreck, true)
	div.appendChild(w)
	const label = new CSS2DObject(div)
	point.add(label)

	return point;
}

function loadWreckIcon() {
	return fetch("wreck.svg")
		.then(response => response.text())
		.then(text => {
			const parser = new window.DOMParser()
			const svg = parser.parseFromString(text, "image/svg+xml")
			wreck = svg.documentElement
			return wreck
		})
}

function processKillmails(killmails) {
	let clusters = []
	killmails.forEach(km => {
		const pos = km.victim.position
		km.victim.position = new THREE.Vector3(pos.x, pos.y, pos.z)
		const vec3 = km.victim.position
		let added = false
		for (const cluster of clusters) {
			if (cluster[0].victim.position.distanceTo(vec3) < 100 * SCALE) {
				cluster.push(km)
				added = true
				break
			}
		}
		if (!added) {
			clusters.push([km])
		}
	})
	let center = new THREE.Vector3()
	let positions = clusters[0].map(km => km.victim.position)
	positions.forEach(pos => center.add(new THREE.Vector3(pos.x, pos.y, pos.z)))
	center.divideScalar(positions.length)
	clusters[0].sort((a, b) => a.killmail_time.localeCompare(b.killmail_time))
	let elements = []
	clusters[0].forEach(km => {
		const vec3 = km.victim.position
		vec3.sub(center)
		vec3.divideScalar(SCALE)
		const point = new THREE.Object3D()
		point.position.copy(vec3)
		const div = document.createElement('div')
		div.className = 'wreck'
		const w = document.importNode(wreck, true)
		div.appendChild(w)
		const label = new CSS2DObject(div)
		point.add(label)
		scene.add(point)
		elements.push(div)
	})
	const timeline = document.getElementById("timeline")
	timeline.max = elements.length + 1
	timeline.oninput = function () {
		elements.slice(0, this.value).forEach(element => element.classList.add("killed"))
		elements.slice(this.value, this.max).forEach(element => element.classList.remove("killed"))
	}
}

function init() {
	camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 100)
	camera.position.set(8, 8, 8)
	camera.lookAt(0, 0, 0)
	scene = new THREE.Scene()

	loadWreckIcon()
		.then(() => {
			const url = new URL(window.location.href)
			return fetch(url.searchParams.get("related") + ".json")
		})
		.then(response => response.json())
		.then(killmails => {
			const url = km => `https://esi.evetech.net/latest/killmails/${km.id}/${km.hash}/?datasource=tranquility`
			const retrieve = km => fetch(url(km)).then(response => response.json())
			return Promise.all(killmails.map(retrieve))
		})
		.then(processKillmails)

	const grid = new THREE.GridHelper(30, 30)
	scene.add(grid)

	renderer = new THREE.WebGLRenderer({antialias: true})
	renderer.setSize(window.innerWidth, window.innerHeight)
	document.body.appendChild(renderer.domElement)

	renderer2d = new CSS2DRenderer()
	renderer2d.setSize(window.innerWidth, window.innerHeight)
	renderer2d.domElement.style.position = 'absolute'
	renderer2d.domElement.style.top = '0px'
	document.body.appendChild(renderer2d.domElement)
	const controls = new OrbitControls(camera, renderer2d.domElement)
	controls.minDistance = 2
	controls.maxDistance = 24

	const loader = new THREE.TextureLoader()
	loader.loadAsync("https://i.imgur.com/rDGOLFC.jpg") // TODO: Don't use imgur as CDN.
		.then(skybox => {
			const rt = new THREE.WebGLCubeRenderTarget(skybox.image.height)
			rt.fromEquirectangularTexture(renderer, skybox)
			scene.background = rt
		});

	window.addEventListener('resize', onWindowResize)
}

function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight
	camera.updateProjectionMatrix()
	renderer.setSize(window.innerWidth, window.innerHeight)
	renderer2d.setSize(window.innerWidth, window.innerHeight)
}

function animate() {
	requestAnimationFrame(animate)
	renderer.render(scene, camera)
	renderer2d.render(scene, camera)
}