@mappedin/mappedin-js 5.12.0 → 5.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,6 @@
4
4
  // ../three
5
5
  // ../@tweenjs/tween.js
6
6
  // ../minisearch
7
- // ../stats.js
8
7
 
9
8
  declare module '@mappedin/mappedin-js' {
10
9
  import { MapView } from '@mappedin/mappedin-js/renderer/public/MapView';
@@ -6928,648 +6927,6 @@ declare module '@mappedin/mappedin-js/renderer/internal/Mappedin.CameraLayers' {
6928
6927
  }
6929
6928
  }
6930
6929
 
6931
- import { Mesh, BufferAttribute, InstancedBufferAttribute, InstancedBufferGeometry, Object3D, Color, CanvasTexture, RawShaderMaterial, } from 'three';
6932
- import { getObjectId, getObject } from '@mappedin/mappedin-js/renderer/internal/utils';
6933
- import { HoverLabel } from '@mappedin/mappedin-js/renderer/internal';
6934
- import LabelAtlasFragment from '@mappedin/mappedin-js/renderer/internal/shaders/Mappedin.LabelAtlas.Fragment.glsl';
6935
- import LabelAtlasVertex from '@mappedin/mappedin-js/renderer/internal/shaders/Mappedin.LabelAtlas.Vertex.glsl';
6936
- export const DEFAULT_LABEL_SIZING = {
6937
- MARGIN: 2,
6938
- HEIGHT_MARGIN: 0.5,
6939
- SIZE: 1.2,
6940
- };
6941
- const FONT_SIZE = 56;
6942
- const SIZE_IN_METERS = FONT_SIZE / DEFAULT_LABEL_SIZING.SIZE; // About this, make it the same as legacy labels anyway
6943
- const PIXELS_PER_METER = FONT_SIZE / 2;
6944
- const METERS_PER_PIXEL = 1 / PIXELS_PER_METER;
6945
- const CANVAS_MAX_LABELS = 64;
6946
- const MIN_SLOT_SIZE = 16;
6947
- const LABEL_PADDING = 4;
6948
- class Label {
6949
- constructor() {
6950
- this.x = 0;
6951
- this.y = 0;
6952
- this.width = 0;
6953
- this.height = 0;
6954
- this.layer = null;
6955
- this.index = null;
6956
- }
6957
- /// This _just_ lays out the vertex into the attribute buffers.
6958
- layout(map, origin, size, rotation, uv, color) {
6959
- let angle = (this.canvasBounds.rotation * Math.PI) / 180;
6960
- let width = this.width * this.pixelsPerMu;
6961
- let height = this.height * this.pixelsPerMu;
6962
- // fix the alignment of the labels. This just shifts them around
6963
- // in canvasBound area to the left/right/center
6964
- let offset;
6965
- if (this.canvasBounds.align == 'left') {
6966
- offset = this.margin;
6967
- }
6968
- else if (this.canvasBounds.align == 'right') {
6969
- offset = this.canvasBounds.maxWidth - width - this.margin;
6970
- }
6971
- else if (this.canvasBounds.align == 'center') {
6972
- offset = (this.canvasBounds.maxWidth - width - this.margin) / 2;
6973
- }
6974
- let offsetX = offset * Math.cos(angle);
6975
- let offsetY = -offset * Math.sin(angle);
6976
- let index = this.index;
6977
- origin.setXYZ(index, this.canvasBounds.x - map.width / 2 + offsetX, -this.canvasBounds.y + map.height / 2 + offsetY, this.z + 0.1);
6978
- size.setXY(index, width, height);
6979
- rotation.setX(index, angle);
6980
- let canvas = this.layer.canvas;
6981
- uv.setXYZW(index, this.x / canvas.width, 1 - this.y / canvas.height, this.width / canvas.width, this.height / canvas.height);
6982
- color.setXYZ(index, this.color.r, this.color.g, this.color.b);
6983
- // mark all the arrays as needing to be updated (reuploaded) since
6984
- // they have now been changed.
6985
- origin.needsUpdate = true;
6986
- size.needsUpdate = true;
6987
- rotation.needsUpdate = true;
6988
- uv.needsUpdate = true;
6989
- color.needsUpdate = true;
6990
- }
6991
- }
6992
- class Slot {
6993
- constructor(x, y, width, height) {
6994
- this.x = x;
6995
- this.y = y;
6996
- this.width = width;
6997
- this.height = height;
6998
- }
6999
- leftOverSpace(glyph) {
7000
- // check to see if it would not fit.
7001
- if (this.width < glyph.width || this.height < glyph.height) {
7002
- return null;
7003
- }
7004
- return this.height * (this.width - glyph.width) + this.width * (this.height - glyph.height);
7005
- }
7006
- /// split this slot into 0-2 spaces.
7007
- split(label, spaces, index) {
7008
- label.x = this.x;
7009
- label.y = this.y;
7010
- if (this.width - label.height < MIN_SLOT_SIZE && this.height - label.height < MIN_SLOT_SIZE) {
7011
- // delete itself from the array
7012
- spaces.splice(index, 1);
7013
- }
7014
- else if (this.width === label.width) {
7015
- spaces.splice(index, 1, new Slot(this.x, this.y + label.height, this.width, this.height - label.height));
7016
- }
7017
- else if (this.height === label.height) {
7018
- spaces.splice(index, 1, new Slot(this.x + label.width, this.y, this.width - label.width, this.height));
7019
- }
7020
- else {
7021
- spaces.splice(index, 1, new Slot(this.x + label.width, this.y, this.width - label.width, label.height), new Slot(this.x, this.y + label.height, this.width, this.height - label.height));
7022
- }
7023
- }
7024
- }
7025
- class AtlasLayer {
7026
- /// Creates a new Atlas Layer,
7027
- constructor(atlas, map, width, height) {
7028
- // height can be anything so we have to match it to a close power of two
7029
- let power = 5; // start with 5 because 2^5 is 32 which is a good lower bounds
7030
- while (Math.pow(2, power) < height) {
7031
- power += 1;
7032
- }
7033
- height = Math.pow(2, power);
7034
- power = 10; // start with 5 because 2^10 is 1024 which is a good lower bounds
7035
- while (Math.pow(2, power) < width) {
7036
- power += 1;
7037
- }
7038
- width = Math.pow(2, power);
7039
- this.atlas = atlas;
7040
- // the map that this layer is used for, since this get rendered
7041
- // as a single object we cannot for any reason reuse a layer for a
7042
- // different map.
7043
- this.map = map;
7044
- this.canvasWidth = width;
7045
- this.canvasHeight = height;
7046
- // create a canvas and context to draw the text labels into
7047
- this.canvas = document.createElement('canvas');
7048
- this.canvas.width = this.canvasWidth;
7049
- this.canvas.height = this.canvasHeight;
7050
- this.context = this.canvas.getContext('2d');
7051
- this.context.textBaseline = 'bottom';
7052
- // the list of free space in the canvas that we can put blocks int
7053
- this.spaces = [new Slot(0, 0, this.canvas.width, this.canvas.height)];
7054
- // an array of free slots for when labels get destroyed
7055
- this.free = [];
7056
- // an array of labels that are contained
7057
- this.labels = [];
7058
- this.count = CANVAS_MAX_LABELS;
7059
- // Create buffers to hold the binary data for rendering data
7060
- let origin = new Float32Array(this.count * 3);
7061
- let size = new Float32Array(this.count * 2);
7062
- let rotation = new Float32Array(this.count);
7063
- let uv = new Float32Array(this.count * 4);
7064
- let color = new Float32Array(this.count * 3);
7065
- this.uv = new InstancedBufferAttribute(uv, 4, false, 1);
7066
- this.uv.set(uv);
7067
- this.uv.needsUpdate = true;
7068
- this.origin = new InstancedBufferAttribute(origin, 3, false, 1);
7069
- this.origin.set(origin);
7070
- this.origin.needsUpdate = true;
7071
- this.size = new InstancedBufferAttribute(size, 2, false, 1);
7072
- this.size.set(size);
7073
- this.size.needsUpdate = true;
7074
- this.rotation = new InstancedBufferAttribute(rotation, 1, false, 1);
7075
- this.rotation.set(rotation);
7076
- this.rotation.needsUpdate = true;
7077
- this.color = new InstancedBufferAttribute(color, 3, true, 1);
7078
- this.color.set(color);
7079
- this.color.needsUpdate = true;
7080
- // these will probably always be zero, but we want to make sure anyhow
7081
- // a none zero sized element might render with an invalid texture
7082
- // coordinate which would be pretty bad.
7083
- for (let i = 0; i < this.count; i++) {
7084
- this.zero(i);
7085
- }
7086
- // create the polygon, this is just a square 1x1
7087
- let positions = new Float32Array([0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0]);
7088
- let indicies = [0, 3, 1, 3, 2, 1];
7089
- // Setup the geometry buffer
7090
- this.geometry = new InstancedBufferGeometry();
7091
- this.geometry.setIndex(indicies);
7092
- this.geometry.setAttribute('position', new BufferAttribute(positions, 3));
7093
- this.geometry.setAttribute('size', this.size);
7094
- this.geometry.setAttribute('origin', this.origin);
7095
- this.geometry.setAttribute('rotation', this.rotation);
7096
- this.geometry.setAttribute('uv', this.uv);
7097
- this.geometry.setAttribute('color', this.color);
7098
- // Three allows for wrapping of a canvas into a texture, neat
7099
- this.texture = new CanvasTexture(this.canvas);
7100
- this.texture.anisotropy = 16;
7101
- // create the shader
7102
- this.material = new RawShaderMaterial();
7103
- this.material.vertexShader = LabelAtlasVertex;
7104
- this.material.fragmentShader = LabelAtlasFragment;
7105
- this.material.uniforms = {
7106
- textureLabel: { value: this.texture },
7107
- };
7108
- // marked as transparent because the we alpha blend the textures
7109
- this.material.transparent = true;
7110
- // create the mesh
7111
- this.mesh = new Mesh(this.geometry, this.material);
7112
- // create a 3D object for the scene graph containing the mesh
7113
- this.obj = new Object3D();
7114
- this.obj.add(this.mesh);
7115
- // This logic uses a custom vertex shader to position and size the
7116
- // actual vertices. This has a side effect of the renderer not actually
7117
- // knowing the size of the object. Disabling the frustum culling
7118
- // prevents this.
7119
- this.obj.frustumCulled = false;
7120
- this.mesh.frustumCulled = false;
7121
- // add to the map
7122
- this.map.add(this.obj);
7123
- }
7124
- // return the number of bytes left in this layer
7125
- space() {
7126
- return this.spaces.map(x => x.width * x.height).reduce((x, y) => x + y);
7127
- }
7128
- // set a index of the arrays to zero.
7129
- zero(index) {
7130
- this.origin.array[index * 3 + 0] = 0;
7131
- this.origin.array[index * 3 + 1] = 0;
7132
- this.origin.array[index * 3 + 2] = 0;
7133
- this.size.array[index * 2 + 0] = 0;
7134
- this.size.array[index * 2 + 1] = 0;
7135
- this.rotation.array[index] = 0;
7136
- this.uv.array[index * 4 + 0] = 0;
7137
- this.uv.array[index * 4 + 1] = 0;
7138
- this.uv.array[index * 4 + 2] = 0;
7139
- this.uv.array[index * 4 + 3] = 0;
7140
- this.color.array[index * 3 + 0] = 0;
7141
- this.color.array[index * 3 + 1] = 0;
7142
- this.color.array[index * 3 + 2] = 0;
7143
- this.origin.needsUpdate = true;
7144
- this.size.needsUpdate = true;
7145
- this.rotation.needsUpdate = true;
7146
- this.uv.needsUpdate = true;
7147
- this.color.needsUpdate = true;
7148
- }
7149
- // find space for a label, if there is no space this will return false.
7150
- // if space was found, the label is added to this Atlas and the label index
7151
- // is set.
7152
- //
7153
- // Note: The label is added for house keeping, the values of the buffers
7154
- // have not been configured yet. And the text has not been rendered
7155
- findSpace(label) {
7156
- // even if there is space, there is no space left in the draw buffers
7157
- if (this.free.length != 0 && this.labels.length == this.count) {
7158
- return false;
7159
- }
7160
- // search 'spaces' for the entry that fits the glyph best. 'best' means
7161
- // least amount of wasted space, ideally 0.
7162
- let best = -1;
7163
- for (let i = 0; i < this.spaces.length; i++) {
7164
- let space = this.spaces[i].leftOverSpace(label);
7165
- if (space === null) {
7166
- continue;
7167
- }
7168
- if (best === -1 || this.spaces[i].leftOverSpace(label) < space) {
7169
- best = i;
7170
- }
7171
- }
7172
- if (best !== -1) {
7173
- this.spaces[best].split(label, this.spaces, best);
7174
- label.layer = this;
7175
- // Append an element to an array if there no free slots,
7176
- // if there are free slots we reuse the space instead.
7177
- if (this.free.length === 0) {
7178
- label.index = this.labels.length;
7179
- this.labels.push(label);
7180
- }
7181
- else {
7182
- label.index = this.free.pop();
7183
- this.labels[label.index] = label;
7184
- }
7185
- }
7186
- // returns true if there was space found.
7187
- return best != -1;
7188
- }
7189
- // remove this label from the internal contents of this atlas.
7190
- remove(label) {
7191
- if (label.index === undefined) {
7192
- return;
7193
- }
7194
- // remove this object from the vertex array.
7195
- this.zero(label.index);
7196
- // clear the pixel memory to avoid reuse of this area getting messed up
7197
- this.context.clearRect(label.x, label.y, label.width, label.height);
7198
- // add the free spaces to the free list
7199
- this.spaces.push(new Slot(label.x, label.y, label.width, label.height));
7200
- this.free.push(label.index);
7201
- this.labels[label.index] = null;
7202
- // check to see if there is actually any labels left
7203
- let empty = true;
7204
- for (let iLabel of this.labels) {
7205
- empty = empty && iLabel != null;
7206
- if (!empty) {
7207
- break;
7208
- }
7209
- }
7210
- // if there are no labels left, remove this layer
7211
- if (empty) {
7212
- this.atlas.removeLayer(this);
7213
- }
7214
- }
7215
- // render a label to the canvas
7216
- render(label) {
7217
- this.context.save();
7218
- this.context.rect(label.x + 1, label.y + 1, label.width - 1 * 2, label.height - 1 * 2);
7219
- this.context.clip();
7220
- this.context.font = label.size + 'px ' + label.font;
7221
- for (let line of label.lines) {
7222
- this.context.fillText(line.text, line.x + label.x + LABEL_PADDING, line.y + label.y + line.height + LABEL_PADDING);
7223
- }
7224
- this.texture.needsUpdate = true;
7225
- this.context.restore();
7226
- }
7227
- // Reset canvas and re-render all labels
7228
- onWebGLContextRestored() {
7229
- this.canvas = document.createElement('canvas');
7230
- this.canvas.width = this.canvasWidth;
7231
- this.canvas.height = this.canvasHeight;
7232
- this.context = this.canvas.getContext('2d');
7233
- this.context.textBaseline = 'bottom';
7234
- this.texture = new CanvasTexture(this.canvas);
7235
- this.texture.anisotropy = 16;
7236
- this.material.uniforms.textureLabel.value = this.texture;
7237
- for (let label of this.labels) {
7238
- if (label != null) {
7239
- this.render(label);
7240
- }
7241
- }
7242
- }
7243
- // free all resources associated with this layer
7244
- dispose() {
7245
- this.geometry.dispose();
7246
- this.material.dispose();
7247
- this.texture.dispose();
7248
- }
7249
- }
7250
- class Atlas {
7251
- constructor() {
7252
- this.layers = [];
7253
- this.canvas = document.createElement('canvas');
7254
- this.context = this.canvas.getContext('2d');
7255
- }
7256
- /// adds label to the atlas
7257
- addLabel(map, lines, font, size, scope, pixelsPerScale) {
7258
- // get the max width / height
7259
- let height = 0;
7260
- let width = 0;
7261
- for (let line of lines) {
7262
- width = Math.max(Math.ceil(line.width + line.x), width);
7263
- height = Math.max(Math.ceil(line.height + line.y), height);
7264
- }
7265
- let label = new Label();
7266
- label.map = map;
7267
- label.width = width + LABEL_PADDING * 2;
7268
- label.height = height + LABEL_PADDING * 2;
7269
- label.lines = lines;
7270
- label.font = font;
7271
- label.size = size;
7272
- label.canvasBounds = scope.canvasBounds;
7273
- label.margin = scope.margin;
7274
- label.heightMargin = scope.heightMargin;
7275
- label.pixelsPerMu = pixelsPerScale;
7276
- label.z = scope.height;
7277
- label.view = scope;
7278
- label.color = scope.color;
7279
- let layer;
7280
- // find a slot to place this label in
7281
- for (let iLayer of this.layers) {
7282
- layer = iLayer;
7283
- if (layer.map !== map) {
7284
- layer = null;
7285
- continue;
7286
- }
7287
- if (layer.findSpace(label)) {
7288
- break;
7289
- }
7290
- layer = null;
7291
- }
7292
- // no space was found, create a new layer
7293
- if (layer == null) {
7294
- layer = new AtlasLayer(this, map, label.width, label.height);
7295
- this.layers.push(layer);
7296
- if (!layer.findSpace(label)) {
7297
- return;
7298
- }
7299
- // order the layers such that the least populated ones are first.
7300
- this.layers.sort((a, b) => b.space() - a.space());
7301
- }
7302
- layer.render(label);
7303
- label.layout(map.mapClass, layer.origin, layer.size, layer.rotation, layer.uv, layer.color);
7304
- label.layer = layer;
7305
- return label;
7306
- }
7307
- // delete a layer from the list of layers
7308
- removeLayer(layer) {
7309
- let index = this.layers.indexOf(layer);
7310
- if (index != -1) {
7311
- this.layers.splice(index, 1);
7312
- }
7313
- }
7314
- // measure the size of a element with a given font and size
7315
- measure(text, font, size) {
7316
- this.context.font = size + 'px ' + font;
7317
- return this.context.measureText(text);
7318
- }
7319
- // Retore all layers
7320
- onWebGLContextRestored() {
7321
- for (let layer of this.layers) {
7322
- layer.onWebGLContextRestored();
7323
- }
7324
- }
7325
- // cleans up all the layers of the Atlas
7326
- dispose() {
7327
- for (let layer of this.layers) {
7328
- layer.dispose();
7329
- }
7330
- }
7331
- }
7332
- class FlatLabel {
7333
- constructor(options, venue, mapObject, DEFAULT_FONT, polygonMeshesById, textLabelsByPolygonId, mapView, scope, atlas) {
7334
- let polygon = getObject(options.polygon, venue.polygons);
7335
- this.id = polygon.id;
7336
- this.text = (options.shortText || options.text).trim();
7337
- this.stateText = options.stateText ? ' (' + options.stateText + ')' : '';
7338
- this.fullText = this.stateText ? this.text + this.stateText : this.text;
7339
- this.font = options.font || DEFAULT_FONT;
7340
- this.atlas = atlas;
7341
- this.canvasBounds = options.canvasBounds || polygon.canvasBounds;
7342
- this.mapScale = mapObject.getMapScale() || 1;
7343
- this.margin = (options.margin || DEFAULT_LABEL_SIZING.MARGIN) * this.mapScale;
7344
- this.heightMargin = (options.heightMargin || DEFAULT_LABEL_SIZING.HEIGHT_MARGIN) * this.mapScale;
7345
- this.scaleMin = options.scaleMin || 0.25;
7346
- this.scaleStep = options.scaleStep > 0 ? options.scaleStep : 0.25;
7347
- this.multiline = options.multiline == undefined ? true : options.multiline;
7348
- this.height = Number(options.height) || null;
7349
- this.polygonMeshesById = polygonMeshesById;
7350
- this.polyId = options.polygonId || getObjectId(options.polygon, venue.polygons);
7351
- this.map = mapObject;
7352
- this.color = new Color(options.color || scope.colors.text || '#000000');
7353
- this.hoverColor = new Color(options.hoverColor || this.color);
7354
- this.baseColor = this.color;
7355
- this.hideOnCreate = false;
7356
- this.hoverLabelText = options.text.trim();
7357
- this.fontSize = options.fontSize * this.mapScale || options.size * SIZE_IN_METERS || FONT_SIZE;
7358
- this.hoverLabelMode = options.hoverLabelMode3D || HoverLabel.MODES.ALL;
7359
- this.hoverLabelClass = options.hoverLabelClass || 'mMapviewHoverLabel';
7360
- this.showHoverLabel =
7361
- this.hoverLabelMode == HoverLabel.MODES.ALL || this.hoverLabelMode == HoverLabel.MODES.NO_TEXT_LABEL_ONLY;
7362
- this.hoverIfLabelFails =
7363
- this.hoverLabelMode == HoverLabel.MODES.ALL || this.hoverLabelMode == HoverLabel.MODES.NO_TEXT_LABEL_ONLY;
7364
- this.textLabelsByPolygonId = textLabelsByPolygonId;
7365
- this.map.textObjects.push(this);
7366
- }
7367
- create() {
7368
- let scope = this;
7369
- // This could get killed before we even get to creation
7370
- if (this.doNotCreate) {
7371
- return;
7372
- }
7373
- if (scope.canvasBounds == null) {
7374
- scope.invalid = true;
7375
- scope.message = "No Canvas Bounds. Can't draw text '" + scope.text + "'";
7376
- scope.showHoverLabel = scope.hoverIfLabelFails;
7377
- return;
7378
- }
7379
- scope.showHoverLabel = this.hoverLabelMode == HoverLabel.MODES.ALL;
7380
- let muPerPixel = METERS_PER_PIXEL * this.mapScale;
7381
- let pixelsPerMu = PIXELS_PER_METER / this.mapScale;
7382
- let labelsToAttempt = [this.text];
7383
- if (this.stateText.length)
7384
- labelsToAttempt.unshift(this.fullText);
7385
- for (let attempt of labelsToAttempt) {
7386
- // attempt to layout the words
7387
- let words = attempt.split(' ').map(function (word) {
7388
- return {
7389
- width: scope.atlas.measure(word, scope.font, scope.fontSize).width,
7390
- word: word,
7391
- };
7392
- });
7393
- // convert the canvas bounds to an area minus in pixels
7394
- let availableWidth = (this.canvasBounds.maxWidth - this.margin * 2) * pixelsPerMu;
7395
- let availableHeight = (this.canvasBounds.maxHeight - this.heightMargin * 2) * pixelsPerMu;
7396
- let maxShrinkSteps = (1 - this.scaleMin) / this.scaleStep;
7397
- let minimumShrinkSteps = 0;
7398
- let spaceWidth = this.atlas.measure(' ', this.font, this.fontSize).width;
7399
- let lineHeight = getTextHeight(this.fontSize + 'px ' + this.font);
7400
- for (let i = 0, iLen = words.length; i < iLen; i++) {
7401
- let width = words[i].width;
7402
- let wShrinkSteps = Math.ceil((width - availableWidth) / (width * this.scaleStep));
7403
- minimumShrinkSteps = Math.max(wShrinkSteps, minimumShrinkSteps);
7404
- }
7405
- if (this.height == null) {
7406
- if (this.polygonMeshesById[this.polyId]) {
7407
- let target = this.polygonMeshesById[this.polyId];
7408
- if (!target.geometry.boundingBox) {
7409
- target.geometry.computeBoundingBox();
7410
- }
7411
- this.height = target.geometry.boundingBox.max.z + target.position.z;
7412
- }
7413
- else {
7414
- this.height = 0;
7415
- }
7416
- }
7417
- var lines;
7418
- var shrinkStep;
7419
- var currentScale;
7420
- let scaleStep = this.scaleStep;
7421
- shrink: for (shrinkStep = minimumShrinkSteps; shrinkStep <= maxShrinkSteps; shrinkStep++) {
7422
- currentScale = 1 - shrinkStep * scaleStep;
7423
- let thisLineWidth = words[0].width * currentScale;
7424
- lines = [
7425
- {
7426
- text: words[0].word,
7427
- width: thisLineWidth,
7428
- height: lineHeight * currentScale,
7429
- x: 0,
7430
- y: 0,
7431
- },
7432
- ];
7433
- for (let iWordSize = 1; iWordSize < words.length; iWordSize++) {
7434
- let wordSize = words[iWordSize];
7435
- let spaceForNextWord = (wordSize.width + spaceWidth) * currentScale;
7436
- if (thisLineWidth + spaceForNextWord < availableWidth) {
7437
- // Add to line
7438
- lines[lines.length - 1].text += ' ' + wordSize.word;
7439
- lines[lines.length - 1].width += spaceForNextWord;
7440
- thisLineWidth += spaceForNextWord;
7441
- }
7442
- else {
7443
- // New line
7444
- if (!this.multiline)
7445
- continue shrink;
7446
- lines[lines.length - 1].width = thisLineWidth;
7447
- thisLineWidth = wordSize.width * currentScale;
7448
- lines.push({
7449
- text: wordSize.word,
7450
- width: thisLineWidth,
7451
- height: lineHeight * currentScale,
7452
- x: 0,
7453
- y: lines[lines.length - 1].height + lines[lines.length - 1].y,
7454
- });
7455
- }
7456
- }
7457
- let requiredHeight = lines.length * lineHeight * currentScale;
7458
- if (requiredHeight <= availableHeight) {
7459
- this.invalid = false;
7460
- break;
7461
- }
7462
- }
7463
- if (this.invalid == false)
7464
- break;
7465
- }
7466
- if (this.invalid == false) {
7467
- // center each line
7468
- let lineTotalWidth = lines.map(x => Math.ceil(x.width)).reduce((a, b) => Math.max(a, b));
7469
- for (let line of lines) {
7470
- line.x = (lineTotalWidth - line.width) / 2;
7471
- }
7472
- // lines, font, size, scope, pixelsPerScale
7473
- this.label = this.atlas.addLabel(this.map, lines, this.font, this.fontSize * currentScale, this, muPerPixel);
7474
- }
7475
- this.created = true;
7476
- }
7477
- // eslint-disable-next-line class-methods-use-this
7478
- flipIfNeeded(cameraAngle) {
7479
- // this is done in the shader, so we will not do anything here
7480
- // this function simply exists for parity with 'TextLabel'
7481
- }
7482
- removeSelf(bulk) {
7483
- if (this.created) {
7484
- if (this.label != null) {
7485
- this.label.layer.remove(this.label);
7486
- this.label = null;
7487
- }
7488
- if (!bulk) {
7489
- this.map.textObjects = this.map.textObjects.filter(object => object != this);
7490
- // Delete from textLabelsByPolygonId
7491
- Object.keys(this.textLabelsByPolygonId).forEach(key => {
7492
- if (this.textLabelsByPolygonId[key] == this) {
7493
- delete this.textLabelsByPolygonId[key];
7494
- }
7495
- });
7496
- }
7497
- }
7498
- else {
7499
- this.doNotCreate = true;
7500
- }
7501
- }
7502
- setColor(textColor) {
7503
- const color = new Color(textColor);
7504
- const sameColor = this.color.equals(color);
7505
- const withLabel = Boolean(this.label?.layer);
7506
- this.color = color;
7507
- if (!sameColor && withLabel) {
7508
- this.label.color = this.color;
7509
- this.label.layout(this.map.mapClass, this.label.layer.origin, this.label.layer.size, this.label.layer.rotation, this.label.layer.uv, this.label.layer.color);
7510
- }
7511
- }
7512
- setHoverColor = color => {
7513
- this.hoverColor = new Color(color);
7514
- };
7515
- clearColor() {
7516
- this.setColor(this.baseColor);
7517
- }
7518
- hide() {
7519
- if (this.label) {
7520
- this.label.layer.zero(this.label.index);
7521
- }
7522
- else {
7523
- this.hideOnCreate = true;
7524
- }
7525
- }
7526
- show() {
7527
- if (this.label) {
7528
- let layer = this.label.layer;
7529
- let map = this.map;
7530
- let label = this.label;
7531
- label.layout(map.mapClass, layer.origin, layer.size, layer.rotation, layer.uv, layer.color);
7532
- }
7533
- else {
7534
- this.hideOnCreate = false;
7535
- }
7536
- }
7537
- toString() {
7538
- return this.hoverLabelText;
7539
- }
7540
- }
7541
- let textHeightCache = new Map();
7542
- var getTextHeight = function (font) {
7543
- // check if the font is in the cache because modifying the dom is expensive
7544
- let height = textHeightCache[font];
7545
- if (height != undefined) {
7546
- return height;
7547
- }
7548
- let text = document.createElement('span');
7549
- text.textContent = 'Hg';
7550
- text.style.font = font;
7551
- let block = document.createElement('div');
7552
- block.style.cssText = 'display: inline-block; width: 1px; height: 0px;';
7553
- let div = document.createElement('div');
7554
- div.appendChild(text);
7555
- div.appendChild(block);
7556
- let body = document.body;
7557
- body.appendChild(div);
7558
- try {
7559
- var bounding = text.getBoundingClientRect();
7560
- }
7561
- finally {
7562
- div.remove();
7563
- }
7564
- // add the element to the cache
7565
- if (bounding.height != undefined) {
7566
- textHeightCache[font] = bounding.height;
7567
- height = bounding.height;
7568
- }
7569
- return height;
7570
- };
7571
- export { Atlas, FlatLabel };
7572
-
7573
6930
  declare module '@mappedin/mappedin-js/renderer/internal/Mappedin.HoverLabel' {
7574
6931
  export default HoverLabel;
7575
6932
  /**
@@ -8241,2267 +7598,6 @@ declare module '@mappedin/mappedin-js/renderer/internal/Mappedin.BlueDot/Mappedi
8241
7598
  export default BlueDotUI;
8242
7599
  }
8243
7600
 
8244
- import { Euler, EventDispatcher, MOUSE, PlaneGeometry, Matrix4, MeshBasicMaterial, Mesh, Raycaster, Vector2, Vector3, Clock, } from 'three';
8245
- import TouchAnchor from '@mappedin/mappedin-js/renderer/internal/Mappedin.CameraControls.TouchAnchor';
8246
- import InputSet from '@mappedin/mappedin-js/renderer/internal/Mappedin.CameraControls.InputSet';
8247
- import { getProjectionScaleFactor, throttle, debounce } from '@mappedin/mappedin-js/renderer/internal/utils';
8248
- import { VIEW_STATE } from '@mappedin/mappedin-js/renderer/internal/Mappedin.MultiFloorView';
8249
- import { DebugUICheckbox } from '@mappedin/mappedin-js/renderer/internal/Mappedin.DebugUIControl';
8250
- import TWEEN from '@tweenjs/tween.js';
8251
- const CAMERA_CLIPPING_RADIUS = 10000;
8252
- const CAMERA_CONTROL_OPTIONS = {
8253
- chain: 'chain',
8254
- cancel: 'cancel',
8255
- };
8256
- const SCROLL_THROTTLE_MS = 100;
8257
- const dispatcher = new EventDispatcher();
8258
- /**
8259
- * The advanced, manual camera controls for {{#crossLink "MapView"}}{{/crossLink}}. You probably don't need to use this at all, instead relying on the MapView's {{#crossLink "MapView/focusOn:method"}}{{/crossLink}}, {{#crossLink "MapView/resetCamera:method"}}{{/crossLink}} and built in touch/mouse controls.
8260
- * This class will let you do things like change the min/max zoom, tilt, and pan, attach to camera events, and move/animate the camera to specifc points.
8261
- *
8262
- * The camera works by setting an anchor point on the scene at ground level and pointing the camera at it. {{#crossLink "CameraControls/zoom:method"}}{{/crossLink}} controls how far the camera is from the anchor, and {{#crossLink "CameraControls/tilt:method"}}{{/crossLink}}/{{#crossLink "CameraControls/rotate:method"}}{{/crossLink}} controls the angle the camera is rotated about it.
8263
- * The camera will always be pointed directly at the anchor point. {{#crossLink "CameraControls/pan:method"}}{{/crossLink}} or {{#crossLink "CameraControls/setPosition:method"}}{{/crossLink}} will move that anchor around on the 2D ground plane.
8264
- *
8265
- * Created for you automatically with a MapView, don't re-create yourself.
8266
- * @type {any}
8267
- *
8268
- * @class CameraControls
8269
- */
8270
- let CameraControls = function (camera, canvas, scene, core) {
8271
- /**
8272
- * Dispatcher
8273
- */
8274
- this.dispatcher = dispatcher;
8275
- this.camera = camera;
8276
- this.canvas = canvas;
8277
- this.elevation = camera.parent;
8278
- this.orbit = this.elevation.parent;
8279
- let intersection;
8280
- let TWO_PI = Math.PI * 2;
8281
- // @TODO: figure out a zero-magic solution
8282
- const CAMERA_ZRANGE = 100;
8283
- /**
8284
- * Factor that controls how fast zooming in and out happens in response to mouse wheel events
8285
- *
8286
- * @property zoomSpeed {Float}
8287
- * @default 5.0
8288
- */
8289
- this.zoomSpeed = 5.0;
8290
- /**
8291
- * Factor to multiple mouse movement by to get tilt/rotation.
8292
- *
8293
- * @property rotateSpeed {Float}
8294
- * @default 100
8295
- */
8296
- this.rotateSpeed = 100;
8297
- /**
8298
- * Disable or re-enable user input.
8299
- *
8300
- * @property enabled {Boolean}
8301
- * @default true
8302
- */
8303
- this.enabled = true;
8304
- /**
8305
- * Disable or re-enable user zoom.
8306
- *
8307
- * @property enableZoom {Boolean}
8308
- * @default true
8309
- */
8310
- this.enableZoom = true;
8311
- /**
8312
- * Disable or re-enable user pan.
8313
- *
8314
- * @property enablePan {Boolean}
8315
- * @default true
8316
- */
8317
- this.enablePan = true;
8318
- /**
8319
- * Disable or re-enable user pedestal.
8320
- *
8321
- * @property enablePedestal {Boolean}
8322
- * @default false
8323
- */
8324
- this.enablePedestal = false;
8325
- /**
8326
- * Max amount to allow scrolling maps down
8327
- * (In Z-axis units, at the origin, down is positive)
8328
- *
8329
- * @property maxPedestal {Number}
8330
- * @default Infinity
8331
- */
8332
- this.maxPedestal = Infinity;
8333
- /**
8334
- * Max amount to allow scrolling maps up
8335
- * (In Z-axis units, at the origin, up is negative)
8336
- *
8337
- * @property minPedestal {Number}
8338
- * @default -Infinity
8339
- */
8340
- this.minPedestal = -Infinity;
8341
- /**
8342
- * Disable or re-enable user tilt/rotation.
8343
- *
8344
- * @property enableRotate {Boolean}
8345
- * @default true
8346
- */
8347
- this.enableRotate = true;
8348
- /**
8349
- * This is actually the minium distance the camera can get from it's anchor on the ground. May be worth changing if your map has very tall buildings to avoid the camera clipping through them.
8350
- *
8351
- * @property minZoom {Number}
8352
- * @default 375
8353
- */
8354
- this.minZoom = 375;
8355
- /**
8356
- * Maximum distance the camera can get from it's anchor on the ground. Setting this too high will result in parts of the map falling out of the camera's clipping plane and disappearing.
8357
- *
8358
- * @property maxZoom {Number}
8359
- * @default 10000
8360
- */
8361
- this.maxZoom = 10000;
8362
- /**
8363
- * Initial min zoom; zoom cannot be restricted beyond this.
8364
- *
8365
- * @property initialMinZoom {Number}
8366
- * @default 375
8367
- */
8368
- this.initialMinZoom = 375;
8369
- /**
8370
- * Initial max zoom; zoom cannot be restricted beyond this.
8371
- *
8372
- * @property initialMaxZoom {Number}
8373
- * @default 10000
8374
- */
8375
- this.initialMaxZoom = 10000;
8376
- /**
8377
- * ignoreZoomLimits; use with caution for special effects
8378
- * @default false
8379
- */
8380
- this.ignoreZoomLimits = false;
8381
- // Internal Documentation
8382
- this.minExpandedZoom = 375;
8383
- // Internal Documentation
8384
- this.maxExpandedZoom = 100000;
8385
- /**
8386
- * Multiplier for min and max zoom, for convenience.
8387
- *
8388
- * @property zoomFactor {Number}
8389
- * @default 1
8390
- */
8391
- this.zoomFactor = 1;
8392
- /**
8393
- * Constrains the camera from panning to far away from the scene. It's set automatically based on the size of the map.
8394
- * If you want to change anything, you probably want to change the margin property, which is the factor the min and max in
8395
- * each dimension are multiplied by to give the true bounds. For example, on a truely huge venue a 1.25 margin could get you
8396
- * way out into space when zoomed in.
8397
- *
8398
- * @property panBounds {Object}
8399
- @property panBounds.margin {Number} The factor the multiply the size of the geometery by to give the true camera bounds.
8400
- @property panBounds.min {Object} An x, y pair representing the bounds of one corner of the map.
8401
- @property panBounds.max {Object} An x, y pair representing the bounds of the other corner of the map.
8402
- */
8403
- this.panBounds = {
8404
- margin: 1.25,
8405
- min: {
8406
- x: Infinity,
8407
- y: Infinity,
8408
- },
8409
- max: {
8410
- x: -Infinity,
8411
- y: -Infinity,
8412
- },
8413
- center: {
8414
- x: 0,
8415
- y: 0,
8416
- },
8417
- radius: 200,
8418
- };
8419
- /**
8420
- * Minium camera tilt, in radians. If it's anything other than 0, you won't be able to look at the venue from the top down perspective.
8421
- *
8422
- * @property minTilt {Number}
8423
- * @default 0.0
8424
- */
8425
- this.minTilt = 0.0;
8426
- /**
8427
- * Minium camera tilt, in radians. If you set it too high, the camera will be able to tilt down through the geometery of the scene, which will produce clipping issues.
8428
- *
8429
- * @property maxTilt {Number}
8430
- * @default 1.2
8431
- */
8432
- this.maxTilt = 1.2;
8433
- /**
8434
- * If you would really prefer to pan with the right mouse button and tilt/rotate with the left, you can swap the values here to achieve that.
8435
- *
8436
- * @property mouseButtons {Object}
8437
- @property mouseButtons.ORBIT=MOUSE.RIGHT {MOUSE} The button to use for tilt/rotation. Defaults to `MOUSE.RIGHT`.
8438
- @property mouseButtons.ZOOM=MOUSE.MIDDLE {MOUSE} The button to use for zoom behaviour. Don't change this.
8439
- @property mouseButtons.PAN=MOUSE.LEFT {MOUSE} The button to use for panning the camera. Defaults to `MOUSE.LEFT`.
8440
- */
8441
- this.mouseButtons = {
8442
- ORBIT: MOUSE.RIGHT,
8443
- ZOOM: MOUSE.MIDDLE,
8444
- PAN: MOUSE.LEFT,
8445
- };
8446
- let scope = this;
8447
- let cameraPlaneGeometery = new PlaneGeometry(1000000, 1000000); // Should set this to be the map bounds later
8448
- let cameraPlaneMaterial = new MeshBasicMaterial({
8449
- color: 0x000000,
8450
- visible: false,
8451
- });
8452
- let cameraPlane = new Mesh(cameraPlaneGeometery, cameraPlaneMaterial);
8453
- let raycaster = new Raycaster();
8454
- scene.add(cameraPlane);
8455
- let STATE = {
8456
- NONE: -1,
8457
- ROTATE: 0,
8458
- DOLLY: 1,
8459
- PAN: 2,
8460
- WHEEL_ZOOM: 3,
8461
- TOUCH_TILT: 4,
8462
- TOUCH_DOLLY: 5,
8463
- TOUCH_PAN: 6,
8464
- MULTI: 7,
8465
- PEDESTAL: 8,
8466
- TOUCH_PEDESTAL: 9,
8467
- };
8468
- let state = STATE.NONE;
8469
- let mouse = new Vector2();
8470
- let touches = [];
8471
- let touchOrigin = { offsetLeft: 0, offsetTop: 0 };
8472
- let rotateStart = new Vector2();
8473
- let rotateEnd = new Vector2();
8474
- let rotateDelta = new Vector2();
8475
- let panStart = new Vector2();
8476
- let panCameraStart = new Vector2();
8477
- let panEnd = new Vector2();
8478
- let panDelta = new Vector3();
8479
- let floorAnchor = new Vector3();
8480
- let dollyStart = new Vector2();
8481
- let dollyEnd = new Vector2();
8482
- let dollyDelta = new Vector2();
8483
- let resetZoom = false; // Indicates whether to reset the camera position and zoom level when panning while mouse zooming
8484
- let zoomStart;
8485
- let clock = new Clock(true);
8486
- let lastWheelTime = 0;
8487
- // If true, the controls will not be limited by min and max values.
8488
- let stayInsideBounds = true;
8489
- let WHEEL_ZOOM_MULTIPLIER = 10000;
8490
- // var currentTween = null;
8491
- /**
8492
- * Camera events you can attach a listener to (with `controls.addListener(event, function)`), if you want to do certain things. They'll be fired both by touch events and by functions you can call yourself like pan() and tilt()/
8493
- *
8494
- * @property CAMERA_EVENTS {Object}
8495
- * @final
8496
- @property CAMERA_EVENTS.CHANGE_EVENT {Object} Fired whenever the camera changes.
8497
- @property CAMERA_EVENTS.PAN_START_EVENT {Object} Fired when the camera starts panning.
8498
- @property CAMERA_EVENTS.PAN_END_EVENT {Object} Fired when the camera finishes panning.
8499
- @property CAMERA_EVENTS.ROTATE_START_EVENT {Object} Fired when the camera starts rotating.
8500
- @property CAMERA_EVENTS.ROTATE_END_EVENT {Object} Fired when the camera stops rotating.
8501
- @property CAMERA_EVENTS.ZOOM_START_EVENT {Object} Fired when the camera starts zooming.
8502
- @property CAMERA_EVENTS.ZOOM_END_EVENT {Object} Fired when the camera finishes zooming.
8503
- @property CAMERA_EVENTS.MULTI_START_EVENT {Object} Fired when the camera starts animating or you called setMulti. It means any one or more of pan, tilt, rotate and zoom could be changing. The individual pan/tilt/rotate/zoom events will NOT be fired.
8504
- @property CAMERA_EVENTS.MULTI_END_EVNT {Object} Fired when the camera stops animating, or has finished the setMulti call.
8505
- */
8506
- this.CAMERA_EVENTS = {};
8507
- this.CAMERA_EVENTS.CHANGE_EVENT = { type: 'change' };
8508
- this.CAMERA_EVENTS.PAN_START_EVENT = { type: 'panStart' };
8509
- this.CAMERA_EVENTS.PAN_END_EVENT = { type: 'panEnd' };
8510
- this.CAMERA_EVENTS.PEDESTAL_START_EVENT = { type: 'pedestalStart' };
8511
- this.CAMERA_EVENTS.PEDESTAL_CHANGE_EVENT = { type: 'pedestalChange' };
8512
- this.CAMERA_EVENTS.PEDESTAL_END_EVENT = { type: 'pedestalEnd' };
8513
- this.CAMERA_EVENTS.ROTATE_START_EVENT = { type: 'rotateStart' };
8514
- this.CAMERA_EVENTS.ROTATE_END_EVENT = { type: 'rotateEnd' };
8515
- this.CAMERA_EVENTS.ZOOM_START_EVENT = { type: 'zoomStart' };
8516
- this.CAMERA_EVENTS.ZOOM_END_EVENT = { type: 'zoomEnd' };
8517
- this.CAMERA_EVENTS.MULTI_START_EVENT = { type: 'multiStart' };
8518
- this.CAMERA_EVENTS.MULTI_END_EVENT = { type: 'multiEnd' };
8519
- this.CAMERA_EVENTS.MULTI_CANCEL_EVENT = { type: 'multiCancel' };
8520
- this.INTERACTION_EVENTS = {};
8521
- this.INTERACTION_EVENTS.USER_PAN_START_EVENT = { type: 'userPanStart' };
8522
- this.INTERACTION_EVENTS.USER_PEDESTAL_START_EVENT = {
8523
- type: 'userPedestalStart',
8524
- };
8525
- this.INTERACTION_EVENTS.USER_ROTATE_START_EVENT = { type: 'userRotateStart' };
8526
- this.INTERACTION_EVENTS.USER_DOLLY_START_EVENT = { type: 'userDollyStart' };
8527
- this.INTERACTION_EVENTS.USER_ZOOM_START_EVENT = { type: 'userZoomStart' };
8528
- this.INTERACTION_EVENTS.USER_TILT_START_EVENT = { type: 'userTiltStart' };
8529
- this.INTERACTION_EVENTS.USER_PAN_END_EVENT = { type: 'userPanEnd' };
8530
- this.INTERACTION_EVENTS.USER_PEDESTAL_END_EVENT = { type: 'userPedestalEnd' };
8531
- this.INTERACTION_EVENTS.USER_ROTATE_END_EVENT = { type: 'userRotateEnd' };
8532
- this.INTERACTION_EVENTS.USER_DOLLY_END_EVENT = { type: 'userDollyEnd' };
8533
- this.INTERACTION_EVENTS.USER_ZOOM_END_EVENT = { type: 'userZoomEnd' };
8534
- this.INTERACTION_EVENTS.USER_TILT_END_EVENT = { type: 'userTiltEnd' };
8535
- this.UPDATE_EVENTS = {};
8536
- this.UPDATE_EVENTS.POSITION_UPDATED_EVENT = { type: 'positionUpdated' };
8537
- this.UPDATE_EVENTS.ZOOM_UPDATED_EVENT = { type: 'zoomUpdated' };
8538
- this.UPDATE_EVENTS.TILT_UPDATED_EVENT = { type: 'tiltUpdated' };
8539
- this.UPDATE_EVENTS.ROTATION_UPDATED_EVENT = { type: 'rotationUpdated' };
8540
- let userInteracting = false;
8541
- const setUserInteracting = () => {
8542
- userInteracting = true;
8543
- };
8544
- const unsetUserInteracting = () => {
8545
- userInteracting = false;
8546
- };
8547
- for (let e in this.INTERACTION_EVENTS) {
8548
- const event = this.INTERACTION_EVENTS[e];
8549
- if (event.type.endsWith('Start')) {
8550
- scope.dispatcher.addEventListener(event.type, setUserInteracting);
8551
- }
8552
- }
8553
- for (let e in this.INTERACTION_EVENTS) {
8554
- const event = this.INTERACTION_EVENTS[e];
8555
- if (event.type.endsWith('End')) {
8556
- scope.dispatcher.addEventListener(event.type, unsetUserInteracting);
8557
- }
8558
- }
8559
- const dispatchPedestalChangeEvent = throttle(newZ => {
8560
- scope.dispatcher.dispatchEvent({
8561
- ...scope.CAMERA_EVENTS.PEDESTAL_CHANGE_EVENT,
8562
- pedestal: newZ,
8563
- scrolledToTop: scope.scrolledToTop,
8564
- scrolledToBottom: scope.scrolledToBottom,
8565
- scrollPercent: scope.scrollPercent,
8566
- });
8567
- }, SCROLL_THROTTLE_MS);
8568
- this.addDebugControls = function (debugUI) {
8569
- debugUI.addDebugControls({
8570
- CameraControls: [
8571
- {
8572
- name: 'Stay Inside Bounds',
8573
- description: 'Ensure the camera stays inside its bounds.\n\n' +
8574
- 'Caution: going too far outside-of-bounds may slow rendering' +
8575
- 'significantly.',
8576
- control: new DebugUICheckbox({
8577
- current: true,
8578
- onValueChanged: stay => {
8579
- stayInsideBounds = stay;
8580
- },
8581
- }),
8582
- },
8583
- ],
8584
- });
8585
- };
8586
- this.removeDebugControls = function (debugUI) {
8587
- debugUI.removeDebugControls({
8588
- CameraControls: ['Stay Inside Bounds'],
8589
- });
8590
- };
8591
- /**
8592
- * Pans the camera right and down from the current position
8593
- *
8594
- * @method pan
8595
- * @param right {Number} The units to move right. Negative will pan left. This is in relation to the global coordinate system, not the current camera rotation.
8596
- * @param down {Number} The units to move down. Negative will pan up. This is in relation to the global coordinate system, not the current camera rotation.
8597
- */
8598
- this.pan = function (right, down) {
8599
- if (isNaN(right) || isNaN(down)) {
8600
- return;
8601
- }
8602
- scope.setPosition(scope.orbit.position.x + right, scope.orbit.position.y + down);
8603
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.POSITION_UPDATED_EVENT);
8604
- };
8605
- /**
8606
- * Sets the camera anchor to a specifc x/y positon, in the global reference frame. 0,0 will be roughly the middle of the map, and panBounds holds the min/max points.
8607
- *
8608
- * @method setPosition
8609
- * @param x {Number} The x position to move the camera to. +x will take you right, from the default camera rotation of 0.
8610
- * @param y {Number} The y position to move the camera to. +y will take you down (towards the viewer) in the default camera rotation of 0.
8611
- */
8612
- this.setPosition = function (x, y) {
8613
- if (isNaN(x) || isNaN(y)) {
8614
- return;
8615
- }
8616
- if (state == STATE.NONE) {
8617
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PAN_START_EVENT);
8618
- }
8619
- const dx = x - scope.panBounds.center.x;
8620
- const dy = y - scope.panBounds.center.y;
8621
- const targetDistanceFromBounds = Math.sqrt(dx * dx + dy * dy);
8622
- if (stayInsideBounds && targetDistanceFromBounds > scope.panBounds.radius) {
8623
- const angle = Math.atan2(dy, dx);
8624
- const target = {
8625
- x: Math.cos(angle) * scope.panBounds.radius + scope.panBounds.center.x,
8626
- y: Math.sin(angle) * scope.panBounds.radius + scope.panBounds.center.y,
8627
- };
8628
- scope.orbit.position.x = target.x;
8629
- scope.orbit.position.y = target.y;
8630
- }
8631
- else {
8632
- scope.orbit.position.x = x;
8633
- scope.orbit.position.y = y;
8634
- }
8635
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.POSITION_UPDATED_EVENT);
8636
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
8637
- if (state == STATE.NONE) {
8638
- scope.dispatcher.dispatchEvent(scope.this.CAMERA_EVENTS.PAN_END_EVENT);
8639
- }
8640
- };
8641
- /**
8642
- * Sets the rotation to a specific orientation, in radians. Mostly useful to orient the map a certain way for a physical directory.
8643
- *
8644
- * @method setRotation
8645
- * @param radians {Number} Absolute rotation to set the camera to, in radians. 0 in the starting point.
8646
- */
8647
- this.setRotation = function (radians) {
8648
- if (isNaN(radians)) {
8649
- return;
8650
- }
8651
- if (state == STATE.NONE) {
8652
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_START_EVENT);
8653
- }
8654
- const newRotation = radians % TWO_PI;
8655
- if (newRotation !== scope.orbit.rotation.z) {
8656
- scope.orbit.rotation.z = newRotation;
8657
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.ROTATION_UPDATED_EVENT);
8658
- }
8659
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
8660
- if (state == STATE.NONE) {
8661
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_END_EVENT);
8662
- }
8663
- };
8664
- /**
8665
- * Rotates the camera a set number of radians relative to the current rotation. Useful for an idle rotation animation.
8666
- *
8667
- * @method rotate
8668
- * @param radians {Number} Number of radians to rotate the camera.
8669
- */
8670
- this.rotate = function (radians) {
8671
- if (isNaN(radians)) {
8672
- return;
8673
- }
8674
- if (state == STATE.NONE) {
8675
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_START_EVENT);
8676
- }
8677
- const newRotation = (scope.orbit.rotation.z + radians) % TWO_PI;
8678
- if (newRotation !== scope.orbit.rotation.z) {
8679
- scope.orbit.rotation.z = newRotation;
8680
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.ROTATION_UPDATED_EVENT);
8681
- }
8682
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
8683
- if (state == STATE.NONE) {
8684
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_END_EVENT);
8685
- }
8686
- };
8687
- /**
8688
- * Sets the tilt to a specific level, in radians. 0 is top down. Bounded by minTilt and maxTilt.
8689
- *
8690
- * @method setTilt
8691
- * @param radians {Number} Tilt to set the camera to, in radians.
8692
- */
8693
- this.setTilt = function (radians) {
8694
- if (isNaN(radians)) {
8695
- return;
8696
- }
8697
- if (state == STATE.NONE) {
8698
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_START_EVENT);
8699
- }
8700
- const newTilt = stayInsideBounds ? Math.max(Math.min(radians, scope.maxTilt), scope.minTilt) : radians;
8701
- if (newTilt !== scope.elevation.rotation.x) {
8702
- scope.elevation.rotation.x = newTilt;
8703
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.TILT_UPDATED_EVENT);
8704
- }
8705
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
8706
- if (state == STATE.NONE) {
8707
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_END_EVENT);
8708
- }
8709
- };
8710
- /**
8711
- * Tilts the camera up or down by some number of radians. Bounded by minTilt and maxTilt.
8712
- *
8713
- * @method tilt
8714
- * @param radians {Number} Number of radians to increase or decrease the current tilt by.
8715
- */
8716
- this.tilt = function (radians) {
8717
- if (isNaN(radians)) {
8718
- return;
8719
- }
8720
- if (state == STATE.NONE) {
8721
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_START_EVENT);
8722
- }
8723
- const newTilt = stayInsideBounds
8724
- ? Math.max(Math.min(radians + scope.getTilt(), scope.maxTilt), scope.minTilt)
8725
- : radians + scope.getTilt();
8726
- if (newTilt !== scope.elevation.rotation.x) {
8727
- scope.elevation.rotation.x = newTilt;
8728
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.TILT_UPDATED_EVENT);
8729
- }
8730
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
8731
- if (state == STATE.NONE) {
8732
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_END_EVENT);
8733
- }
8734
- };
8735
- /**
8736
- * Sets the camera to be a certain distance from the anchor point, along it's tilt and rotation.
8737
- * Keeps it inside minZoom and maxZoom.
8738
- *
8739
- * @method setZoom
8740
- * @param zoom {Number} The distance to set the camera to.
8741
- */
8742
- this.setZoom = function (zoom) {
8743
- if (isNaN(zoom)) {
8744
- return;
8745
- }
8746
- if (state == STATE.NONE) {
8747
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_START_EVENT);
8748
- }
8749
- let z = stayInsideBounds ? Math.min(Math.max(zoom, scope.getZoomScaledMin()), scope.getZoomScaledMax()) : zoom;
8750
- if (z !== scope.camera.position.z) {
8751
- scope.camera.position.z = z;
8752
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.ZOOM_UPDATED_EVENT);
8753
- }
8754
- scope.camera.near = z / CAMERA_ZRANGE;
8755
- scope.camera.far = z * CAMERA_ZRANGE;
8756
- scope.camera.updateProjectionMatrix();
8757
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
8758
- if (state == STATE.NONE) {
8759
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
8760
- }
8761
- };
8762
- /**
8763
- * Moves the camera towards or away from the camera by a set amount. Positive will zoom in (bringing the distance closer to 0).
8764
- *
8765
- * @method zoom
8766
- * @param zoom {Number} The distance to increase or decrease the zoom.
8767
- */
8768
- this.zoom = function (zoomDelta) {
8769
- if (isNaN(zoomDelta)) {
8770
- return;
8771
- }
8772
- if (state == STATE.NONE) {
8773
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_START_EVENT);
8774
- }
8775
- let newZoom = scope.getZoom() - zoomDelta;
8776
- scope.camera.position.z = stayInsideBounds
8777
- ? Math.min(Math.max(newZoom, scope.minZoom * scope.zoomFactor), scope.maxZoom * scope.zoomFactor)
8778
- : newZoom;
8779
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.ZOOM_UPDATED_EVENT);
8780
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
8781
- if (state == STATE.NONE) {
8782
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
8783
- }
8784
- };
8785
- /**
8786
- * Zooms the camera in on the center of the current view.
8787
- *
8788
- * @method zoomIn
8789
- * @param duration
8790
- * @param curve
8791
- * @param callback
8792
- */
8793
- this.zoomIn = function (duration, curve, callback) {
8794
- let zoom = this.getZoom();
8795
- this.animateCamera({
8796
- zoom: zoom / 1.5,
8797
- }, duration, curve, callback);
8798
- };
8799
- /**
8800
- * Zooms the camera out from the center of the current view.
8801
- *
8802
- * @method zoomOut
8803
- * @param duration
8804
- * @param curve
8805
- * @param callback
8806
- */
8807
- this.zoomOut = function (duration, curve, callback) {
8808
- let zoom = this.getZoom();
8809
- this.animateCamera({
8810
- zoom: zoom * 1.5,
8811
- }, duration, curve, callback);
8812
- };
8813
- /**
8814
- * Expands the zoom limits to allow zooming to the specified distance.
8815
- *
8816
- * @method expandZoomLimits
8817
- * @param zoom {Number} The distance to allow zooming to.
8818
- * @param buffer {Number} The factor by which the user should be able to zoom beyond the specified distance.
8819
- */
8820
- this.expandZoomLimits = function (zoom, buffer) {
8821
- if (!buffer)
8822
- buffer = 1.2;
8823
- if (zoom * buffer > scope.maxZoom) {
8824
- scope.maxZoom = Math.min(zoom * buffer, scope.maxExpandedZoom);
8825
- }
8826
- if (zoom / buffer < scope.minZoom) {
8827
- scope.minZoom = Math.max(zoom / buffer, scope.minExpandedZoom);
8828
- }
8829
- };
8830
- /**
8831
- * Restricts the zoom limits, but will not restrict beyond the current zoom level.
8832
- *
8833
- * @method restrictZoomLimits
8834
- * @param zoom {Number} The number to limit zooming to.
8835
- * @param buffer {Number} The factor by which the user should be able to zoom beyond the specified distance.
8836
- */
8837
- this.restrictZoomLimits = function (zoom, buffer) {
8838
- if (!buffer)
8839
- buffer = 1.2;
8840
- if (zoom * buffer > scope.initialMaxZoom && zoom * buffer < scope.maxZoom) {
8841
- scope.maxZoom = Math.max(scope.getZoom(), zoom * buffer);
8842
- }
8843
- if (zoom / buffer < scope.initialMinZoom && zoom / buffer > scope.minZoom) {
8844
- scope.minZoom = Math.min(scope.getZoom(), zoom / buffer);
8845
- }
8846
- };
8847
- /**
8848
- * Returns the current camera position.
8849
- *
8850
- * @method getPosition
8851
- * @return {Object} An {x, y} object of the current camera postion.
8852
- */
8853
- this.getPosition = function () {
8854
- return {
8855
- x: scope.orbit.position.x,
8856
- y: scope.orbit.position.y,
8857
- z: scope.orbit.position.z,
8858
- };
8859
- };
8860
- this.getPedestal = function () {
8861
- return scope.orbit.position.z;
8862
- };
8863
- function setPedestal(z) {
8864
- if (typeof z === 'number' && !isNaN(z)) {
8865
- scope.orbit.position.z = z;
8866
- core.tryRendering();
8867
- }
8868
- }
8869
- Object.defineProperty(this, 'setPedestal', {
8870
- enumerable: false,
8871
- value: setPedestal,
8872
- });
8873
- /**
8874
- * The amount the camera is shifted up/down
8875
- * (corresponds to scroll amount in multifloor mode)
8876
- * 100% == camera is as far up as it will go
8877
- * 0% == camera is as far down as it will go
8878
- */
8879
- Object.defineProperty(this, 'scrollPercent', {
8880
- get() {
8881
- const min = this.minPedestal;
8882
- const max = this.maxPedestal;
8883
- let pedestal = this.getPedestal();
8884
- pedestal = Math.min(pedestal, max);
8885
- pedestal = Math.max(pedestal, min);
8886
- const offset = pedestal - min;
8887
- const portion = offset / (max - min);
8888
- return portion * 100;
8889
- },
8890
- });
8891
- /**
8892
- * Returns whether the maps are scrolled to bottom
8893
- */
8894
- Object.defineProperty(this, 'scrolledToTop', {
8895
- get() {
8896
- return this.maxPedestal === 0 || Math.ceil(this.getPedestal()) >= this.maxPedestal;
8897
- },
8898
- });
8899
- /**
8900
- * Returns whether the maps are scrolled to top
8901
- */
8902
- Object.defineProperty(this, 'scrolledToBottom', {
8903
- get() {
8904
- return this.minPedestal === 0 || Math.floor(this.getPedestal()) <= this.minPedestal;
8905
- },
8906
- });
8907
- /**
8908
- * Returns the current camera rotation.
8909
- *
8910
- * @method getRotation
8911
- * @return {Number} The current rotation of the camera, in radians.
8912
- */
8913
- this.getRotation = function () {
8914
- return scope.orbit.rotation.z;
8915
- };
8916
- /**
8917
- * Returns the current camera tilt
8918
- *
8919
- * @method getTilt
8920
- * @return {Number} The current tilt of the camera, in radians.
8921
- */
8922
- this.getTilt = function () {
8923
- return scope.elevation.rotation.x;
8924
- };
8925
- /**
8926
- * Returns the current camera zoom
8927
- *
8928
- * @method getZoom
8929
- * @return {Number} The distance of the camera from the anchor.
8930
- */
8931
- this.getZoom = function () {
8932
- return scope.camera.position.z;
8933
- };
8934
- /**
8935
- * Returns true if the camera is currently moving (it's animating, the user is manipulating it).
8936
- *
8937
- * @method isCameraMoving
8938
- * @return {Boolean} True if the camera is moving, false otherwise.
8939
- */
8940
- this.isCameraMoving = function () {
8941
- return state != STATE.NONE;
8942
- };
8943
- /**
8944
- * Returns true if the the user is currently manipulating the camera.
8945
- *
8946
- * @method isUserInteracting
8947
- * @return {Boolean} True if the user is currently manipulating the camera, false otherwise.
8948
- */
8949
- this.isUserInteracting = function () {
8950
- return userInteracting;
8951
- };
8952
- /**
8953
- * Returns the actual zoom minimum based on real-world distance.
8954
- *
8955
- * @method getZoomScaledMin
8956
- * @return {Number} The actual zoom minimum in map units.
8957
- */
8958
- this.getZoomScaledMin = function () {
8959
- return scope.minZoom * scope.zoomFactor;
8960
- };
8961
- /**
8962
- * Returns the actual zoom maximum based on visible map size.
8963
- *
8964
- * @method getZoomScaledMax
8965
- * @return {Number} The actual zoom maximum in map units.
8966
- */
8967
- this.getZoomScaledMax = function () {
8968
- let min = scope.minZoom * scope.zoomFactor;
8969
- return Math.max(scope.maxZoom, 1.5 * min);
8970
- };
8971
- let isAnimating = function () {
8972
- return TWEEN.getAll().some(function (value) {
8973
- return value._mMapAnimation;
8974
- });
8975
- };
8976
- const tweens = new Set();
8977
- function completeTween(tween) {
8978
- if (tween.isPlaying) {
8979
- tween.stop();
8980
- }
8981
- else {
8982
- tween.end();
8983
- }
8984
- tweens.delete(tween);
8985
- }
8986
- /**
8987
- * Cancel animation- for the new API
8988
- */
8989
- this.cancelAnimation = function () {
8990
- const tweensArray = Array.from(tweens);
8991
- let lastTween = tweensArray[tweensArray.length - 1];
8992
- // Make sure to emit an event if we are cancelling a tween
8993
- if (lastTween.state === STATE.MULTI) {
8994
- scope.dispatcher.dispatchEvent({
8995
- type: scope.CAMERA_EVENTS.MULTI_CANCEL_EVENT.type,
8996
- message: {
8997
- zooming: true,
8998
- rotating: true,
8999
- tilting: true,
9000
- },
9001
- });
9002
- }
9003
- completeTween(lastTween);
9004
- };
9005
- /**
9006
- * Animates the camera from it's current position to the state specified in target. You only
9007
- * need to specify the properties you actually want to change.
9008
- *
9009
- * You can also specify a duration, animation curve, and a callback for when it's done.
9010
- *
9011
- * @method animateCamera
9012
- * @param target {Object} A list of optional parameters you can set that represent the camera state.
9013
- @param [target.position] {Object} An {x, y, z} object representing the position to move to.
9014
- @param [target.zoom] {Number} The zoom level to end at.
9015
- @param [target.tilt] {Number} The tilt to end at, in radians.
9016
- @param [target.rotation] {Number} The rotation to end at, in radians.
9017
- @param [target.doNotAutoStart=false] {Boolean} Set this to true if you want to start the tween yourself.
9018
- * @param [duration] {Number} The duration to animate the camera for, in ms.
9019
- * @param [curve] {Mappedin.Easing} The animation curve to use for the animation.
9020
- * @param [callback] {Function} A callback that will be executed when the animation is done.
9021
- * @param [options] {Object} An Options object
9022
- * @param [options.mode=cancel] {Object} Mode determines what happens when multiple animateCamera calls happen at once. By Default,
9023
- it will cancel and omit any previous animations. Set to "chain" to chain instead
9024
- *
9025
- * @return {Mappedin.Tween} The tween being used, if you want to do anything to control it manually. Do not overide it's events.
9026
- */
9027
- this.animateCamera = function (target, duration, curve, callback, cancelledCallback, options) {
9028
- const _options = { mode: CAMERA_CONTROL_OPTIONS.cancel, ...options };
9029
- let tweenStart = {};
9030
- let tweenEnd = {};
9031
- let position = scope.getPosition();
9032
- if (target.position != null) {
9033
- if (target.position.x !== undefined) {
9034
- tweenStart.x = position.x;
9035
- tweenEnd.x = target.position.x;
9036
- }
9037
- if (target.position.y !== undefined) {
9038
- tweenStart.y = position.y;
9039
- tweenEnd.y = target.position.y;
9040
- }
9041
- if (target.position.z !== undefined) {
9042
- tweenStart.z = position.z;
9043
- tweenEnd.z = target.position.z;
9044
- }
9045
- }
9046
- if (target.zoom !== undefined) {
9047
- tweenStart.zoom = stayInsideBounds
9048
- ? Math.min(Math.max(scope.getZoom(), scope.getZoomScaledMin()), scope.getZoomScaledMax())
9049
- : scope.getZoom();
9050
- if (scope.viewState === VIEW_STATE.MULTI_FLOOR || !stayInsideBounds) {
9051
- tweenEnd.zoom = target.zoom;
9052
- }
9053
- else {
9054
- tweenEnd.zoom = Math.min(Math.max(target.zoom, scope.getZoomScaledMin()), scope.getZoomScaledMax());
9055
- }
9056
- }
9057
- if (target.rotation !== undefined) {
9058
- tweenStart.rotation = scope.getRotation();
9059
- const targetRotationMod2Pi = target.rotation % (2 * Math.PI);
9060
- if (Math.abs(targetRotationMod2Pi - tweenStart.rotation) > Math.PI) {
9061
- tweenEnd.rotation =
9062
- targetRotationMod2Pi < 0 ? 2 * Math.PI + targetRotationMod2Pi : -(2 * Math.PI - targetRotationMod2Pi);
9063
- }
9064
- else {
9065
- tweenEnd.rotation = targetRotationMod2Pi;
9066
- }
9067
- }
9068
- if (target.tilt !== undefined) {
9069
- tweenStart.tilt = scope.getTilt();
9070
- if (scope.viewState === VIEW_STATE.MULTI_FLOOR || !stayInsideBounds) {
9071
- tweenEnd.tilt = target.tilt;
9072
- }
9073
- else {
9074
- tweenEnd.tilt = Math.max(Math.min(target.tilt, scope.maxTilt), scope.minTilt);
9075
- }
9076
- }
9077
- const zooming = tweenStart.zoom !== tweenEnd.zoom;
9078
- const rotating = tweenStart.rotation !== tweenEnd.rotation;
9079
- const tilting = tweenStart.tilt !== tweenEnd.tilt;
9080
- var tween = new TWEEN.Tween(tweenStart)
9081
- .to(tweenEnd, process.env.TESTING ? 0 : duration)
9082
- .onUpdate(function (between) {
9083
- scope.setMulti({
9084
- x: between.x,
9085
- y: between.y,
9086
- z: between.z,
9087
- }, between.zoom, between.rotation, between.tilt);
9088
- })
9089
- .onComplete(function () {
9090
- // Why doesn't this dispatch scope.CAMERA_EVENTS.MULTI_END_EVENT..?
9091
- if (callback) {
9092
- callback();
9093
- }
9094
- completeTween(tween);
9095
- scope.dispatcher.dispatchEvent({
9096
- type: scope.CAMERA_EVENTS.MULTI_END_EVENT.type,
9097
- message: {
9098
- zooming,
9099
- rotating,
9100
- tilting,
9101
- },
9102
- });
9103
- })
9104
- .onStart(function () {
9105
- if (state != STATE.MULTI) {
9106
- state = STATE.MULTI;
9107
- tween.state = STATE.MULTI;
9108
- scope.dispatcher.dispatchEvent({
9109
- type: scope.CAMERA_EVENTS.MULTI_START_EVENT.type,
9110
- message: {
9111
- zooming,
9112
- rotating,
9113
- tilting,
9114
- },
9115
- });
9116
- }
9117
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
9118
- });
9119
- // check for ongoing tweens
9120
- const tweensArray = Array.from(tweens);
9121
- let lastTween = tweensArray[tweensArray.length - 1];
9122
- let wait = false;
9123
- // if an ongoing tween exists, chain it to this new one, so it starts when that one ends
9124
- if (lastTween != null) {
9125
- if (_options.mode === CAMERA_CONTROL_OPTIONS.chain) {
9126
- wait = true;
9127
- lastTween.chain(tween);
9128
- }
9129
- else {
9130
- completeTween(lastTween);
9131
- if (typeof cancelledCallback === 'function') {
9132
- cancelledCallback();
9133
- }
9134
- lastTween = null;
9135
- }
9136
- }
9137
- tweens.add(tween);
9138
- if (curve) {
9139
- tween.easing(curve);
9140
- }
9141
- else {
9142
- tween.easing(TWEEN.Easing.Quadratic.In);
9143
- }
9144
- if (!target.doNotAutoStart && !wait) {
9145
- tween.start();
9146
- core.tryRendering();
9147
- }
9148
- tween._mMapAnimation = true;
9149
- return tween;
9150
- };
9151
- /**
9152
- * Allows you to set any of the Camera's position, zoom, rotation and tilt at once, with one function.
9153
- *
9154
- * @method setMulti
9155
- * @param [position] {Object} an {x, y, z} object representing the new position.
9156
- * @param [zoom] {Number} The new zoom distance.
9157
- * @param [rotation] {Number} The new rotation, in radians.
9158
- * @param [tilt] {Number} The new tilt, in radians.
9159
- */
9160
- this.setMulti = function (position, zoom, rotation, tilt) {
9161
- if (state == STATE.NONE) {
9162
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.MULTI_START_EVENT);
9163
- }
9164
- let positionUpdated = false;
9165
- if (position != null) {
9166
- if (position.x !== undefined && !isNaN(position.x)) {
9167
- scope.orbit.position.x = position.x;
9168
- positionUpdated = true;
9169
- }
9170
- if (position.y !== undefined && !isNaN(position.y)) {
9171
- scope.orbit.position.y = position.y;
9172
- positionUpdated = true;
9173
- }
9174
- if (position.z !== undefined && !isNaN(position.z)) {
9175
- this.setPedestal(position.z);
9176
- }
9177
- }
9178
- if (positionUpdated) {
9179
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.POSITION_UPDATED_EVENT);
9180
- }
9181
- const payload = { zooming: false, tilting: zoom, rotating: false };
9182
- if (zoom !== undefined && !isNaN(zoom)) {
9183
- const targetZoom = stayInsideBounds
9184
- ? Math.min(Math.max(zoom, scope.getZoomScaledMin()), scope.getZoomScaledMax())
9185
- : zoom;
9186
- if (targetZoom !== scope.camera.position.z) {
9187
- payload.zooming = true;
9188
- scope.camera.position.z = targetZoom;
9189
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.ZOOM_UPDATED_EVENT);
9190
- // adjust camera near / far to prevent Z-fighting when extremely zoomed out
9191
- // this is only likely to happen in multifloor views, when FOV is reduced
9192
- if (scope.viewState === VIEW_STATE.MULTI_FLOOR) {
9193
- if (zoom > CAMERA_CLIPPING_RADIUS) {
9194
- scope.camera.near = zoom - CAMERA_CLIPPING_RADIUS;
9195
- scope.camera.far = zoom + CAMERA_CLIPPING_RADIUS;
9196
- }
9197
- }
9198
- else {
9199
- scope.camera.near = zoom / CAMERA_ZRANGE;
9200
- scope.camera.far = zoom * CAMERA_ZRANGE;
9201
- }
9202
- scope.camera.updateProjectionMatrix();
9203
- }
9204
- }
9205
- if (rotation !== undefined && !isNaN(rotation)) {
9206
- if (rotation !== scope.orbit.rotation.z) {
9207
- payload.rotating = true;
9208
- scope.orbit.rotation.z = rotation;
9209
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.ROTATION_UPDATED_EVENT);
9210
- }
9211
- }
9212
- if (tilt !== undefined && !isNaN(tilt)) {
9213
- const newTilt = stayInsideBounds ? Math.max(Math.min(tilt, scope.maxTilt), scope.minTilt) : tilt;
9214
- if (newTilt !== scope.elevation.rotation.x) {
9215
- payload.tilting = true;
9216
- scope.elevation.rotation.x = newTilt;
9217
- scope.dispatcher.dispatchEvent(scope.UPDATE_EVENTS.TILT_UPDATED_EVENT);
9218
- }
9219
- }
9220
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
9221
- if (state == STATE.NONE) {
9222
- scope.dispatcher.dispatchEvent({
9223
- type: scope.CAMERA_EVENTS.MULTI_END_EVENT.type,
9224
- message: payload,
9225
- });
9226
- }
9227
- };
9228
- function handleMouseDownRotate(event) {
9229
- rotateStart.set(event.clientX, event.clientY);
9230
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_START_EVENT);
9231
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_ROTATE_START_EVENT);
9232
- }
9233
- function handleMouseDownDolly(event) {
9234
- dollyStart.set(event.clientX, event.clientY);
9235
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_START_EVENT);
9236
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_DOLLY_START_EVENT);
9237
- }
9238
- function handleMouseDownPan() {
9239
- let pos = raycastToFloor(mouse);
9240
- panStart.set(pos.x, pos.y);
9241
- panCameraStart.set(scope.orbit.position.x, scope.orbit.position.y);
9242
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PAN_START_EVENT);
9243
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_PAN_START_EVENT);
9244
- }
9245
- function handleMouseDownPedestal(event) {
9246
- const pos = { x: event.clientX, y: event.clientY };
9247
- scope.initialShiftPos = pos;
9248
- scope.initialPedestal = scope.getPedestal();
9249
- scope.pedestalScaleFactor = getProjectionScaleFactor(scope.camera.fov, scope.canvas.clientHeight, scope.getZoom());
9250
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PEDESTAL_START_EVENT);
9251
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_PEDESTAL_START_EVENT);
9252
- }
9253
- function handleMouseMovePedestal(event) {
9254
- const pos = { x: event.clientX, y: event.clientY };
9255
- let newZ = scope.initialPedestal + scope.pedestalScaleFactor * (pos.y - scope.initialShiftPos.y);
9256
- // clamp to min/max
9257
- if (stayInsideBounds) {
9258
- if (newZ < scope.minPedestal) {
9259
- newZ = Math.max(newZ, scope.minPedestal);
9260
- }
9261
- else if (newZ > scope.maxPedestal) {
9262
- newZ = Math.min(newZ, scope.maxPedestal);
9263
- }
9264
- }
9265
- if (newZ === scope.getPedestal()) {
9266
- return;
9267
- }
9268
- scope.setPedestal(newZ);
9269
- dispatchPedestalChangeEvent(newZ);
9270
- }
9271
- function handleMouseMoveRotate(event) {
9272
- rotateEnd.set(event.clientX, event.clientY);
9273
- rotateDelta.subVectors(rotateEnd, rotateStart);
9274
- scope.rotate(-rotateDelta.x / scope.rotateSpeed);
9275
- scope.setTilt(scope.getTilt() - rotateDelta.y / scope.rotateSpeed);
9276
- rotateStart.copy(rotateEnd);
9277
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
9278
- }
9279
- function handleMouseMoveDolly(event) {
9280
- dollyEnd.set(event.clientX, event.clientY);
9281
- dollyDelta.subVectors(dollyEnd, dollyStart);
9282
- scope.setZoom(dollyDelta.y * scope.zoomSpeed);
9283
- dollyStart.copy(dollyEnd);
9284
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
9285
- }
9286
- function handleMouseMovePan() {
9287
- let pos = raycastToFloor(mouse);
9288
- panEnd.set(pos.x, pos.y);
9289
- panDelta.subVectors(panEnd, panStart);
9290
- scope.setPosition(panCameraStart.x - panDelta.x, panCameraStart.y - panDelta.y);
9291
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
9292
- }
9293
- this.getProjectionScaleFactor = getProjectionScaleFactor;
9294
- /**
9295
- * Anything we need to do after we render the camera, like update anchors for the mouse/touch controls.
9296
- *
9297
- * @method postRender
9298
- * @private
9299
- */
9300
- this.postRender = function () {
9301
- if (state == STATE.PAN || state == STATE.TOUCH_PAN) {
9302
- raycaster.setFromCamera(mouse, camera);
9303
- intersection = raycaster.intersectObject(cameraPlane, false)[0];
9304
- if (intersection) {
9305
- panStart.set(intersection.point.x, intersection.point.y);
9306
- panCameraStart.set(scope.orbit.position.x, scope.orbit.position.y);
9307
- }
9308
- }
9309
- if (state == STATE.WHEEL_ZOOM && resetZoom) {
9310
- resetZoom = false;
9311
- zoomStart = scope.getZoom();
9312
- floorAnchor = raycastToFloor(mouse);
9313
- panCameraStart.set(scope.orbit.position.x, scope.orbit.position.y);
9314
- }
9315
- if (state == STATE.MULTI && isAnimating() == false) {
9316
- state = STATE.NONE;
9317
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.MULTI_END_EVENT);
9318
- }
9319
- };
9320
- function scrollDelta(event) {
9321
- let delta = 0;
9322
- if (event.wheelDelta !== undefined) {
9323
- // WebKit / Opera / Explorer 9
9324
- delta = event.wheelDelta;
9325
- }
9326
- else if (event.detail !== undefined) {
9327
- // Firefox
9328
- delta = -event.detail;
9329
- }
9330
- return delta;
9331
- }
9332
- function canScrollZoom(event) {
9333
- let delta = scrollDelta(event);
9334
- let currentZoom = scope.getZoom();
9335
- return !((delta < 0 && currentZoom == scope.getZoomScaledMax()) ||
9336
- (delta > 0 && currentZoom == scope.getZoomScaledMin()));
9337
- }
9338
- function handleMouseWheel(event) {
9339
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_START_EVENT);
9340
- if (!canScrollZoom(event)) {
9341
- if (lastWheelTime > 0) {
9342
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
9343
- }
9344
- lastWheelTime = 0;
9345
- state = STATE.NONE;
9346
- return;
9347
- }
9348
- let currentZoom = scope.getZoom();
9349
- let zoom = currentZoom - ((scrollDelta(event) * currentZoom) / WHEEL_ZOOM_MULTIPLIER) * scope.zoomSpeed;
9350
- scope.setZoom(zoom);
9351
- let zoomPercent = zoom / zoomStart;
9352
- panDelta.subVectors(floorAnchor, panCameraStart).multiplyScalar(1 - zoomPercent);
9353
- scope.setPosition(panCameraStart.x + panDelta.x, panCameraStart.y + panDelta.y);
9354
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
9355
- }
9356
- function onMouseDown(event) {
9357
- if (scope.enabled === false)
9358
- return;
9359
- mouseToScene(event);
9360
- event.preventDefault();
9361
- if (state == STATE.WHEEL_ZOOM) {
9362
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
9363
- }
9364
- if (event.button === scope.mouseButtons.ORBIT) {
9365
- if (scope.enableRotate === false)
9366
- return;
9367
- handleMouseDownRotate(event);
9368
- state = STATE.ROTATE;
9369
- }
9370
- else if (event.button === scope.mouseButtons.ZOOM) {
9371
- if (scope.enableZoom === false)
9372
- return;
9373
- handleMouseDownDolly(event);
9374
- state = STATE.DOLLY;
9375
- }
9376
- else if (event.button === scope.mouseButtons.PAN) {
9377
- if (scope.enablePan) {
9378
- handleMouseDownPan(event);
9379
- state = STATE.PAN;
9380
- }
9381
- else if (scope.enablePedestal) {
9382
- handleMouseDownPedestal(event);
9383
- state = STATE.PEDESTAL;
9384
- }
9385
- }
9386
- if (state !== STATE.NONE) {
9387
- scope.canvas.addEventListener('mousemove', onMouseMove, false);
9388
- window.addEventListener('mouseup', onMouseUp, false);
9389
- window.addEventListener('mouseout', onMouseUp, false);
9390
- }
9391
- }
9392
- function onMouseMove(event) {
9393
- if (scope.enabled === false)
9394
- return;
9395
- event.preventDefault();
9396
- mouseToScene(event);
9397
- if (state === STATE.ROTATE) {
9398
- if (scope.enableRotate === false)
9399
- return;
9400
- handleMouseMoveRotate(event);
9401
- }
9402
- else if (state === STATE.DOLLY) {
9403
- if (scope.enableZoom === false)
9404
- return;
9405
- handleMouseMoveDolly(event);
9406
- }
9407
- else if (state === STATE.PAN) {
9408
- if (scope.enablePan === false)
9409
- return;
9410
- handleMouseMovePan(event);
9411
- }
9412
- else if (state === STATE.WHEEL_ZOOM) {
9413
- resetZoom = true;
9414
- state = STATE.NONE;
9415
- }
9416
- else if (state === STATE.PEDESTAL) {
9417
- handleMouseMovePedestal(event);
9418
- }
9419
- }
9420
- function onMouseUp() {
9421
- if (scope.enabled === false)
9422
- return;
9423
- document.removeEventListener('mousemove', onMouseMove, false);
9424
- window.removeEventListener('mouseup', onMouseUp, false);
9425
- window.removeEventListener('mouseout', onMouseUp, false);
9426
- switch (state) {
9427
- case STATE.PAN:
9428
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PAN_END_EVENT);
9429
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_PAN_END_EVENT);
9430
- break;
9431
- case STATE.DOLLY:
9432
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
9433
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_DOLLY_END_EVENT);
9434
- break;
9435
- case STATE.ROTATE:
9436
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_END_EVENT);
9437
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_ROTATE_END_EVENT);
9438
- break;
9439
- case STATE.PEDESTAL:
9440
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PEDESTAL_END_EVENT);
9441
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_PEDESTAL_END_EVENT);
9442
- break;
9443
- }
9444
- state = STATE.NONE;
9445
- }
9446
- let scrollTimer = null;
9447
- function onMouseWheel(event) {
9448
- if (scope.enabled === false || scope.enableZoom === false || (state !== STATE.NONE && state !== STATE.WHEEL_ZOOM))
9449
- return;
9450
- event.preventDefault();
9451
- event.stopPropagation();
9452
- mouseToScene(event);
9453
- if (canScrollZoom(event)) {
9454
- lastWheelTime = clock.getElapsedTime();
9455
- scope.dispatchUserZoomEvent();
9456
- }
9457
- if (state != STATE.WHEEL_ZOOM && canScrollZoom(event)) {
9458
- scope.canvas.addEventListener('mousemove', onMouseMove, false);
9459
- state = STATE.WHEEL_ZOOM;
9460
- zoomStart = scope.getZoom();
9461
- floorAnchor = raycastToFloor(mouse);
9462
- panCameraStart.set(scope.orbit.position.x, scope.orbit.position.y);
9463
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_START_EVENT);
9464
- }
9465
- if (scrollTimer != null) {
9466
- clearTimeout(scrollTimer);
9467
- }
9468
- scrollTimer = setTimeout(() => {
9469
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
9470
- }, 50);
9471
- handleMouseWheel(event);
9472
- }
9473
- function handleTouchStartPan(event) {
9474
- let pos = raycastToFloor(event.x ? event : touches[0]);
9475
- panStart.set(pos.x, pos.y);
9476
- panCameraStart.set(scope.orbit.position.x, scope.orbit.position.y);
9477
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_PAN_START_EVENT);
9478
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PAN_START_EVENT);
9479
- }
9480
- function handleTouchMovePan(event) {
9481
- let pos = raycastToFloor(event.x ? event : touches[0]);
9482
- panEnd.set(pos.x, pos.y);
9483
- panDelta.subVectors(panEnd, panStart);
9484
- scope.setPosition(panCameraStart.x - panDelta.x, panCameraStart.y - panDelta.y);
9485
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.CHANGE_EVENT);
9486
- }
9487
- function handleTouchStartDolly() {
9488
- scope.touchInputs = new InputSet(touches[0], touches[1]);
9489
- let dx = touches[0].x - touches[1].x;
9490
- let dy = touches[0].y - touches[1].y;
9491
- zoomStart = scope.getZoom();
9492
- let pos = { x: touches[0].x - dx / 2, y: touches[0].y - dy / 2 };
9493
- floorAnchor = raycastToFloor(pos);
9494
- scope.orbit.updateMatrixWorld();
9495
- scope.touchAnchor1 = new TouchAnchor(touches[0], scope.camera, cameraPlane);
9496
- scope.touchAnchor2 = new TouchAnchor(touches[1], scope.camera, cameraPlane);
9497
- panCameraStart.set(scope.orbit.position.x, scope.orbit.position.y);
9498
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_DOLLY_START_EVENT);
9499
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.MULTI_START_EVENT);
9500
- }
9501
- function handleTouchMoveDolly() {
9502
- scope.touchInputs.update(touches[0], touches[1]);
9503
- const first = scope.touchInputs.first;
9504
- const second = scope.touchInputs.second;
9505
- scope.touchAnchor1.viewCoordinate = { x: first.x, y: first.y };
9506
- scope.touchAnchor2.viewCoordinate = { x: second.x, y: second.y };
9507
- let newMatrix = makeTransformFromTouchAnchors(scope.touchAnchor1, scope.touchAnchor2);
9508
- if (newMatrix) {
9509
- setCameraFromTransformMatrix(newMatrix);
9510
- }
9511
- scope.orbit.updateMatrixWorld();
9512
- scope.touchAnchor1.updateAnchorState(scope.camera, first);
9513
- scope.touchAnchor2.updateAnchorState(scope.camera, second);
9514
- }
9515
- function handleTouchStartTilt(event) {
9516
- rotateStart.set(event.touches[1].clientX, event.touches[1].clientY);
9517
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_TILT_START_EVENT);
9518
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_START_EVENT);
9519
- }
9520
- function handleTouchMoveTilt(event) {
9521
- rotateEnd.set(event.touches[1].clientX, event.touches[1].clientY);
9522
- rotateDelta.subVectors(rotateEnd, rotateStart);
9523
- scope.setTilt(scope.getTilt() - rotateDelta.y / scope.rotateSpeed);
9524
- rotateStart.copy(rotateEnd);
9525
- }
9526
- function handleTouchStartPedestal(event) {
9527
- handleMouseDownPedestal(event.touches[0]);
9528
- }
9529
- function handleTouchMovePedestal(event) {
9530
- handleMouseMovePedestal(event.touches[0]);
9531
- }
9532
- function handleTouchEnd() {
9533
- if (state === STATE.TOUCH_TILT || state === STATE.TOUCH_DOLLY) {
9534
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.MULTI_END_EVENT);
9535
- }
9536
- }
9537
- function onTouchStart(event) {
9538
- if (scope.enabled === false)
9539
- return;
9540
- updateTouchOrigin(event);
9541
- touchToScene(event);
9542
- switch (event.touches.length) {
9543
- case 1: // one-fingered touch: pan/pedestal
9544
- // note - they are mutually exclusive as they use the same event
9545
- // pedestal takes precedence
9546
- if (scope.enablePedestal) {
9547
- handleTouchStartPedestal(event);
9548
- state = STATE.TOUCH_PEDESTAL;
9549
- }
9550
- else if (scope.enableRotate) {
9551
- handleTouchStartPan(event);
9552
- state = STATE.TOUCH_PAN;
9553
- }
9554
- break;
9555
- case 2: // two-fingered touch: dolly
9556
- if (scope.enableZoom === false)
9557
- return;
9558
- handleTouchStartDolly(event);
9559
- state = STATE.TOUCH_DOLLY;
9560
- break;
9561
- case 3: // three-fingered touch: tilt
9562
- if (scope.enablePan === false)
9563
- return;
9564
- handleTouchStartTilt(event);
9565
- state = STATE.TOUCH_TILT;
9566
- break;
9567
- default:
9568
- state = STATE.NONE;
9569
- }
9570
- if (state !== STATE.NONE) {
9571
- // Probably don't need to do anything
9572
- }
9573
- }
9574
- function onTouchMove(event) {
9575
- if (scope.enabled === false)
9576
- return;
9577
- event.preventDefault();
9578
- event.stopPropagation();
9579
- touchToScene(event);
9580
- switch (event.touches.length) {
9581
- case 3: // one-fingered touch: rotate
9582
- if (scope.enableRotate === false)
9583
- return;
9584
- if (state !== STATE.TOUCH_TILT)
9585
- return;
9586
- handleTouchMoveTilt(event);
9587
- break;
9588
- case 2: // two-fingered touch: zoom
9589
- if (scope.enableZoom === false)
9590
- return;
9591
- if (state !== STATE.TOUCH_DOLLY)
9592
- return;
9593
- handleTouchMoveDolly(event);
9594
- break;
9595
- case 1: // One-fingered touch: pan/pedestal
9596
- if (scope.enablePedestal && state === STATE.TOUCH_PEDESTAL) {
9597
- handleTouchMovePedestal(event);
9598
- }
9599
- else if (scope.enablePan && state === STATE.TOUCH_PAN) {
9600
- handleTouchMovePan(event);
9601
- }
9602
- break;
9603
- default:
9604
- state = STATE.NONE;
9605
- }
9606
- }
9607
- function onTouchEnd(event) {
9608
- if (scope.enabled === false)
9609
- return;
9610
- handleTouchEnd(event);
9611
- switch (state) {
9612
- case STATE.PAN:
9613
- case STATE.TOUCH_PAN:
9614
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_PAN_END_EVENT);
9615
- break;
9616
- case STATE.TOUCH_TILT:
9617
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PAN_END_EVENT);
9618
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_TILT_END_EVENT);
9619
- break;
9620
- case STATE.DOLLY:
9621
- case STATE.TOUCH_DOLLY:
9622
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
9623
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_DOLLY_END_EVENT);
9624
- break;
9625
- case STATE.ROTATE:
9626
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ROTATE_END_EVENT);
9627
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_ROTATE_END_EVENT);
9628
- break;
9629
- case STATE.PEDESTAL:
9630
- case STATE.TOUCH_PEDESTAL:
9631
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.PEDESTAL_END_EVENT);
9632
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_PEDESTAL_END_EVENT);
9633
- break;
9634
- }
9635
- state = STATE.NONE;
9636
- }
9637
- function onContextMenu(event) {
9638
- event.preventDefault();
9639
- }
9640
- function differenceBetweenAngles(a, b) {
9641
- return ((b - a + Math.PI) % (Math.PI * 2)) + ((Math.PI * 2) % (Math.PI * 2)) - Math.PI;
9642
- }
9643
- function makeTransformFromTouchAnchors(touchAnchor1, touchAnchor2) {
9644
- scope.orbit.updateMatrixWorld();
9645
- scope.camera.updateProjectionMatrix();
9646
- let p = scope.camera.projectionMatrix.clone();
9647
- let w1 = touchAnchor1.worldCoordinate;
9648
- let w2 = touchAnchor2.worldCoordinate;
9649
- let nw1 = touchAnchor1.reUnproject();
9650
- let nw2 = touchAnchor2.reUnproject();
9651
- if (!nw1 || !nw2) {
9652
- return null;
9653
- }
9654
- let s1 = touchAnchor1.viewCoordinate;
9655
- let s2 = touchAnchor2.viewCoordinate;
9656
- let xd1 = w2.x - w1.x;
9657
- let yd1 = w2.y - w1.y;
9658
- let angle1 = Math.atan2(yd1, xd1);
9659
- let xd2 = nw2.x - nw1.x;
9660
- let yd2 = nw2.y - nw1.y;
9661
- let angle2 = Math.atan2(yd2, xd2);
9662
- let zAngleDelta = differenceBetweenAngles(angle1, angle2);
9663
- let yawAdjustment = new Matrix4();
9664
- yawAdjustment.makeRotationZ(-zAngleDelta);
9665
- let t = yawAdjustment.clone();
9666
- t.multiply(touchAnchor1.snapHolderMatrix);
9667
- // Matrix subscript helper
9668
- let m = (o, a, b) => o.elements[(a - 1) * 4 + b - 1];
9669
- let x1 = m(p, 1, 1) * m(t, 1, 1) - s1.x * m(p, 3, 4) * m(t, 3, 1);
9670
- let y1 = m(p, 1, 1) * m(t, 1, 2) - s1.x * m(p, 3, 4) * m(t, 3, 2);
9671
- let z1 = m(p, 1, 1) * m(t, 1, 3) - s1.x * m(p, 3, 4) * m(t, 3, 3);
9672
- let c1 = w1.x * x1 + w1.y * y1 + w1.z * z1;
9673
- let x2 = m(p, 2, 2) * m(t, 2, 1) - s1.y * m(p, 3, 4) * m(t, 3, 1);
9674
- let y2 = m(p, 2, 2) * m(t, 2, 2) - s1.y * m(p, 3, 4) * m(t, 3, 2);
9675
- let z2 = m(p, 2, 2) * m(t, 2, 3) - s1.y * m(p, 3, 4) * m(t, 3, 3);
9676
- let c2 = w1.x * x2 + w1.y * y2 + w1.z * z2;
9677
- let x3 = m(p, 1, 1) * m(t, 1, 1) - s2.x * m(p, 3, 4) * m(t, 3, 1);
9678
- let y3 = m(p, 1, 1) * m(t, 1, 2) - s2.x * m(p, 3, 4) * m(t, 3, 2);
9679
- let z3 = m(p, 1, 1) * m(t, 1, 3) - s2.x * m(p, 3, 4) * m(t, 3, 3);
9680
- let c3 = w2.x * x3 + w2.y * y3 + w2.z * z3;
9681
- let xb = m(p, 2, 2) * m(t, 2, 1) - s2.y * m(p, 3, 4) * m(t, 3, 1);
9682
- let yb = m(p, 2, 2) * m(t, 2, 2) - s2.y * m(p, 3, 4) * m(t, 3, 2);
9683
- let zb = m(p, 2, 2) * m(t, 2, 3) - s2.y * m(p, 3, 4) * m(t, 3, 3);
9684
- let cb = w2.x * xb + w2.y * yb + w2.z * zb;
9685
- let dx1 = Math.abs(x3 - x1);
9686
- let dy1 = Math.abs(y3 - y1);
9687
- let dz1 = Math.abs(z3 - z1);
9688
- let dx2 = Math.abs(x3 - x2);
9689
- let dy2 = Math.abs(y3 - y2);
9690
- let dz2 = Math.abs(z3 - z2);
9691
- let dx3 = Math.abs(xb - x1);
9692
- let dy3 = Math.abs(yb - y1);
9693
- let dz3 = Math.abs(zb - z1);
9694
- let dx4 = Math.abs(xb - x2);
9695
- let dy4 = Math.abs(yb - y2);
9696
- let dz4 = Math.abs(zb - z2);
9697
- let min1 = Math.min(dx1 + dy1 + dz1, dx2 + dy2 + dz2);
9698
- let min2 = Math.min(dx3 + dy3 + dz3, dx4 + dy4 + dz4);
9699
- if (min1 < min2) {
9700
- x3 = xb;
9701
- y3 = yb;
9702
- z3 = zb;
9703
- c3 = cb;
9704
- }
9705
- let x4 = x2 * z1 - x1 * z2;
9706
- let y4 = y2 * z1 - y1 * z2;
9707
- let c4 = c2 * z1 - c1 * z2;
9708
- let x5 = x3 * z2 - x2 * z3;
9709
- let y5 = y3 * z2 - y2 * z3;
9710
- let c5 = c3 * z2 - c2 * z3;
9711
- let x = (c5 * y4 - c4 * y5) / (x5 * y4 - x4 * y5);
9712
- let y = (c5 * x4 - c4 * x5) / (y5 * x4 - y4 * x5);
9713
- let y6 = y2 * x1 - y1 * x2;
9714
- let z6 = z2 * x1 - z1 * x2;
9715
- let c6 = c2 * x1 - c1 * x2;
9716
- let y7 = y3 * x2 - y2 * x3;
9717
- let z7 = z3 * x2 - z2 * x3;
9718
- let c7 = c3 * x2 - c2 * x3;
9719
- let z = (c7 * y6 - c6 * y7) / (z7 * y6 - z6 * y7);
9720
- let finalTransformMatrix = t.clone();
9721
- finalTransformMatrix.elements[12] = x;
9722
- finalTransformMatrix.elements[13] = y;
9723
- finalTransformMatrix.elements[14] = z;
9724
- if (z > 0) {
9725
- return finalTransformMatrix;
9726
- }
9727
- else {
9728
- return null;
9729
- }
9730
- }
9731
- function setCameraFromTransformMatrix(transform) {
9732
- scope.orbit.updateMatrixWorld();
9733
- let newAngle = new Euler();
9734
- newAngle.setFromRotationMatrix(transform, 'ZYX');
9735
- let zoom = transform.elements[14] / Math.cos(newAngle.x);
9736
- let rotation = newAngle.z;
9737
- let vectorToGround = new Vector3(0, 0, 1);
9738
- vectorToGround.applyEuler(newAngle);
9739
- vectorToGround.setLength(zoom);
9740
- let groundPosition = new Vector3();
9741
- groundPosition.setFromMatrixPosition(transform);
9742
- groundPosition.sub(vectorToGround);
9743
- scope.setPosition(groundPosition.x, groundPosition.y);
9744
- scope.setZoom(zoom);
9745
- scope.setRotation(rotation);
9746
- }
9747
- function getMousePos(canvas, evt) {
9748
- return {
9749
- x: evt.offsetX,
9750
- y: evt.offsetY,
9751
- };
9752
- }
9753
- function getTouchPos(canvas, touch) {
9754
- return {
9755
- x: touch.clientX - touchOrigin.offsetLeft,
9756
- y: touch.clientY - touchOrigin.offsetTop,
9757
- };
9758
- }
9759
- function updateTouchOrigin(event) {
9760
- let touchOriginElement = scope.canvas;
9761
- touchOrigin.offsetLeft = touchOriginElement.getBoundingClientRect().left;
9762
- touchOrigin.offsetTop = touchOriginElement.getBoundingClientRect().top;
9763
- }
9764
- function raycastToFloor(pos) {
9765
- raycaster.setFromCamera(pos, camera);
9766
- let intersection = raycaster.intersectObject(cameraPlane, false)[0];
9767
- if (intersection) {
9768
- return intersection.point;
9769
- }
9770
- else {
9771
- return new Vector3();
9772
- }
9773
- }
9774
- function mouseToScene(event) {
9775
- let pos = getMousePos(scope.canvas, event);
9776
- mouse.x = (pos.x / scope.canvas.width) * core.resolutionScale * 2 - 1;
9777
- mouse.y = -((pos.y / scope.canvas.height) * core.resolutionScale) * 2 + 1;
9778
- return mouse;
9779
- }
9780
- function touchToScene(event) {
9781
- touches = [];
9782
- for (let i = 0, iLen = event.touches.length; i < iLen; i++) {
9783
- let touch = event.touches[i];
9784
- let pos = getTouchPos(scope.canvas, touch);
9785
- touches.push(new Vector2((pos.x / scope.canvas.width) * core.resolutionScale * 2 - 1, -((pos.y / scope.canvas.height) * core.resolutionScale) * 2 + 1));
9786
- }
9787
- mouse = touches[0];
9788
- }
9789
- /**
9790
- * Should probably be "pre-render". Anything we need to do before rendering the scene.
9791
- *
9792
- * @method update
9793
- * @private
9794
- */
9795
- this.update = function () {
9796
- TWEEN.update();
9797
- if (lastWheelTime > 0 && state == STATE.WHEEL_ZOOM && clock.getElapsedTime() - lastWheelTime > 0.2) {
9798
- lastWheelTime = 0;
9799
- state = STATE.NONE;
9800
- scope.dispatcher.dispatchEvent(scope.CAMERA_EVENTS.ZOOM_END_EVENT);
9801
- scope.canvas.removeEventListener('mousemove', onMouseMove, false);
9802
- }
9803
- };
9804
- let isUserZooming = false;
9805
- this.dispatchUserZoomDebounced = debounce(() => {
9806
- // dispatch user zoom end
9807
- isUserZooming = false;
9808
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_ZOOM_END_EVENT);
9809
- }, 250);
9810
- this.dispatchUserZoomEvent = function () {
9811
- if (isUserZooming) {
9812
- scope.dispatchUserZoomDebounced();
9813
- }
9814
- else {
9815
- scope.dispatcher.dispatchEvent(scope.INTERACTION_EVENTS.USER_ZOOM_START_EVENT);
9816
- isUserZooming = true;
9817
- scope.dispatchUserZoomDebounced();
9818
- }
9819
- };
9820
- /**
9821
- * Disposes of the camera and all of it's events.
9822
- *
9823
- * @method dispose
9824
- * @private
9825
- */
9826
- this.dispose = function () {
9827
- for (let e in this.INTERACTION_EVENTS) {
9828
- const event = this.INTERACTION_EVENTS[e];
9829
- if (event.type.endsWith('Start')) {
9830
- scope.dispatcher.removeEventListener(event.type, setUserInteracting);
9831
- }
9832
- }
9833
- for (let e in this.INTERACTION_EVENTS) {
9834
- const event = this.INTERACTION_EVENTS[e];
9835
- if (event.type.endsWith('End')) {
9836
- scope.dispatcher.removeEventListener(event.type, unsetUserInteracting);
9837
- }
9838
- }
9839
- scope.canvas.removeEventListener('contextmenu', onContextMenu, false);
9840
- scope.canvas.removeEventListener('mousedown', onMouseDown, false);
9841
- scope.canvas.removeEventListener('wheel', onMouseWheel, { passive: false });
9842
- scope.canvas.removeEventListener('touchstart', onTouchStart, false);
9843
- scope.canvas.removeEventListener('touchend', onTouchEnd, false);
9844
- scope.canvas.removeEventListener('touchmove', onTouchMove, false);
9845
- scope.canvas.removeEventListener('mousemove', onMouseMove, false);
9846
- window.removeEventListener('mouseup', onMouseUp, false);
9847
- window.removeEventListener('mouseout', onMouseUp, false);
9848
- scene.remove(cameraPlane);
9849
- scope.dispatcher._listeners = {};
9850
- };
9851
- this.canvas.addEventListener('contextmenu', onContextMenu, false);
9852
- this.canvas.addEventListener('mousedown', onMouseDown, false);
9853
- this.canvas.addEventListener('wheel', onMouseWheel, { passive: false });
9854
- this.canvas.addEventListener('touchstart', onTouchStart, false);
9855
- this.canvas.addEventListener('touchend', onTouchEnd, false);
9856
- this.canvas.addEventListener('touchmove', onTouchMove, false);
9857
- };
9858
- export default CameraControls;
9859
-
9860
- import { Color, DepthTexture, Mesh, Vector2, NearestFilter, OrthographicCamera, PlaneBufferGeometry, RGBAFormat, Scene, ShaderMaterial, UnsignedIntType, WebGLRenderTarget, WebGL1Renderer, WebGLRenderer, sRGBEncoding, } from 'three';
9861
- import { CAMERA_LAYER, RENDER } from '@mappedin/mappedin-js/renderer/internal';
9862
- import CompositeVertexShader from '@mappedin/mappedin-js/renderer/internal/shaders/Mappedin.Composite.Vertex.glsl';
9863
- import CompositeFragmentShader from '@mappedin/mappedin-js/renderer/internal/shaders/Mappedin.Composite.Fragment.glsl';
9864
- import Stats from 'stats.js';
9865
- import Logger from '@mappedin/mappedin-js/--/common/Mappedin.Logger';
9866
- let SHOW_FPS = false;
9867
- let stats;
9868
- if (SHOW_FPS) {
9869
- stats = new Stats();
9870
- stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
9871
- document.body.appendChild(stats.dom);
9872
- }
9873
- /**
9874
- * A class that controls the rendering resources for a canvas output.
9875
- *
9876
- * @class Renderer
9877
- * @private
9878
- */
9879
- export default class Renderer {
9880
- /**
9881
- * @constructor
9882
- * @param renderOptions {Object} Options for rendering
9883
- * @param [options.multiBufferRendering=false] {boolean}
9884
- * Whether to use a multi-buffer renderer
9885
- * @param [options.alpha=true] {boolean}
9886
- * If true, will allow for a semi-transparent background
9887
- * @param [options.antialias=false] {boolean}
9888
- * If true, will attempt to antialias rendering if supported
9889
- * @param [options.backgroundColor='#ffffff'] {Color | string}
9890
- * The color that will be displayed behind the scene
9891
- * @param [options.backgroundAlpha=1.0] {number}
9892
- * The opacity of the background color
9893
- * @param [options.xRayPath=true] {boolean}
9894
- * If true, journey path will be visible through other objects
9895
- * @param [options.onWebGLContextCreationError=null] {function}
9896
- * A callback that will be triggered if WebGL context creation fails
9897
- * @param [options.onWebGLContextLost=null] {function}
9898
- * A callback that will be triggered if the WebGL context is killed
9899
- * @param [options.onWebGLContextRestored=null] {function}
9900
- * A callback that will be triggered when three.js reacquires a WebGL context
9901
- * @param [options.onWebGLRendererError=null] {function}
9902
- * A callback that will be triggered if the renderer throws an error
9903
- */
9904
- constructor(renderOptions) {
9905
- renderOptions.multiBufferRendering = renderOptions.multiBufferRendering || false;
9906
- this.contextLost = false;
9907
- this.implementation = this.getRenderer(renderOptions);
9908
- this.onWebGLContextCreationError = renderOptions.onWebGLContextCreationError;
9909
- this.onWebGLContextLost = renderOptions.onWebGLContextLost;
9910
- this.onWebGLContextRestored = renderOptions.onWebGLContextRestored;
9911
- this.webGLContextCreationErrorListener = this.domElement().addEventListener('webglcontextcreationerror', e => this.reportWebGlContextCreationError(e));
9912
- this.webGLContextLostListener = this.domElement().addEventListener('webglcontextlost', e => this.reportWebGlContextLost(e));
9913
- this.webGLContextRestoredListener = this.domElement().addEventListener('webglcontextrestored', e => this.reportWebGLContextRestored(e));
9914
- let backgroundColor = renderOptions.backgroundColor;
9915
- if (typeof backgroundColor === 'string') {
9916
- backgroundColor = new Color(renderOptions.backgroundColor);
9917
- }
9918
- if (backgroundColor == null) {
9919
- backgroundColor = new Color('#ffffff');
9920
- }
9921
- let backgroundAlpha = renderOptions.backgroundAlpha;
9922
- if (backgroundAlpha == null) {
9923
- backgroundAlpha = 1.0;
9924
- }
9925
- this.shouldConsiderAlpha = renderOptions.alpha || false;
9926
- this.antialias = renderOptions.antialias || false;
9927
- this.setBackgroundColor(backgroundColor, backgroundAlpha);
9928
- }
9929
- /**
9930
- * Dispose of any resources and connections allocated by the renderer.
9931
- *
9932
- * @method destroy
9933
- */
9934
- destroy() {
9935
- this.domElement().removeEventListener('webglcontextcreationerror', this.webGLContextCreationErrorListener);
9936
- this.domElement().removeEventListener('webglcontextlost', this.webGLContextLostListener);
9937
- this.domElement().removeEventListener('webglcontextrestored', this.webGLContextRestoredListener);
9938
- this.implementation.destroy();
9939
- }
9940
- /**
9941
- * Render the scene to the provided framebuffer. A null framebuffer will
9942
- * render to the default canvas.
9943
- *
9944
- * @method render
9945
- * @param renderTask {RENDER}
9946
- * @param renderTarget {null or WebGLRenderTarget}
9947
- * @param scene {Scene}
9948
- * @param sceneCamera {Camera}
9949
- */
9950
- render(renderTask, renderTarget, scene, sceneCamera) {
9951
- this.implementation.render(renderTask, renderTarget, scene, sceneCamera);
9952
- }
9953
- /**
9954
- * Return the maximum supported anisotropy of this renderer.
9955
- *
9956
- * @method getMaxAnisotropy
9957
- * @return {number}
9958
- */
9959
- getMaxAnisotropy() {
9960
- return this.implementation.renderer.capabilities.getMaxAnisotropy();
9961
- }
9962
- /**
9963
- * Return the size of the renderer's target.
9964
- *
9965
- * @method getBufferSize
9966
- * @return {Vector2}
9967
- */
9968
- getBufferSize() {
9969
- return this.implementation.renderer.getSize(new Vector2());
9970
- }
9971
- /**
9972
- * Return the WebGL context associated with this renderer, to ensure
9973
- * other objects that use WebGL will put their textures in the same
9974
- * context.
9975
- *
9976
- * @method context
9977
- * @return {WebGLRenderingContext} context used by this renderer
9978
- */
9979
- getContext() {
9980
- return this.implementation.renderer.getContext();
9981
- }
9982
- /**
9983
- * Preload a texture, I think. Not actually sure what this does. TODO.
9984
- *
9985
- * @method preloadTexture
9986
- * @param texture {Texture}
9987
- */
9988
- preloadTexture(texture) {
9989
- this.implementation.renderer.initTexture(texture);
9990
- }
9991
- /**
9992
- * Set the renderer and all its internal buffers to the provided width and
9993
- * height in pixels.
9994
- *
9995
- * @method setBufferSize
9996
- * @param width {number}
9997
- * @param height {number}
9998
- */
9999
- setBufferSize(width, height) {
10000
- this.implementation.setBufferSize(width, height);
10001
- }
10002
- get backgroundColor() {
10003
- return this.implementation.backgroundColor;
10004
- }
10005
- get backgroundAlpha() {
10006
- return this.implementation.backgroundAlpha;
10007
- }
10008
- /**
10009
- * Set the color and opacity that will be drawn behind the scene.
10010
- *
10011
- * @method setBackgroundColor
10012
- * @param color {Color}
10013
- * @param alpha {number}
10014
- */
10015
- setBackgroundColor(color, alpha) {
10016
- if (!this.shouldConsiderAlpha) {
10017
- alpha = 1.0;
10018
- }
10019
- alpha = alpha !== undefined ? +alpha : 1.0;
10020
- alpha = alpha >= 0.0 && alpha <= 1.0 ? alpha : 1.0;
10021
- this.implementation.setBackgroundColor(color, alpha);
10022
- }
10023
- /**
10024
- * Assign an outdoor context to this renderer, which will be included as
10025
- * part of the `STATIC` rendering pass.
10026
- *
10027
- * @method setMapboxOutdoorContext
10028
- * @param {MapboxOutdoorContext} mapboxOutdoorContext context to draw
10029
- */
10030
- setMapboxOutdoorContext(mapboxOutdoorContext) {
10031
- this.implementation.setMapboxOutdoorContext(mapboxOutdoorContext);
10032
- }
10033
- // Return the DOM element to which this renderer will render when provided
10034
- // a `null` `renderTarget` in the `render` method. You can position this
10035
- // DOM element and add it to the DOM as you see fit.
10036
- domElement() {
10037
- return this.implementation.renderer.domElement;
10038
- }
10039
- // Return true if this renderer is available to use for rendering. If false,
10040
- // this means that the context is currently lost, or the renderer failed to
10041
- // initialize.
10042
- isAvailable() {
10043
- return this.implementation.renderer != null && !this.contextLost;
10044
- }
10045
- // Temporary solution until https://github.com/mrdoob/three.js/issues/12447 is addressed
10046
- disposeOfRenderLists() {
10047
- this.implementation.renderer.renderLists.dispose();
10048
- }
10049
- reportWebGlContextCreationError(e) {
10050
- if (this.onWebGLContextCreationError) {
10051
- this.onWebGLContextCreationError(e);
10052
- }
10053
- }
10054
- reportWebGlContextLost(e) {
10055
- e.preventDefault();
10056
- if (this.onWebGLContextLost) {
10057
- this.onWebGLContextLost(e);
10058
- }
10059
- this.contextLost = true;
10060
- }
10061
- reportWebGLContextRestored() {
10062
- if (this.onWebGLContextRestored) {
10063
- this.onWebGLContextRestored();
10064
- }
10065
- this.contextLost = false;
10066
- }
10067
- /**
10068
- * Sometimes we have to use WebGL 1 because of a regression in Chrome
10069
- * due to some unknown performance bug in Apple's OpenGL Angle backend on MacBook+Radeon GPU.
10070
- * The official recommendation is to turn on Metal backed ANGLE in your Chrome's about:flags.
10071
- * We can't force our users to do it, so we have to support both WebGL 1 & WebGL 2
10072
- * See: https://bugs.chromium.org/p/chromium/issues/detail?id=1245448
10073
- * The Method returns target WebGL version based on the hardware configuration.
10074
- *
10075
- * @param context WebGL context
10076
- * @returns {1|2} WebGL version
10077
- */
10078
- getTargetWebGLVersion(context) {
10079
- const webkit = /(AppleWebKit)/.test(navigator.userAgent);
10080
- const macOS = /(Mac)/.test(navigator.platform);
10081
- const debugInfo = context.getExtension('WEBGL_debug_renderer_info');
10082
- const problemGPUsRegexp = /radeon\spro\s4\d\d|radeon\spro\s5\d\d/i;
10083
- const problemGPUs = problemGPUsRegexp.test(context.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL));
10084
- return webkit && macOS && problemGPUs ? 1 : 2;
10085
- }
10086
- /**
10087
- *
10088
- * @param options
10089
- * @returns {SingleBufferRenderer|MultiBufferRenderer}
10090
- */
10091
- getRenderer(options) {
10092
- const BufferRenderer = options.multiBufferRendering ? MultiBufferRenderer : SingleBufferRenderer;
10093
- const bufferRenderer = new BufferRenderer(options);
10094
- const targetWebGLVersion = this.getTargetWebGLVersion(bufferRenderer.renderer.getContext());
10095
- if (targetWebGLVersion === 2) {
10096
- return bufferRenderer;
10097
- }
10098
- return new BufferRenderer(options, targetWebGLVersion);
10099
- }
10100
- }
10101
- /**
10102
- * A legacy renderer that renders all the elements in the scene to a single
10103
- * buffer. Does not support things like transparent paths behind polygons
10104
- * natively, but can potentially fake it.
10105
- *
10106
- * 2019/07/10 Terence Dickson
10107
- * Generally has worse performance under average loads compared to the
10108
- * multi-buffer renderer, as the multi-buffer renderer can avoid re-rendering
10109
- * the entire map if only animated elements on the map have changed.
10110
- *
10111
- * @class SingleBufferRenderer
10112
- * @private
10113
- */
10114
- class SingleBufferRenderer {
10115
- constructor(renderOptions, targetWebGLVersion = 2) {
10116
- try {
10117
- const options = {
10118
- alpha: renderOptions.alpha || false,
10119
- stencil: true,
10120
- outputEncoding: sRGBEncoding,
10121
- antialias: renderOptions.antialias,
10122
- powerPreference: 'high-performance',
10123
- preserveDrawingBuffer: true,
10124
- };
10125
- this.renderer = targetWebGLVersion === 2 ? new WebGLRenderer(options) : new WebGL1Renderer(options);
10126
- }
10127
- catch (e) {
10128
- if (renderOptions.onWebGLRendererError) {
10129
- renderOptions.onWebGLRendererError(e);
10130
- }
10131
- }
10132
- this.backgroundColor = new Color();
10133
- this.backgroundAlpha = 1.0;
10134
- this.mapboxOutdoorContext = null;
10135
- }
10136
- /**
10137
- * Dispose of the renderer.
10138
- */
10139
- destroy() {
10140
- this.renderer.dispose();
10141
- }
10142
- /**
10143
- * Re-render the scene. This ignores the `renderTask` argument, as all
10144
- * re-renders in the single-buffer renderer are full re-renders.
10145
- *
10146
- * @method render
10147
- * @param renderTask {RENDER}
10148
- * @param renderTarget {null or WebGLRenderTarget}
10149
- * @param scene {Scene}
10150
- * @param sceneCamera {Camera}
10151
- */
10152
- render(renderTask, renderTarget, scene, sceneCamera) {
10153
- sceneCamera.layers.enable(CAMERA_LAYER.STATIC);
10154
- sceneCamera.layers.enable(CAMERA_LAYER.ANIMATED);
10155
- sceneCamera.layers.enable(CAMERA_LAYER.ALWAYS_ON_TOP);
10156
- this.renderer.autoClear = false;
10157
- this.renderer.setClearColor(this.backgroundColor, this.backgroundAlpha);
10158
- this.renderer.setRenderTarget(renderTarget);
10159
- this.renderer.clear();
10160
- if (SHOW_FPS) {
10161
- stats.begin();
10162
- }
10163
- this.renderer.render(scene, sceneCamera);
10164
- if (SHOW_FPS) {
10165
- stats.end();
10166
- }
10167
- if (this.mapboxOutdoorContext != null) {
10168
- this.renderer.state.reset();
10169
- this.mapboxOutdoorContext.render({
10170
- resolution: this.renderer.getSize(new Vector2()),
10171
- camera: sceneCamera,
10172
- });
10173
- this.renderer.state.reset();
10174
- }
10175
- }
10176
- /**
10177
- * Set the size of the renderer.
10178
- *
10179
- * @method setBufferSize
10180
- * @param width {number}
10181
- * @param height {number}
10182
- */
10183
- setBufferSize(width, height) {
10184
- this.renderer.setSize(width, height, false);
10185
- }
10186
- /**
10187
- * Set the color and opacity that will be drawn behind the scene.
10188
- *
10189
- * @method setBackgroundColor
10190
- * @param color {Color}
10191
- * @param alpha {number}
10192
- */
10193
- setBackgroundColor(color, alpha) {
10194
- this.backgroundColor = color;
10195
- this.backgroundAlpha = alpha;
10196
- }
10197
- /**
10198
- * Assign an outdoor context to this renderer.
10199
- *
10200
- * @method setMapboxOutdoorContext
10201
- * @param {MapboxOutdoorContext} mapboxOutdoorContext context to draw
10202
- */
10203
- setMapboxOutdoorContext(mapboxOutdoorContext) {
10204
- this.mapboxOutdoorContext = mapboxOutdoorContext;
10205
- }
10206
- }
10207
- /**
10208
- * Our new renderer that renders static elements to one buffer and animated
10209
- * elements to another buffer, allowing only the latter buffer to be
10210
- * re-rendered if the camera isn't moving. There is also a third buffer that
10211
- * can be used to render elements that must appear always on top.
10212
- *
10213
- * Does not yet support antialiasing.
10214
- *
10215
- * 2019/07/10 Terence Dickson
10216
- * Generally has better performance under average loads compared to the
10217
- * single-buffer renderer, but does not work on IE 11.
10218
- *
10219
- * @class MultiBufferRenderer
10220
- * @private
10221
- */
10222
- class MultiBufferRenderer {
10223
- constructor(renderOptions, targetWebGLVersion = 2) {
10224
- try {
10225
- const options = {
10226
- alpha: renderOptions.alpha || false,
10227
- stencil: true,
10228
- outputEncoding: sRGBEncoding,
10229
- powerPreference: 'high-performance',
10230
- preserveDrawingBuffer: true,
10231
- };
10232
- this.renderer = targetWebGLVersion === 2 ? new WebGLRenderer(options) : new WebGL1Renderer(options);
10233
- }
10234
- catch (e) {
10235
- if (renderOptions.onWebGLRendererError) {
10236
- renderOptions.onWebGLRendererError(e);
10237
- }
10238
- }
10239
- this.journeyOpacity = renderOptions.xRayPath ? 0.3 : 0.0;
10240
- this.backgroundColor = new Color();
10241
- this.backgroundAlpha = 1.0;
10242
- this.mapboxOutdoorContext = null;
10243
- // Antialiasing for WebGLRenderTargets is done through the samples option
10244
- // Note: this only works for WebGL 2
10245
- // TODO: For some reason this is breaking path opacity but since antialiasing only affects DPI 1 monitors, we'll just turn it off for multi-buffer
10246
- const samples = 0; // renderOptions.antialias ? 4 : 0;
10247
- // The render target (color and depth textures) that will be used to
10248
- // store the static scene, to composite it with the animated scene.
10249
- this.staticSceneRenderTarget = new WebGLRenderTarget(this.renderer.width, this.renderer.height);
10250
- this.staticSceneRenderTarget.texture.format = RGBAFormat;
10251
- this.staticSceneRenderTarget.texture.minFilter = NearestFilter;
10252
- this.staticSceneRenderTarget.texture.magFilter = NearestFilter;
10253
- this.staticSceneRenderTarget.texture.generateMipmaps = false;
10254
- this.staticSceneRenderTarget.stencilBuffer = true;
10255
- this.staticSceneRenderTarget.depthBuffer = true;
10256
- this.staticSceneRenderTarget.depthTexture = new DepthTexture(this.renderer.width, this.renderer.height);
10257
- this.staticSceneRenderTarget.depthTexture.type = UnsignedIntType;
10258
- this.staticSceneRenderTarget.samples = samples;
10259
- // The render target that will be used to store the dynamic scene,
10260
- // re-rendered once per frame while animated elements exist in the
10261
- // 3D scene (e.g., paths.)
10262
- this.animatedSceneRenderTarget = new WebGLRenderTarget(this.renderer.width, this.renderer.height);
10263
- this.animatedSceneRenderTarget.texture.format = RGBAFormat;
10264
- this.animatedSceneRenderTarget.stencilBuffer = true;
10265
- this.animatedSceneRenderTarget.texture.minFilter = NearestFilter;
10266
- this.animatedSceneRenderTarget.texture.magFilter = NearestFilter;
10267
- this.animatedSceneRenderTarget.texture.generateMipmaps = false;
10268
- this.animatedSceneRenderTarget.stencilBuffer = true;
10269
- this.animatedSceneRenderTarget.depthBuffer = true;
10270
- this.animatedSceneRenderTarget.depthTexture = new DepthTexture(this.renderer.width, this.renderer.height);
10271
- this.animatedSceneRenderTarget.depthTexture.type = UnsignedIntType;
10272
- this.animatedSceneRenderTarget.samples = samples;
10273
- // The render target that will be used to store the scene whose elements
10274
- // are always drawn on top of everything else. Rendering is done once per
10275
- // frame while such elements exist in the 3D scene, just like the animated scene target.
10276
- // 2020/08/18 - Daniel Vijayakumar
10277
- // This buffer should be used for animated objects that must appear on top of everything
10278
- // else. I can't think of any use-cases where you'd ever want *static* elements to be
10279
- // always on top. If this is not adequate, we can solve this problem with yet a 4th buffer.
10280
- // although this would create a combinatoric explosion of render passes and might start
10281
- // eating into the gains of the multi-buffer approach.
10282
- this.alwaysOnTopSceneRenderTarget = new WebGLRenderTarget(this.renderer.width, this.renderer.height);
10283
- this.alwaysOnTopSceneRenderTarget.texture.format = RGBAFormat;
10284
- this.alwaysOnTopSceneRenderTarget.texture.minFilter = NearestFilter;
10285
- this.alwaysOnTopSceneRenderTarget.texture.magFilter = NearestFilter;
10286
- this.alwaysOnTopSceneRenderTarget.texture.generateMipmaps = false;
10287
- this.alwaysOnTopSceneRenderTarget.stencilBuffer = true;
10288
- this.alwaysOnTopSceneRenderTarget.depthBuffer = true;
10289
- this.alwaysOnTopSceneRenderTarget.depthTexture = new DepthTexture(this.renderer.width, this.renderer.height);
10290
- this.alwaysOnTopSceneRenderTarget.depthTexture.type = UnsignedIntType;
10291
- this.alwaysOnTopSceneRenderTarget.samples = samples;
10292
- this.compositeUniforms = {
10293
- animatedColorOpacity: {
10294
- type: 'f',
10295
- value: this.journeyOpacity,
10296
- },
10297
- staticSceneColorTexture: {
10298
- type: 't',
10299
- value: this.staticSceneRenderTarget.texture,
10300
- },
10301
- staticSceneDepthTexture: {
10302
- type: 't',
10303
- value: this.staticSceneRenderTarget.depthTexture,
10304
- },
10305
- animatedSceneColorTexture: {
10306
- type: 't',
10307
- value: this.animatedSceneRenderTarget.texture,
10308
- },
10309
- animatedSceneDepthTexture: {
10310
- type: 't',
10311
- value: this.animatedSceneRenderTarget.depthTexture,
10312
- },
10313
- alwaysOnTopSceneColorTexture: {
10314
- type: 't',
10315
- value: this.alwaysOnTopSceneRenderTarget.texture,
10316
- },
10317
- };
10318
- // The scene that renders a full-screen quad that will sample from
10319
- // all render targets to produce the composited result.
10320
- this.compositeCamera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
10321
- this.compositeMaterial = new ShaderMaterial({
10322
- uniforms: this.compositeUniforms,
10323
- vertexShader: CompositeVertexShader,
10324
- fragmentShader: CompositeFragmentShader,
10325
- });
10326
- this.compositeFullscreenQuad = new Mesh(new PlaneBufferGeometry(2, 2), this.compositeMaterial);
10327
- this.compositeScene = new Scene();
10328
- this.compositeScene.add(this.compositeFullscreenQuad);
10329
- }
10330
- /**
10331
- * Dispose of the renderer and its buffers.
10332
- */
10333
- destroy() {
10334
- this.staticSceneRenderTarget.dispose();
10335
- this.animatedSceneRenderTarget.dispose();
10336
- this.alwaysOnTopSceneRenderTarget.dispose();
10337
- this.renderer.dispose();
10338
- }
10339
- /**
10340
- * Compose all buffers into the final image that is sent to the viewport.
10341
- *
10342
- * @private
10343
- * @method renderComposite
10344
- * @param renderTarget {null or WebGLRenderTarget}
10345
- */
10346
- renderComposite(renderTarget) {
10347
- this.renderer.setRenderTarget(renderTarget);
10348
- this.renderer.clear();
10349
- // 23/05/2019 Terence Dickson
10350
- // We need to disable depth writes on the fullscreen-quad, since
10351
- // otherwise it is considered "very close to the camera" and will
10352
- // obscure the path rendering. Keep an eye on this if you update
10353
- // three.js, since we're modifying properties on the WebGL context
10354
- // itself, and three.js might decide to override this in the future.
10355
- this.renderer.getContext().depthMask(false);
10356
- this.renderer.render(this.compositeScene, this.compositeCamera);
10357
- this.renderer.getContext().depthMask(true);
10358
- }
10359
- /**
10360
- * Renders the scene's animated layer to its dedicated buffer.
10361
- *
10362
- * @private
10363
- * @method renderToAnimatedBuffer
10364
- * @param {*} scene
10365
- * @param {*} sceneCamera
10366
- */
10367
- renderToAnimatedBuffer(scene, sceneCamera) {
10368
- sceneCamera.layers.set(CAMERA_LAYER.ANIMATED);
10369
- this.renderer.setClearColor(new Color(), 0.0);
10370
- this.renderer.setRenderTarget(this.animatedSceneRenderTarget);
10371
- this.renderer.clear();
10372
- this.renderer.render(scene, sceneCamera);
10373
- }
10374
- /**
10375
- * Render the provided scene using the given camera, re-rendering only
10376
- * the animated parts.
10377
- *
10378
- * @method renderAnimated
10379
- * @param renderTarget {null or WebGLRenderTarget}
10380
- * @param scene {Scene}
10381
- * @param sceneCamera {Camera}
10382
- */
10383
- renderAnimated(renderTarget, scene, sceneCamera) {
10384
- this.renderToAnimatedBuffer(scene, sceneCamera);
10385
- this.renderComposite(renderTarget);
10386
- }
10387
- /**
10388
- * Renders the scene's always-on-top layer to its dedicated buffer.
10389
- *
10390
- * @private
10391
- * @method renderToAlwaysOnTopBuffer
10392
- * @param {*} scene
10393
- * @param {*} sceneCamera
10394
- */
10395
- renderToAlwaysOnTopBuffer(scene, sceneCamera) {
10396
- sceneCamera.layers.set(CAMERA_LAYER.ALWAYS_ON_TOP);
10397
- this.renderer.setClearColor(new Color(), 0.0);
10398
- this.renderer.setRenderTarget(this.alwaysOnTopSceneRenderTarget);
10399
- this.renderer.clear();
10400
- this.renderer.render(scene, sceneCamera);
10401
- }
10402
- /**
10403
- * Render the provided scene using the given camera, re-rendering only
10404
- * the parts that should appear on top of all other elements.
10405
- *
10406
- * @method renderAlwaysOnTop
10407
- * @param renderTarget {null or WebGLRenderTarget}
10408
- * @param scene {Scene}
10409
- * @param sceneCamera {Camera}
10410
- */
10411
- renderAlwaysOnTop(renderTarget, scene, sceneCamera) {
10412
- this.renderToAlwaysOnTopBuffer(scene, sceneCamera);
10413
- this.renderComposite(renderTarget);
10414
- }
10415
- /**
10416
- * Render all of the scene layers, then combine them.
10417
- *
10418
- * @method renderAll
10419
- * @param renderTarget {null or WebGLRenderTarget}
10420
- * @param scene {Scene}
10421
- * @param sceneCamera {Camera}
10422
- */
10423
- renderAll(renderTarget, scene, sceneCamera) {
10424
- // First, render to static buffer
10425
- sceneCamera.layers.set(CAMERA_LAYER.STATIC);
10426
- this.renderer.autoClear = false;
10427
- this.renderer.setClearColor(this.backgroundColor, this.backgroundAlpha);
10428
- this.renderer.setRenderTarget(this.staticSceneRenderTarget);
10429
- this.renderer.clear();
10430
- this.renderer.render(scene, sceneCamera);
10431
- // Then render mapbox outdoor context
10432
- if (this.mapboxOutdoorContext != null) {
10433
- this.renderer.state.reset();
10434
- this.mapboxOutdoorContext.render({
10435
- resolution: this.renderer.getSize(new Vector2()),
10436
- camera: sceneCamera,
10437
- renderTarget: this.staticSceneRenderTarget,
10438
- });
10439
- this.renderer.state.reset();
10440
- }
10441
- // Next, render the remaining buffers
10442
- this.renderToAnimatedBuffer(scene, sceneCamera);
10443
- this.renderToAlwaysOnTopBuffer(scene, sceneCamera);
10444
- // Finally, composite the scene
10445
- this.renderComposite(renderTarget);
10446
- }
10447
- /**
10448
- * Re-render the scene, depending on which parts of the scene have been
10449
- * invalidated.
10450
- *
10451
- * @method render
10452
- * @param renderTarget {null or WebGLRenderTarget}
10453
- * @param scene {Scene}
10454
- * @param sceneCamera {Camera}
10455
- */
10456
- render(renderTask, renderTarget, scene, sceneCamera) {
10457
- if (renderTask === RENDER.ANIMATED) {
10458
- this.renderAnimated(renderTarget, scene, sceneCamera);
10459
- }
10460
- else if (renderTask === RENDER.ALWAYS_ON_TOP) {
10461
- this.renderAlwaysOnTop(renderTarget, scene, sceneCamera);
10462
- }
10463
- else if (renderTask === RENDER.ALL) {
10464
- this.renderAll(renderTarget, scene, sceneCamera);
10465
- }
10466
- else {
10467
- Logger.error('Renderer.render called with invalid renderTask');
10468
- }
10469
- }
10470
- /**
10471
- * Set the size of the renderer, and all its internal buffers.
10472
- *
10473
- * @method setBufferSize
10474
- * @param width {number}
10475
- * @param height {number}
10476
- */
10477
- setBufferSize(width, height) {
10478
- this.renderer.setSize(width, height, false);
10479
- this.staticSceneRenderTarget.setSize(width, height);
10480
- this.animatedSceneRenderTarget.setSize(width, height);
10481
- this.alwaysOnTopSceneRenderTarget.setSize(width, height);
10482
- }
10483
- /**
10484
- * Set the color and opacity that will be drawn behind the scene.
10485
- *
10486
- * @method setBackgroundColor
10487
- * @param color {Color}
10488
- * @param alpha {number}
10489
- */
10490
- setBackgroundColor(color, alpha) {
10491
- this.backgroundColor = color;
10492
- this.backgroundAlpha = alpha;
10493
- }
10494
- /**
10495
- * Assign an outdoor context to this renderer.
10496
- *
10497
- * @method setMapboxOutdoorContext
10498
- * @param {MapboxOutdoorContext} mapboxOutdoorContext context to draw
10499
- */
10500
- setMapboxOutdoorContext(mapboxOutdoorContext) {
10501
- this.mapboxOutdoorContext = mapboxOutdoorContext;
10502
- }
10503
- }
10504
-
10505
7601
  declare module '@mappedin/mappedin-js/renderer/internal/utils' {
10506
7602
  /**
10507
7603
  * Utils function listing
@@ -11364,24 +8460,6 @@ declare module '@mappedin/mappedin-js/renderer/internal/quad-tree' {
11364
8460
  export { QuadTree, Rectangle };
11365
8461
  }
11366
8462
 
11367
- declare module '@mappedin/mappedin-js/renderer/internal/Mappedin.CameraControls.TouchAnchor' {
11368
- export default TouchAnchor;
11369
- /**
11370
- * An object representing a touch event anchored to a point along the z=0 plane.
11371
- * @type {any}
11372
- */
11373
- let TouchAnchor: any;
11374
- }
11375
-
11376
- declare module '@mappedin/mappedin-js/renderer/internal/Mappedin.CameraControls.InputSet' {
11377
- export default InputSet;
11378
- /**
11379
- *
11380
- * @type {any}
11381
- */
11382
- let InputSet: any;
11383
- }
11384
-
11385
8463
  declare module '@mappedin/mappedin-js/renderer/internal/Mappedin.MultiFloorView' {
11386
8464
  export namespace VIEW_STATE {
11387
8465
  const SINGLE_FLOOR: string;