• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

epam / miew / 10463708857

20 Aug 2024 01:31AM UTC coverage: 25.125% (+0.02%) from 25.11%
10463708857

push

github

web-flow
Access the top window in a safe manner (#550)

Fixes Uncaught DOMException: Failed to read a named property
'addEventListener' from 'Window': Blocked a frame with origin
"xxx" from accessing a cross-origin frame.

1443 of 5703 branches covered (25.3%)

Branch coverage included in aggregate %.

4 of 8 new or added lines in 3 files covered. (50.0%)

1 existing line in 1 file now uncovered.

3968 of 15833 relevant lines covered (25.06%)

47529.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/miew/src/ui/ObjectControls.js
1
import * as THREE from 'three';
2
import Timer from '../Timer';
3
import settings from '../settings';
4
import EventDispatcher from '../utils/EventDispatcher';
5
import getTopWindow from '../utils/getTopWindow';
6

7
const VK_LEFT = 37;
×
8
const VK_UP = 38;
×
9
const VK_RIGHT = 39;
×
10
const VK_DOWN = 40;
×
11

12
const STATE = {
×
13
  NONE: -1, ROTATE: 0, TRANSLATE: 1, SCALE: 2, TRANSLATE_PIVOT: 3,
14
};
15

16
// pausing for this amount of time before releasing mouse button prevents inertial rotation (seconds)
17
const FULL_STOP_THRESHOLD = 0.1;
×
18

19
const quaternion = new THREE.Quaternion();
×
20
const matrix4 = new THREE.Matrix4();
×
21

22
// pivot -- local offset of the rotation pivot point
23
function ObjectHandler(objects, camera, pivot, options) {
24
  this.objects = objects;
×
25
  [this.object] = objects;
×
26
  this.camera = camera;
×
27
  this.pivot = pivot;
×
28
  this.axis = new THREE.Vector3(0, 0, 1);
×
29
  this.options = options;
×
30

31
  this.lastRotation = {
×
32
    axis: new THREE.Vector3(),
33
    angle: 0.0,
34
  };
35
}
36

37
ObjectHandler.prototype._rotate = (function () {
×
38
  const p = new THREE.Vector3();
×
39
  const q = new THREE.Quaternion();
×
40
  const s = new THREE.Vector3();
×
41

42
  const m = new THREE.Matrix4();
×
43

44
  return function (quat) {
×
45
    const zeroPivot = (this.pivot.x === 0.0 && this.pivot.y === 0.0 && this.pivot.z === 0.0);
×
46

47
    m.copy(this.object.matrix);
×
48

49
    if (zeroPivot) {
×
50
      m.multiply(matrix4.makeRotationFromQuaternion(quat));
×
51
    } else {
52
      m.multiply(matrix4.makeTranslation(this.pivot.x, this.pivot.y, this.pivot.z));
×
53
      m.multiply(matrix4.makeRotationFromQuaternion(quat));
×
54
      m.multiply(matrix4.makeTranslation(-this.pivot.x, -this.pivot.y, -this.pivot.z));
×
55
    }
56

57
    m.decompose(p, q, s);
×
58

59
    // update objects
60
    if (!zeroPivot) {
×
61
      for (let i = 0; i < this.objects.length; ++i) {
×
62
        this.objects[i].position.copy(p);
×
63
      }
64
    }
65

66
    for (let j = 0; j < this.objects.length; ++j) {
×
67
      this.objects[j].quaternion.copy(q);
×
68
      this.objects[j].updateMatrix();
×
69
    }
70
  };
71
}());
72

73
ObjectHandler.prototype.setObjects = function (objects) {
×
74
  this.objects = objects;
×
75
  [this.object] = objects;
×
76
};
77

78
ObjectHandler.prototype.rotate = (function () {
×
79
  const rot = {
×
80
    axis: new THREE.Vector3(),
81
    angle: 0.0,
82
  };
83

84
  return function (quat, mousePrevPos, mouseCurPos, aboutAxis) {
×
85
    this.mouse2rotation(rot, mousePrevPos, mouseCurPos, aboutAxis);
×
86
    quat.setFromAxisAngle(rot.axis, rot.angle);
×
87

88
    if (rot.angle) {
×
89
      this._rotate(quat);
×
90
    }
91

92
    this.lastRotation = rot;
×
93
  };
94
}());
95

96
ObjectHandler.prototype.translate = (function () {
×
97
  const dir = new THREE.Vector3();
×
98
  const pivot = new THREE.Vector3();
×
99

100
  return function (delta) {
×
101
    // reverse-project viewport movement to view coords (compensate for screen aspect ratio)
102
    dir.set(
×
103
      delta.x / this.camera.projectionMatrix.elements[0],
104
      delta.y / this.camera.projectionMatrix.elements[5],
105
      0,
106
    );
107
    let dist = dir.length();
×
108
    dir.normalize();
×
109

110
    // transform movement direction to object local coords
111
    dir.transformDirection(matrix4.copy(this.object.matrixWorld).invert());
×
112

113
    // visible translate distance shouldn't depend on camera-to-object distance
114
    pivot.copy(this.pivot);
×
115
    this.object.localToWorld(pivot);
×
116
    dist *= Math.abs(pivot.z - this.camera.position.z);
×
117

118
    // visible translate distance shouldn't depend on object scale
119
    dist /= this.object.matrixWorld.getMaxScaleOnAxis();
×
120

121
    // all objects are translated similar to principal object
122
    // (we assume they all have identical pivot and scale)
123
    for (let i = 0; i < this.objects.length; ++i) {
×
124
      this.objects[i].translateOnAxis(dir, dist);
×
125
    }
126
  };
127
}());
128

129
ObjectHandler.prototype.update = (function () {
×
130
  const axis = new THREE.Vector3();
×
131

132
  return function (timeSinceLastUpdate, timeSinceMove) {
×
133
    if (settings.now.autoRotation !== 0.0) {
×
134
      // auto-rotation with constant speed
135

136
      // if rotation axis is fixed or hasn't been defined yet
137
      if (settings.now.autoRotationAxisFixed || this.lastRotation.axis.length() === 0.0) {
×
138
        // use Y-axis (transformed to local object coords)
139
        axis.set(0, 1, 0).transformDirection(matrix4.copy(this.object.matrixWorld).invert());
×
140
      } else {
141
        // use axis defined by last user rotation
142
        axis.copy(this.lastRotation.axis);
×
143
      }
144

145
      this._rotate(quaternion.setFromAxisAngle(axis, settings.now.autoRotation * timeSinceLastUpdate));
×
146
      return true;
×
147
    }
148

149
    if (this.options.intertia && this.lastRotation.angle) {
×
150
      // inertial object rotation
151
      const angle = this.lastRotation.angle * ((1.0 - this.options.dynamicDampingFactor) ** (40.0 * timeSinceMove));
×
152

153
      if (Math.abs(angle) <= this.options.intertiaThreshold) {
×
154
        this.lastRotation.angle = 0.0;
×
155
      } else {
156
        this._rotate(quaternion.setFromAxisAngle(this.lastRotation.axis, angle));
×
157
        return true;
×
158
      }
159
    }
160

161
    return false;
×
162
  };
163
}());
164

165
ObjectHandler.prototype.stop = function () {
×
166
  this.lastRotation.angle = 0.0;
×
167
};
168

169
// calculate (axis, angle) pair from mouse/touch movement
170
ObjectHandler.prototype.mouse2rotation = (function () {
×
171
  const center = new THREE.Vector3();
×
172

173
  const eye = new THREE.Vector3();
×
174
  const eyeDirection = new THREE.Vector3();
×
175

176
  const cameraUpDirection = new THREE.Vector3();
×
177
  const cameraSidewaysDirection = new THREE.Vector3();
×
178

179
  const moveDirection = new THREE.Vector3();
×
180

181
  const mouseDelta = new THREE.Vector2();
×
182

183
  return function (rot, mousePrev, mouseCur, aboutAxis) {
×
184
    if (aboutAxis) {
×
185
      rot.axis.copy(this.axis);
×
186
      rot.angle = this.options.axisRotateFactor * (mouseCur.y - mousePrev.y);
×
187

188
      /* cool method that allows rotation around Z axis to be "tied" to mouse cursor
189

190
        res.axis.copy(this.axis);
191

192
        var pivot = this.pivot.clone();
193
        this.object.localToWorld(pivot);
194
        pivot.project(this.camera);
195

196
        var v1 = new THREE.Vector3(mousePrev.x, mousePrev.y, this.camera.position.z);
197
        v1.sub(pivot);
198
        var v2 = new THREE.Vector3(mouseCur.x, mouseCur.y, this.camera.position.z);
199
        v2.sub(pivot);
200

201
        v1.sub(res.axis.clone().multiplyScalar(v1.dot(res.axis)));
202
        v2.sub(res.axis.clone().multiplyScalar(v2.dot(res.axis)));
203

204
        var abs = v1.length() * v2.length();
205
        if (abs > 0) {
206
          res.angle = res.axis.dot(v1.cross(v2)) / abs;
207
        }
208
      */
209
    } else {
210
      mouseDelta.subVectors(mouseCur, mousePrev);
×
211
      const angle = mouseDelta.length();
×
212
      if (angle === 0.0) {
×
213
        return;
×
214
      }
215

216
      center.copy(this.pivot);
×
217
      this.object.localToWorld(center);
×
218
      eye.subVectors(this.camera.position, center);
×
219
      eyeDirection.copy(eye).normalize();
×
220

221
      cameraUpDirection.copy(this.camera.up).normalize();
×
222
      cameraSidewaysDirection.crossVectors(cameraUpDirection, eyeDirection).normalize();
×
223

224
      cameraUpDirection.setLength(mouseDelta.y);
×
225
      cameraSidewaysDirection.setLength(mouseDelta.x);
×
226

227
      moveDirection.copy(cameraUpDirection.add(cameraSidewaysDirection));
×
228

229
      rot.axis.crossVectors(moveDirection, eye);
×
230

231
      rot.angle = -angle * this.options.rotateFactor;
×
232
    }
233

234
    rot.axis.transformDirection(matrix4.copy(this.object.matrixWorld).invert());
×
235

236
    // make sure angle is always positive (thus 'axis' defines both axis and direction of rotation)
237
    if (rot.angle < 0.0) {
×
238
      rot.axis.negate();
×
239
      rot.angle = -rot.angle;
×
240
    }
241
  };
242
}());
243

244
function ObjectControls(object, objectPivot, camera, domElement, getAltObj) {
245
  EventDispatcher.call(this);
×
246
  const self = this;
×
247

248
  this.object = object;
×
249
  this.objectPivot = objectPivot;
×
250
  this.camera = camera;
×
251
  this.domElement = (typeof domElement !== 'undefined') ? domElement : document;
×
252
  this.getAltObj = getAltObj;
×
253

254
  // API
255

256
  this.enabled = true;
×
257
  this.hotkeysEnabled = true;
×
258

259
  this.screen = {
×
260
    left: 0, top: 0, width: 0, height: 0,
261
  };
262

263
  this.options = {
×
264
    rotateFactor: Math.PI, // full screen slide (along short side) would roughly mean 180 deg. rotation
265
    axisRotateFactor: 4 * Math.PI, // full screen slide (along short side) would roughly mean 720 deg. rotation
266
    intertia: true,
267
    dynamicDampingFactor: 0.1,
268
    intertiaThreshold: 1e-3,
269
  };
270

271
  // internals
272

273
  this._state = STATE.NONE;
×
274

275
  this._mousePrevPos = new THREE.Vector2();
×
276
  this._mouseCurPos = new THREE.Vector2();
×
277

278
  this._mainObj = new ObjectHandler([this.object], this.camera, new THREE.Vector3(0, 0, 0), this.options);
×
279
  this._altObj = new ObjectHandler([this.object], this.camera, new THREE.Vector3(0, 0, 0), this.options);
×
280
  this._affectedObj = this._mainObj;
×
281
  this._isAltObjFreeRotationAllowed = true;
×
282
  this._isTranslationAllowed = true;
×
283
  this._isKeysTranslatingObj = false;
×
284

285
  this._pressedKeys = [];
×
286

287
  this._clock = new Timer();
×
288
  this._clock.start();
×
289
  this._lastUpdateTime = this._clock.getElapsedTime();
×
290

291
  // events
292
  this._listeners = [
×
293
    {
294
      obj: self.domElement,
295
      type: 'mousedown',
296
      handler(e) {
297
        self.mousedown(e);
×
298
      },
299
    },
300
    {
301
      obj: self.domElement,
302
      type: 'mouseup',
303
      handler(e) {
304
        self.mouseup(e);
×
305
      },
306
    },
307
    {
308
      obj: self.domElement,
309
      type: 'mousemove',
310
      handler(e) {
311
        self.mousemove(e);
×
312
      },
313
    },
314
    {
315
      obj: self.domElement,
316
      type: 'mousewheel',
317
      handler(e) {
318
        self.mousewheel(e);
×
319
      },
320
    },
321
    {
322
      obj: self.domElement,
323
      type: 'DOMMouseScroll',
324
      handler(e) {
325
        self.mousewheel(e);
×
326
      },
327
    },
328
    {
329
      obj: self.domElement,
330
      type: 'mouseout',
331
      handler(e) {
332
        self.mouseup(e);
×
333
      },
334
    },
335
    {
336
      obj: self.domElement,
337
      type: 'touchstart',
338
      handler(e) {
339
        self.touchstartend(e);
×
340
      },
341
    },
342
    {
343
      obj: self.domElement,
344
      type: 'touchend',
345
      handler(e) {
346
        self.touchstartend(e);
×
347
      },
348
    },
349
    {
350
      obj: self.domElement,
351
      type: 'touchmove',
352
      handler(e) {
353
        self.touchmove(e);
×
354
      },
355
    },
356
    {
357
      obj: self.getKeyBindObject(),
358
      type: 'keydown',
359
      handler(e) {
360
        self.keydownup(e);
×
361
      },
362
    },
363
    {
364
      obj: self.getKeyBindObject(),
365
      type: 'keyup',
366
      handler(e) {
367
        self.keydownup(e);
×
368
      },
369
    },
370
    {
371
      obj: window,
372
      type: 'resize',
373
      handler() {
374
        self.handleResize();
×
375
      },
376
    },
377
    {
378
      obj: window,
379
      type: 'blur',
380
      handler() {
381
        self.resetKeys();
×
382
      },
383
    },
384
    {
385
      obj: self.domElement,
386
      type: 'contextmenu',
387
      handler(e) {
388
        self.contextmenu(e);
×
389
      },
390
    }];
391

392
  for (let i = 0; i < this._listeners.length; i++) {
×
393
    const l = this._listeners[i];
×
394
    l.obj.addEventListener(l.type, l.handler);
×
395
  }
396

397
  this.handleResize();
×
398

399
  this.resetKeys();
×
400

401
  // force an update at start
402
  this.update();
×
403
}
404

405
// methods
406

407
ObjectControls.prototype = Object.create(EventDispatcher.prototype);
×
408
ObjectControls.prototype.constructor = ObjectControls;
×
409

410
ObjectControls.prototype.resetKeys = function () {
×
411
  this._pressedKeys[VK_LEFT] = false;
×
412
  this._pressedKeys[VK_UP] = false;
×
413
  this._pressedKeys[VK_RIGHT] = false;
×
414
  this._pressedKeys[VK_DOWN] = false;
×
415
};
416

417
ObjectControls.prototype.contextmenu = function (e) {
×
418
  e.stopPropagation();
×
419
  e.preventDefault();
×
420
};
421

422
ObjectControls.prototype.handleResize = function () {
×
423
  if (this.domElement === document) {
×
424
    this.screen.left = 0;
×
425
    this.screen.top = 0;
×
426
    this.screen.width = window.innerWidth;
×
427
    this.screen.height = window.innerHeight;
×
428
  } else {
429
    const box = this.domElement.getBoundingClientRect();
×
430
    // adjustments come from similar code in the jquery offset() function
431
    const d = this.domElement.ownerDocument.documentElement;
×
432
    this.screen.left = box.left + window.pageXOffset - d.clientLeft;
×
433
    this.screen.top = box.top + window.pageYOffset - d.clientTop;
×
434
    this.screen.width = box.width;
×
435
    this.screen.height = box.height;
×
436
  }
437
};
438

439
ObjectControls.prototype.enable = function (enable) {
×
440
  this.enabled = enable;
×
441
};
442

443
ObjectControls.prototype.enableHotkeys = function (enable) {
×
444
  this.hotkeysEnabled = enable;
×
445
};
446

447
ObjectControls.prototype.allowTranslation = function (allow) {
×
448
  this._isTranslationAllowed = allow;
×
449
};
450

451
ObjectControls.prototype.allowAltObjFreeRotation = function (allow) {
×
452
  this._isAltObjFreeRotationAllowed = allow;
×
453
};
454

455
ObjectControls.prototype.keysTranslateObj = function (on) {
×
456
  this._isKeysTranslatingObj = on;
×
457
};
458

459
ObjectControls.prototype.isEditingAltObj = function () {
×
460
  return ((this._state === STATE.ROTATE) || (this._state === STATE.TRANSLATE))
×
461
            && (this._affectedObj === this._altObj);
462
};
463

464
// convert page coords of mouse/touch to uniform coords with smaller side being [-0.5, 0.5]
465
// (uniform coords keep direct proportion with screen distance travelled by mouse regardless of screen aspect ratio)
466
ObjectControls.prototype.convertMouseToOnCircle = function (coords, pageX, pageY) {
×
467
  const screenSize = Math.min(this.screen.width, this.screen.height);
×
468

469
  if (screenSize === 0) {
×
470
    coords.set(0, 0);
×
471
    return;
×
472
  }
473

474
  coords.set(
×
475
    ((pageX - this.screen.width * 0.5 - this.screen.left) / screenSize),
476
    ((0.5 * this.screen.height + this.screen.top - pageY) / screenSize),
477
  );
478
};
479

480
// convert page coords of mouse/touch to viewport coords with both sides being [-1, 1]
481
// (those are non-uniform coords affected by screen aspect ratio)
482
ObjectControls.prototype.convertMouseToViewport = function (coords, pageX, pageY) {
×
483
  if (this.screen.width === 0 || this.screen.height === 0) {
×
484
    coords.set(0, 0);
×
485
    return;
×
486
  }
487

488
  coords.set(
×
489
    (2.0 * (pageX - this.screen.width * 0.5 - this.screen.left) / this.screen.width),
490
    (2.0 * (0.5 * this.screen.height + this.screen.top - pageY) / this.screen.height),
491
  );
492
};
493

494
ObjectControls.prototype.stop = function () {
×
495
  this._mainObj.stop();
×
496
  this._altObj.stop();
×
497
};
498

499
// rotate object based on latest mouse/touch movement
500
ObjectControls.prototype.rotateByMouse = (function () {
×
501
  const quat = new THREE.Quaternion();
×
502

503
  return function (aboutZAxis) {
×
504
    this._affectedObj.rotate(quat, this._mousePrevPos, this._mouseCurPos, aboutZAxis);
×
505
    this.dispatchEvent({ type: 'change', action: 'rotate', quaternion: quat });
×
506
  };
507
}());
508

509
// rotate object by specified quaternion
510
ObjectControls.prototype.rotate = function (quat) {
×
511
  this.object.quaternion.multiply(quat);
×
512
  this.dispatchEvent({ type: 'change', action: 'rotate', quaternion: quat });
×
513
};
514

515
// get object's orientation
516
ObjectControls.prototype.getOrientation = function () {
×
517
  return this.object.quaternion;
×
518
};
519

520
// set object's orientation
521
ObjectControls.prototype.setOrientation = function (quat) {
×
522
  this.object.quaternion.copy(quat);
×
523
};
524

525
// translate object based on latest mouse/touch movement
526
ObjectControls.prototype.translate = (function () {
×
527
  const delta = new THREE.Vector2();
×
528
  return function () {
×
529
    delta.subVectors(this._mouseCurPos, this._mousePrevPos);
×
530
    this._affectedObj.translate(delta);
×
531
    this.dispatchEvent({ type: 'change', action: 'translate' });
×
532
  };
533
}());
534

535
// get object scale
536
ObjectControls.prototype.getScale = function () {
×
537
  return this.object.scale.x;
×
538
};
539

540
// set uniform object scale
541
ObjectControls.prototype.setScale = function (scale) {
×
542
  this.object.scale.set(scale, scale, scale);
×
543
};
544

545
// scale object by factor (factor should be greater than zero)
546
ObjectControls.prototype.scale = function (factor) {
×
547
  if (factor <= 0) {
×
548
    return;
×
549
  }
550
  this.setScale(this.object.scale.x * factor);
×
551
  this.dispatchEvent({ type: 'change', action: 'zoom', factor });
×
552
};
553

554
ObjectControls.prototype.update = (function () {
×
555
  const shift = new THREE.Vector2();
×
556

557
  return function () {
×
558
    const curTime = this._clock.getElapsedTime();
×
559
    const timeSinceLastUpdate = curTime - this._lastUpdateTime;
×
560

561
    // update object handler
562
    if (this._state === STATE.NONE) {
×
563
      const timeSinceMove = curTime - this._lastMouseMoveTime;
×
564
      if (this._mainObj.update(timeSinceLastUpdate, timeSinceMove)
×
565
        || this._altObj.update(timeSinceLastUpdate, timeSinceMove)) {
566
        this.dispatchEvent({ type: 'change', action: 'auto' });
×
567
      }
568
    }
569

570
    // apply arrow keys
571
    if (this._isKeysTranslatingObj) {
×
572
      const speedX = Number(this._pressedKeys[VK_RIGHT]) - Number(this._pressedKeys[VK_LEFT]);
×
573
      const speedY = Number(this._pressedKeys[VK_UP]) - Number(this._pressedKeys[VK_DOWN]);
×
574
      if (speedX !== 0.0 || speedY !== 0.0) {
×
575
        const delta = timeSinceLastUpdate;
×
576

577
        // update object translation
578
        const altObj = this.getAltObj();
×
579
        if (altObj.objects.length > 0) {
×
580
          this._altObj.setObjects(altObj.objects);
×
581
          this._altObj.pivot = altObj.pivot;
×
582

583
          if ('axis' in altObj) {
×
584
            this._altObj.axis = altObj.axis.clone();
×
585
          } else {
586
            this._altObj.axis.set(0, 0, 1);
×
587
          }
588

589
          shift.set(delta * speedX, delta * speedY);
×
590
          this._altObj.translate(shift);
×
591
          this.dispatchEvent({ type: 'change', action: 'translate' });
×
592
        }
593
      }
594
    }
595

596
    this._lastUpdateTime = curTime;
×
597
  };
598
}());
599

600
ObjectControls.prototype.reset = function () {
×
601
  this._state = STATE.NONE;
×
602

603
  this.object.quaternion.copy(quaternion.set(0, 0, 0, 1));
×
604
};
605

606
// listeners
607

608
ObjectControls.prototype.mousedown = function (event) {
×
609
  if (this.enabled === false || this._state !== STATE.NONE) {
×
610
    return;
×
611
  }
612

613
  event.preventDefault();
×
614
  event.stopPropagation();
×
615

616
  if (this._state === STATE.NONE) {
×
617
    if (event.button === 0) {
×
618
      this._affectedObj.stop(); // can edit only one object at a time
×
619

620
      let workWithAltObj = false;
×
621

622
      if (event.altKey) {
×
623
        const altObj = this.getAltObj();
×
624
        workWithAltObj = (altObj.objects.length > 0);
×
625
        if (workWithAltObj) {
×
626
          this._altObj.setObjects(altObj.objects);
×
627
          this._altObj.pivot = altObj.pivot;
×
628

629
          if ('axis' in altObj) {
×
630
            this._altObj.axis = altObj.axis.clone();
×
631
          } else {
632
            this._altObj.axis.set(0, 0, 1);
×
633
          }
634
        }
635
      }
636

637
      this._affectedObj = workWithAltObj ? this._altObj : this._mainObj;
×
638

639
      this._state = (workWithAltObj && event.ctrlKey && this._isTranslationAllowed) ? STATE.TRANSLATE : STATE.ROTATE;
×
640
    } else if (event.button === 2) {
×
641
      this._state = STATE.TRANSLATE_PIVOT;
×
642
    }
643
  }
644

645
  if (this._state === STATE.ROTATE) {
×
646
    this.convertMouseToOnCircle(this._mouseCurPos, event.pageX, event.pageY);
×
647
    this._mousePrevPos.copy(this._mouseCurPos);
×
648
  }
649

650
  if (this._state === STATE.TRANSLATE || this._state === STATE.TRANSLATE_PIVOT) {
×
651
    this.convertMouseToViewport(this._mouseCurPos, event.pageX, event.pageY);
×
652
    this._mousePrevPos.copy(this._mouseCurPos);
×
653
  }
654
};
655

656
ObjectControls.prototype.mousemove = function (event) {
×
657
  if (this.enabled === false || this._state === STATE.NONE) {
×
658
    return;
×
659
  }
660

661
  event.preventDefault();
×
662
  event.stopPropagation();
×
663

664
  switch (this._state) {
×
665
    case STATE.ROTATE:
666
      this._mousePrevPos.copy(this._mouseCurPos);
×
667
      this.convertMouseToOnCircle(this._mouseCurPos, event.pageX, event.pageY);
×
668
      this.rotateByMouse((event.altKey && !this._isAltObjFreeRotationAllowed) || event.shiftKey);
×
669
      this._lastMouseMoveTime = this._clock.getElapsedTime();
×
670
      break;
×
671

672
    case STATE.TRANSLATE:
673
      this._mousePrevPos.copy(this._mouseCurPos);
×
674
      this.convertMouseToViewport(this._mouseCurPos, event.pageX, event.pageY);
×
675
      this.translate();
×
676
      break;
×
677

678
    case STATE.TRANSLATE_PIVOT:
679
      this._mousePrevPos.copy(this._mouseCurPos);
×
680
      this.convertMouseToViewport(this._mouseCurPos, event.pageX, event.pageY);
×
681
      this.translatePivotByMouse();
×
682
      break;
×
683

684
    default: break;
×
685
  }
686
};
687

688
ObjectControls.prototype.mousewheel = function (event) {
×
689
  if (this.enabled === false || !settings.now.zooming || this._state !== STATE.NONE || event.shiftKey) {
×
690
    return;
×
691
  }
692

693
  event.preventDefault();
×
694

695
  let delta = 0;
×
696

697
  if (event.wheelDelta) {
×
698
    // WebKit / Opera / Explorer 9
699
    delta = event.wheelDelta / 40;
×
700
  } else if (event.detail) {
×
701
    // Firefox
702
    delta = -event.detail / 3;
×
703
  }
704

705
  let factor = 1.0 + delta * 0.05;
×
706
  factor = Math.max(factor, 0.01);
×
707
  this.scale(factor);
×
708
};
709

710
ObjectControls.prototype.mouseup = function (event) {
×
711
  if (this.enabled === false || this._state === STATE.NONE) {
×
712
    return;
×
713
  }
714

715
  event.preventDefault();
×
716
  event.stopPropagation();
×
717

718
  this._state = STATE.NONE;
×
719

720
  if (this._clock.getElapsedTime() - this._lastMouseMoveTime > FULL_STOP_THRESHOLD) {
×
721
    this._affectedObj.stop();
×
722
  }
723
};
724

725
ObjectControls.prototype.touchstartend = function (event) {
×
726
  if (this.enabled === false) {
×
727
    return;
×
728
  }
729

730
  event.preventDefault();
×
731
  event.stopPropagation();
×
732

733
  switch (event.touches.length) {
×
734
    case 1:
735
      this._state = STATE.ROTATE;
×
736
      this.convertMouseToOnCircle(this._mouseCurPos, event.touches[0].pageX, event.touches[0].pageY);
×
737
      this._mousePrevPos.copy(this._mouseCurPos);
×
738
      break;
×
739

740
    case 2: {
741
      // prevent inertial rotation
742
      this._mainObj.stop();
×
743
      this._altObj.stop();
×
744

745
      this._state = STATE.SCALE;
×
746
      const dx = event.touches[0].pageX - event.touches[1].pageX;
×
747
      const dy = event.touches[0].pageY - event.touches[1].pageY;
×
748
      this._touchDistanceCur = this._touchDistanceStart = Math.sqrt(dx * dx + dy * dy);
×
749
      this._scaleStart = this.object.scale.x;
×
750
      break;
×
751
    }
752

753
    default:
754
      this._state = STATE.NONE;
×
755
  }
756
};
757

758
ObjectControls.prototype.touchmove = function (event) {
×
759
  if (this.enabled === false || this._state === STATE.NONE) {
×
760
    return;
×
761
  }
762

763
  event.preventDefault();
×
764
  event.stopPropagation();
×
765

766
  switch (this._state) {
×
767
    case STATE.ROTATE:
768
      this._mousePrevPos.copy(this._mouseCurPos);
×
769
      this.convertMouseToOnCircle(this._mouseCurPos, event.touches[0].pageX, event.touches[0].pageY);
×
770
      this.rotateByMouse(false);
×
771

772
      this._lastMouseMoveTime = this._clock.getElapsedTime();
×
773
      break;
×
774

775
    case STATE.SCALE:
776
      if (settings.now.zooming) {
×
777
        // update scale
778
        const dx = event.touches[0].pageX - event.touches[1].pageX;
×
779
        const dy = event.touches[0].pageY - event.touches[1].pageY;
×
780
        this._touchDistanceCur = Math.sqrt(dx * dx + dy * dy);
×
781
        const newScale = this._scaleStart * this._touchDistanceCur / this._touchDistanceStart;
×
782
        const factor = newScale / this.object.scale.x;
×
783
        this.scale(factor);
×
784
      }
785
      break;
×
786

787
    default:
788
  }
789
};
790

791
ObjectControls.prototype.keydownup = function (event) {
×
792
  if (this.enabled === false || this.hotkeysEnabled === false) {
×
793
    return;
×
794
  }
795

796
  switch (event.keyCode) {
×
797
    case VK_LEFT:
798
    case VK_UP:
799
    case VK_RIGHT:
800
    case VK_DOWN:
801
      this._pressedKeys[event.keyCode] = (event.type === 'keydown');
×
802
      event.preventDefault();
×
803
      event.stopPropagation();
×
804
      break;
×
805
    default:
806
  }
807
};
808

809
ObjectControls.prototype.getKeyBindObject = function () {
×
NEW
810
  return getTopWindow();
×
811
};
812

813
ObjectControls.prototype.dispose = function () {
×
814
  for (let i = 0; i < this._listeners.length; i++) {
×
815
    const l = this._listeners[i];
×
816
    l.obj.removeEventListener(l.type, l.handler);
×
817
  }
818
};
819

820
ObjectControls.prototype.translatePivotByMouse = (function () {
×
821
  const delta = new THREE.Vector2();
×
822
  return function () {
×
823
    delta.subVectors(this._mouseCurPos, this._mousePrevPos);
×
824
    this.translatePivotInWorld(settings.now.translationSpeed * delta.x, settings.now.translationSpeed * delta.y, 0);
×
825
  };
826
}());
827

828
// Translate in WorldCS, translation is scaled with root scale matrix
829
ObjectControls.prototype.translatePivotInWorld = function (x, y, z) {
×
830
  const pos = this.objectPivot.position;
×
831
  pos.applyMatrix4(this.object.matrixWorld);
×
832
  pos.setX(pos.x + x);
×
833
  pos.setY(pos.y + y);
×
834
  pos.setZ(pos.z + z);
×
835
  pos.applyMatrix4(matrix4.copy(this.object.matrixWorld).invert());
×
836

837
  this.dispatchEvent({ type: 'change', action: 'translatePivot' });
×
838
};
839

840
// Translate in ModelCS, x, y, z are Ang
841
ObjectControls.prototype.translatePivot = function (x, y, z) {
×
842
  const pos = this.objectPivot.position;
×
843
  pos.setX(pos.x + x);
×
844
  pos.setY(pos.y + y);
×
845
  pos.setZ(pos.z + z);
×
846

847
  this.dispatchEvent({ type: 'change', action: 'translatePivot' });
×
848
};
849

850
// Set pivot
851
ObjectControls.prototype.setPivot = function (newPivot) {
×
852
  this.objectPivot.position.copy(newPivot);
×
853

854
  this.dispatchEvent({ type: 'change', action: 'translatePivot' });
×
855
};
856

857
export default ObjectControls;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc