-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathposition_marker.js
348 lines (307 loc) · 11 KB
/
position_marker.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
import * as THREE from './three.module.min.js';
/**
* Represents a sphere that shows a position.
*/
class PositionMarker
{
/**
* The mesh object
*/
mesh;
/**
* A html element that acts as the marker's label.
*/
label_el;
/**
* The scene that this marker is added to.
*/
scene;
is_visible;
/**
* @type {string} The marker's label text.
*/
label_text;
#label_width;
#label_height;
#label_state_change;
/**
*
* @param {THREE.Scene} scene The scene to which the marker will be added.
* @param {THREE.Vector3} position The position of the marker.
* @param {float} radius The radius of the sphere.
*/
constructor(scene, position = new THREE.Vector3(), radius = 1)
{
const geometry = new THREE.SphereGeometry(radius, 10, 10);
const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
material.transparent = true;
material.opacity = 0.5;
this.mesh = new THREE.Mesh(geometry, material);
this.mesh.position.set(position.x, position.y, position.z);
this.mesh.layers.enable(2); // All marker meshes belong to layer 2.
this.scene = scene;
scene.add(this.mesh);
this.is_visible = false;
this.#label_state_change = 0;
}
set_label(name)
{
let labels_container = document.getElementById('labels'); // TODO: find a way to specify the labels container
const elem = document.createElement('div');
elem.textContent = name;
elem.id = "_label_" + name;
labels_container.appendChild(elem);
this.label_el = elem;
this.#label_width = elem.clientWidth;
this.#label_height = elem.clientHeight;
this.label_text = name;
}
update_label_position(camera, renderer_width, renderer_height)
{
if (!this.label_el) return; // no label
let visible = false;
//this.mesh.updateWorldMatrix( true, false ); <-- not sure what this does. Doesn't seem to affect the outcome?
//this.mesh.getWorldPosition( tempV ); <-- why this rather then just mesh.position ?
let tempV = this.mesh.position.clone();
// Only show the marker label if the marker is within 2km of the camera.
let limit = 20000;
let dist = tempV.distanceTo(camera.position);
if (dist < limit)
{
visible = true;
// get the normalized screen coordinate of that position
// x and y will be in the -1 to +1 range with x = -1 being
// on the left and y = -1 being on the bottom
tempV.project(camera);
// convert the normalized position to CSS coordinates
const x = (tempV.x * .5 + .5) * renderer_width;
const y = (tempV.y * -.5 + .5) * renderer_height - 15;
// Is there free space to position the label?
if (Math.abs(tempV.z) > 1) visible = false;
if (visible)
{
//console.log('check label at', x - this.#label_width / 2, y + this.#label_height, this.#label_width, this.#label_height);
if (ScreenSpace.is_free(x - this.#label_width / 2, y + this.#label_height, this.#label_width, this.#label_height))
{
ScreenSpace.mark_as_occupied(x - this.#label_width / 2, y + this.#label_height, this.#label_width, this.#label_height);
// move the elem to that position
this.label_el.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
// set the zIndex for sorting
// this.label_el.style.zIndex = (-tempV.z * .5 + .5) * 100000 | 0;
// make elements further away more transparent.
//this.label_el.style.opacity = (1000 + (limit - dist)) / limit;
}
else
{
visible = false;
}
}
}
if (!visible || Math.abs(tempV.z) > 1)
{
// if (this.#label_state_change > -10) this.#label_state_change--;
// if (this.#label_state_change == -10)
// {
// hide the label
this.label_el.style.opacity = 0;
this.label_el.className = 'fadeout';
this.#label_state_change = 0;
// }
}
if (visible)
{
if (this.#label_state_change < 5) this.#label_state_change++;
if (this.#label_state_change == 5)
{
this.label_el.style.opacity = 1;
this.label_el.className = 'fadein';
}
}
this.is_visible = visible;
}
/**
* Sets the position of the marker.
* @param {THREE.Vector3} position
*/
set_position(position = new THREE.Vector3())
{
this.mesh.position.set(position.x, position.y, position.z);
}
/**
* Sets the size (diameter) of the marker.
* @param {float} size This will be the new diameter of the marker.
*/
set_size(size)
{
this.mesh.scale(size, size, size);
}
/**
* Sets the visibility.
* @param {bool} is_visible
*/
visible(is_visible)
{
this.mesh.visible = is_visible;
}
/**
* Removes this marker's mesh.
*/
destroy()
{
this.scene.remove(this.mesh);
this.mesh.geometry.dispose();
this.mesh.material.dispose();
this.mesh = undefined;
if (this.label_el) this.label_el.remove();
}
}
/**
* Represents a disc to show position and a cylinder around the disc to show accuracy of the position.
*/
class GPSPositionMarker
{
/**
* Mesh for displaying the accuracy
*/
accuracy_mesh;
/**
* The HTML element to use for the marker position.
*/
#disc_el;
/**
* The position of the marker.
*/
position;
/**
*
* @param {THREE.Scene} scene The scene to which the marker will be added.
* @param {THREE.Vector3} position The position of the marker.
* @param {HTMLElement} disc_el The HTML element to use for the marker position.
*/
constructor(scene, position = new THREE.Vector3(), disc_el)
{
const geometry = new THREE.CylinderGeometry(0.5, 0.5, 20, 20, 1, true); // Diameter of 1.
const material = new THREE.MeshBasicMaterial({color: 0xffff00});
material.transparent = true;
material.opacity = 0.25;
material.side = THREE.DoubleSide;
this.accuracy_mesh = new THREE.Mesh(geometry, material);
this.accuracy_mesh.position.set(position.x, position.y, position.z);
this.scene = scene;
scene.add(this.accuracy_mesh);
this.#disc_el = disc_el;
}
/**
* Sets the position of the marker and its accuracy.
* @param {THREE.Vector3} position
* @param {number} accuracy A positive value in meters. This will determine the size of the accuracy sphere for this marker.
* @param {THREE.Camera} camera
* @param {number} viewport_width The width of the viewport
* @param {number} viewport_heigth The height of the viewport
*/
set_position(position = new THREE.Vector3(), accuracy = 0, camera, viewport_width, viewport_heigth)
{
// limit to 50m, as any larger makes no sense for display purposes.
if (accuracy > 50) accuracy = 50;
accuracy *= 2; // remember to double for drawing the sphere!
this.accuracy_mesh.position.set(position.x, position.y, position.z);
let height_scale = accuracy / 100;
if (height_scale < 0.1) height_scale = 0.1;
this.accuracy_mesh.scale.set(accuracy, height_scale, accuracy);
// For working out if the marker is in the frustrum. If not can skip the screen projection.
const frustum = new THREE.Frustum();
frustum.setFromProjectionMatrix(camera.projectionMatrix)
frustum.planes.forEach(function(plane) { plane.applyMatrix4(camera.matrixWorld) })
let bb = new THREE.Box3().setFromCenterAndSize(position, new THREE.Vector3(1,1,1));
let in_view = false;
if(frustum.intersectsBox(bb))
{
// The 3D position of marker needs to be projected to the screen to set the disc element position.
let tempV = position.clone();
// get the normalized screen coordinate of that position
// x and y will be in the -1 to +1 range with x = -1 being
// on the left and y = -1 being on the bottom
tempV.project(camera);
// convert the normalized position to CSS coordinates
const x = (tempV.x * .5 + .5) * viewport_width;
const y = (tempV.y * -.5 + .5) * viewport_heigth;
//console.log(x, y, tempV.x, tempV.y);
// move the elem to that position
this.#disc_el.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
if (Math.abs(tempV.z) <= 1) in_view = true;
}
if (in_view)
this.#disc_el.style.opacity = 1;
else
this.#disc_el.style.opacity = 0;
this.position = position;
}
/**
* Sets the visibility.
* @param {bool} is_visible
*/
visible(is_visible)
{
this.accuracy_mesh.visible = is_visible;
this.#disc_el.style.opacity = is_visible ? 1 : 0;
}
/**
* Removes this marker's mesh.
*/
destroy()
{
this.scene.remove(this.accuracy_mesh);
this.accuracy_mesh.geometry.dispose();
this.accuracy_mesh.material.dispose();
this.accuracy_mesh = undefined;
}
}
/**
* Class for keeping track of screen space available for labels and for checking if a given area already has a label.
*/
class ScreenSpace
{
static occupied;
static width;
static height;
static reset(width, height)
{
this.width = Math.ceil(width / 10);
this.height = Math.ceil(height / 10);
this.occupied = new Uint8Array(this.width * this.height);
}
static is_free(posx, posy, width, height)
{
posx = Math.ceil(posx / 10);
posy = Math.ceil(posy / 10);
width = Math.ceil(width / 10);
height = Math.ceil(height / 10);
let index;
for (let x = posx; x < posx + width; x++)
{
for (let y = posy; y < posy + height; y++)
{
index = x + y * this.width;
if (this.occupied[index]) return false;
}
}
return true;
}
static mark_as_occupied(posx, posy, width, height)
{
posx = Math.ceil(posx / 10);
posy = Math.ceil(posy / 10);
width = Math.ceil(width / 10);
height = Math.ceil(height / 10);
let index;
for (let x = posx; x < posx + width; x++)
{
for (let y = posy; y < posy + height; y++)
{
index = x + y * this.width;
this.occupied[index] = 1;
}
}
}
}
export {PositionMarker, GPSPositionMarker, ScreenSpace};