MikaFil commited on
Commit
d2f0dae
·
verified ·
1 Parent(s): f8632dc

Create ctrl_camera_pr_env.js

Browse files
Files changed (1) hide show
  1. ctrl_camera_pr_env.js +672 -0
ctrl_camera_pr_env.js ADDED
@@ -0,0 +1,672 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // orbit-camera.js
2
+
3
+ var OrbitCamera = pc.createScript('orbitCamera');
4
+
5
+ // Camera attributes
6
+ OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max' });
7
+ OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
8
+ OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
9
+ OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min (degrees)' });
10
+ OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (degrees)' });
11
+ OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min (degrees)' });
12
+ OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y', description: 'Minimum Y value for camera world position' });
13
+
14
+ OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2, title: 'Inertia Factor' });
15
+ OrbitCamera.attributes.add('focusEntity', { type: 'entity', title: 'Focus Entity' });
16
+ OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true, title: 'Frame on Start' });
17
+
18
+ Object.defineProperty(OrbitCamera.prototype, 'distance', {
19
+ get: function () { return this._targetDistance; },
20
+ set: function (value) { this._targetDistance = this._clampDistance(value); }
21
+ });
22
+
23
+ Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
24
+ get: function () { return this.entity.camera.orthoHeight; },
25
+ set: function (value) { this.entity.camera.orthoHeight = Math.max(0, value); }
26
+ });
27
+
28
+ Object.defineProperty(OrbitCamera.prototype, 'pitch', {
29
+ get: function () { return this._targetPitch; },
30
+ set: function (value) { this._targetPitch = this._clampPitchAngle(value); }
31
+ });
32
+
33
+ Object.defineProperty(OrbitCamera.prototype, 'yaw', {
34
+ get: function () { return this._targetYaw; },
35
+ set: function (value) { this._targetYaw = this._clampYawAngle(value); }
36
+ });
37
+
38
+ Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
39
+ get: function () { return this._pivotPoint; },
40
+ set: function (value) { this._pivotPoint.copy(value); }
41
+ });
42
+
43
+ OrbitCamera.prototype.focus = function (focusEntity) {
44
+ this._buildAabb(focusEntity);
45
+ var halfExtents = this._modelsAabb.halfExtents;
46
+ var radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
47
+ this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
48
+ this._removeInertia();
49
+ this._pivotPoint.copy(this._modelsAabb.center);
50
+ };
51
+
52
+ OrbitCamera.distanceBetween = new pc.Vec3();
53
+
54
+ OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
55
+ this.pivotPoint.copy(lookAtPoint);
56
+ this.entity.setPosition(resetPoint);
57
+ this.entity.lookAt(lookAtPoint);
58
+ var distance = OrbitCamera.distanceBetween;
59
+ distance.sub2(lookAtPoint, resetPoint);
60
+ this.distance = distance.length();
61
+ this.pivotPoint.copy(lookAtPoint);
62
+ var cameraQuat = this.entity.getRotation();
63
+ this.yaw = this._calcYaw(cameraQuat);
64
+ this.pitch = this._calcPitch(cameraQuat, this.yaw);
65
+ this._removeInertia();
66
+ this._updatePosition();
67
+ };
68
+
69
+ OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
70
+ this._buildAabb(entity);
71
+ this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
72
+ };
73
+
74
+ OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
75
+ this.pitch = pitch;
76
+ this.yaw = yaw;
77
+ this.distance = distance;
78
+ this._removeInertia();
79
+ };
80
+
81
+ OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
82
+ this.entity.setPosition(position);
83
+ this.entity.lookAt(lookAtPoint);
84
+ this._pivotPoint.copy(lookAtPoint);
85
+ var distanceVec = new pc.Vec3();
86
+ distanceVec.sub2(position, lookAtPoint);
87
+ this._targetDistance = this._distance = distanceVec.length();
88
+ var cameraQuat = this.entity.getRotation();
89
+ this._targetYaw = this._yaw = this._calcYaw(cameraQuat);
90
+ this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
91
+ this._removeInertia();
92
+ this._updatePosition();
93
+ };
94
+
95
+ // ==== Helper: calc cam Y if pivot became 'pivot' (used to enforce minY) ====
96
+ OrbitCamera.prototype.worldCameraYForPivot = function(pivot) {
97
+ var quat = new pc.Quat();
98
+ quat.setFromEulerAngles(this._pitch, this._yaw, 0);
99
+ var forward = new pc.Vec3();
100
+ quat.transformVector(pc.Vec3.FORWARD, forward);
101
+ var camPos = pivot.clone();
102
+ camPos.add(forward.clone().scale(-this._distance));
103
+ return camPos.y;
104
+ };
105
+
106
+ ////////////////////////////////////////////////////////////////////////////////
107
+ // Private Methods //
108
+ ////////////////////////////////////////////////////////////////////////////////
109
+
110
+ OrbitCamera.prototype.initialize = function () {
111
+ var self = this;
112
+ var onWindowResize = function () { self._checkAspectRatio(); };
113
+ window.addEventListener('resize', onWindowResize, false);
114
+ this._checkAspectRatio();
115
+
116
+ this._modelsAabb = new pc.BoundingBox();
117
+ this._buildAabb(this.focusEntity || this.app.root);
118
+
119
+ this.entity.lookAt(this._modelsAabb.center);
120
+ this._pivotPoint = new pc.Vec3();
121
+ this._pivotPoint.copy(this._modelsAabb.center);
122
+
123
+ var cameraQuat = this.entity.getRotation();
124
+ this._yaw = this._calcYaw(cameraQuat);
125
+ this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
126
+ this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
127
+
128
+ this._distance = 0;
129
+ this._targetYaw = this._yaw;
130
+ this._targetPitch = this._pitch;
131
+
132
+ if (this.frameOnStart) {
133
+ this.focus(this.focusEntity || this.app.root);
134
+ } else {
135
+ var distanceBetween = new pc.Vec3();
136
+ distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
137
+ this._distance = this._clampDistance(distanceBetween.length());
138
+ }
139
+ this._targetDistance = this._distance;
140
+
141
+ this.on('attr:distanceMin', function () { this._distance = this._clampDistance(this._distance); });
142
+ this.on('attr:distanceMax', function () { this._distance = this._clampDistance(this._distance); });
143
+ this.on('attr:pitchAngleMin', function () { this._pitch = this._clampPitchAngle(this._pitch); });
144
+ this.on('attr:pitchAngleMax', function () { this._pitch = this._clampPitchAngle(this._pitch); });
145
+
146
+ this.on('attr:focusEntity', function (value) {
147
+ if (this.frameOnStart) { this.focus(value || this.app.root); }
148
+ else { this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root); }
149
+ });
150
+
151
+ this.on('attr:frameOnStart', function (value) {
152
+ if (value) { this.focus(this.focusEntity || this.app.root); }
153
+ });
154
+
155
+ this.on('destroy', function () { window.removeEventListener('resize', onWindowResize, false); });
156
+ };
157
+
158
+ OrbitCamera.prototype.update = function (dt) {
159
+ var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
160
+ this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
161
+ this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
162
+ this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
163
+ this._updatePosition();
164
+ };
165
+
166
+ OrbitCamera.prototype._updatePosition = function () {
167
+ this.entity.setLocalPosition(0, 0, 0);
168
+ this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
169
+
170
+ var position = this.entity.getPosition();
171
+ position.copy(this.entity.forward);
172
+ position.mulScalar(-this._distance);
173
+ position.add(this.pivotPoint);
174
+
175
+ // Clamp camera's Y position so it never goes below minY
176
+ position.y = Math.max(position.y, this.minY);
177
+
178
+ this.entity.setPosition(position);
179
+ };
180
+
181
+ OrbitCamera.prototype._removeInertia = function () {
182
+ this._yaw = this._targetYaw;
183
+ this._pitch = this._targetPitch;
184
+ this._distance = this._targetDistance;
185
+ };
186
+
187
+ OrbitCamera.prototype._checkAspectRatio = function () {
188
+ var height = this.app.graphicsDevice.height;
189
+ var width = this.app.graphicsDevice.width;
190
+ this.entity.camera.horizontalFov = (height > width);
191
+ };
192
+
193
+ OrbitCamera.prototype._buildAabb = function (entity) {
194
+ var i, m, meshInstances = [];
195
+
196
+ var renders = entity.findComponents('render');
197
+ for (i = 0; i < renders.length; i++) {
198
+ var render = renders[i];
199
+ for (m = 0; m < render.meshInstances.length; m++) {
200
+ meshInstances.push(render.meshInstances[m]);
201
+ }
202
+ }
203
+
204
+ var models = entity.findComponents('model');
205
+ for (i = 0; i < models.length; i++) {
206
+ var model = models[i];
207
+ for (m = 0; m < model.meshInstances.length; m++) {
208
+ meshInstances.push(model.meshInstances[m]);
209
+ }
210
+ }
211
+
212
+ var gsplats = entity.findComponents('gsplat');
213
+ for (i = 0; i < gsplats.length; i++) {
214
+ var gsplat = gsplats[i];
215
+ var instance = gsplat.instance;
216
+ if (instance?.meshInstance) {
217
+ meshInstances.push(instance.meshInstance);
218
+ }
219
+ }
220
+
221
+ for (i = 0; i < meshInstances.length; i++) {
222
+ if (i === 0) {
223
+ this._modelsAabb.copy(meshInstances[i].aabb);
224
+ } else {
225
+ this._modelsAabb.add(meshInstances[i].aabb);
226
+ }
227
+ }
228
+ };
229
+
230
+ OrbitCamera.prototype._calcYaw = function (quat) {
231
+ var transformedForward = new pc.Vec3();
232
+ quat.transformVector(pc.Vec3.FORWARD, transformedForward);
233
+ return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
234
+ };
235
+
236
+ OrbitCamera.prototype._clampDistance = function (distance) {
237
+ if (this.distanceMax > 0) {
238
+ return pc.math.clamp(distance, this.distanceMin, this.distanceMax);
239
+ }
240
+ return Math.max(distance, this.distanceMin);
241
+ };
242
+
243
+ OrbitCamera.prototype._clampPitchAngle = function (pitch) {
244
+ return pc.math.clamp(pitch, this.pitchAngleMin, this.pitchAngleMax);
245
+ };
246
+
247
+ OrbitCamera.prototype._clampYawAngle = function (yaw) {
248
+ return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
249
+ };
250
+
251
+ OrbitCamera.quatWithoutYaw = new pc.Quat();
252
+ OrbitCamera.yawOffset = new pc.Quat();
253
+
254
+ OrbitCamera.prototype._calcPitch = function (quat, yaw) {
255
+ var quatWithoutYaw = OrbitCamera.quatWithoutYaw;
256
+ var yawOffset = OrbitCamera.yawOffset;
257
+ yawOffset.setFromEulerAngles(0, -yaw, 0);
258
+ quatWithoutYaw.mul2(yawOffset, quat);
259
+ var transformedForward = new pc.Vec3();
260
+ quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
261
+ return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
262
+ };
263
+
264
+ // ===================== Orbit Camera Input Mouse Script ========================
265
+ var OrbitCameraInputMouse = pc.createScript('orbitCameraInputMouse');
266
+ OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type: 'number', default: 0.3, title: 'Orbit Sensitivity' });
267
+ OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type: 'number', default: 0.4, title: 'Distance Sensitivity' });
268
+
269
+ OrbitCameraInputMouse.prototype.initialize = function () {
270
+ this.orbitCamera = this.entity.script.orbitCamera;
271
+
272
+ if (this.orbitCamera) {
273
+ var self = this;
274
+ var onMouseOut = function () { self.onMouseOut(); };
275
+
276
+ this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
277
+ this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
278
+ this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
279
+ this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
280
+ window.addEventListener('mouseout', onMouseOut, false);
281
+
282
+ this.on('destroy', function () {
283
+ this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
284
+ this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
285
+ this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
286
+ this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
287
+ window.removeEventListener('mouseout', onMouseOut, false);
288
+ });
289
+ }
290
+
291
+ this.app.mouse.disableContextMenu();
292
+
293
+ this.lookButtonDown = false;
294
+ this.panButtonDown = false;
295
+ this.lastPoint = new pc.Vec2();
296
+ };
297
+
298
+ OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
299
+ OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
300
+ OrbitCameraInputMouse.worldDiff = new pc.Vec3();
301
+
302
+ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
303
+ var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
304
+ var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
305
+ var worldDiff = OrbitCameraInputMouse.worldDiff;
306
+
307
+ var camera = this.entity.camera;
308
+ var distance = this.orbitCamera.distance;
309
+
310
+ camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
311
+ camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
312
+
313
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
314
+
315
+ // Enforce minY like vertical/horizontal pan with arrows
316
+ var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
317
+ var minY = this.orbitCamera.minY;
318
+ var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
319
+
320
+ if (resultingY >= minY - 1e-4) {
321
+ this.orbitCamera.pivotPoint.add(worldDiff);
322
+ } else {
323
+ // Try horizontal-only
324
+ worldDiff.y = 0;
325
+ proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
326
+ resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
327
+ if (resultingY >= minY - 1e-4) {
328
+ this.orbitCamera.pivotPoint.add(worldDiff);
329
+ }
330
+ }
331
+ };
332
+
333
+ OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
334
+ switch (event.button) {
335
+ case pc.MOUSEBUTTON_LEFT: this.panButtonDown = true; break;
336
+ case pc.MOUSEBUTTON_MIDDLE:
337
+ case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = true; break;
338
+ }
339
+ };
340
+
341
+ OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
342
+ switch (event.button) {
343
+ case pc.MOUSEBUTTON_LEFT: this.panButtonDown = false; break;
344
+ case pc.MOUSEBUTTON_MIDDLE:
345
+ case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = false; break;
346
+ }
347
+ };
348
+
349
+ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
350
+ if (this.lookButtonDown) {
351
+ var sens = this.orbitSensitivity;
352
+
353
+ var deltaPitch = event.dy * sens; // mouse up (dy < 0) raises pitch
354
+ var deltaYaw = event.dx * sens;
355
+
356
+ var currPitch = this.orbitCamera.pitch;
357
+ var currYaw = this.orbitCamera.yaw;
358
+ var currDist = this.orbitCamera.distance;
359
+ var currPivot = this.orbitCamera.pivotPoint.clone();
360
+
361
+ var camQuat = new pc.Quat();
362
+ camQuat.setFromEulerAngles(currPitch, currYaw, 0);
363
+ var forward = new pc.Vec3();
364
+ camQuat.transformVector(pc.Vec3.FORWARD, forward);
365
+ var preY = currPivot.y + (-forward.y) * currDist;
366
+
367
+ var proposedPitch = currPitch - deltaPitch;
368
+ var testQuat = new pc.Quat();
369
+ testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
370
+ var testForward = new pc.Vec3();
371
+ testQuat.transformVector(pc.Vec3.FORWARD, testForward);
372
+ var proposedY = currPivot.y + (-testForward.y) * currDist;
373
+
374
+ var minY = this.orbitCamera.minY;
375
+ var wouldGoBelow = proposedY < minY - 1e-4;
376
+
377
+ if (wouldGoBelow && (proposedY < preY)) {
378
+ this.orbitCamera.yaw = currYaw - deltaYaw;
379
+ } else {
380
+ this.orbitCamera.pitch = proposedPitch;
381
+ this.orbitCamera.yaw = currYaw - deltaYaw;
382
+ }
383
+ } else if (this.panButtonDown) {
384
+ this.pan(new pc.Vec2(event.x, event.y));
385
+ }
386
+
387
+ this.lastPoint.set(event.x, event.y);
388
+ };
389
+
390
+ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
391
+ if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
392
+ this.orbitCamera.distance -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
393
+ } else {
394
+ this.orbitCamera.orthoHeight -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.orthoHeight * 0.1);
395
+ }
396
+ event.event.preventDefault();
397
+ };
398
+
399
+ OrbitCameraInputMouse.prototype.onMouseOut = function () {
400
+ this.lookButtonDown = false;
401
+ this.panButtonDown = false;
402
+ };
403
+
404
+ // =================== Orbit Camera Input Touch Script ========================
405
+ var OrbitCameraInputTouch = pc.createScript('orbitCameraInputTouch');
406
+ OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type: 'number', default: 0.6, title: 'Orbit Sensitivity' });
407
+ OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type: 'number', default: 0.5, title: 'Distance Sensitivity' });
408
+
409
+ OrbitCameraInputTouch.prototype.initialize = function () {
410
+ this.orbitCamera = this.entity.script.orbitCamera;
411
+ this.lastTouchPoint = new pc.Vec2();
412
+ this.lastPinchMidPoint = new pc.Vec2();
413
+ this.lastPinchDistance = 0;
414
+
415
+ if (this.orbitCamera && this.app.touch) {
416
+ this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
417
+ this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
418
+ this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
419
+ this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
420
+
421
+ this.on('destroy', function () {
422
+ this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
423
+ this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
424
+ this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
425
+ this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
426
+ });
427
+ }
428
+ };
429
+
430
+ OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
431
+ var dx = pointA.x - pointB.x;
432
+ var dy = pointA.y - pointB.y;
433
+ return Math.sqrt((dx * dx) + (dy * dy));
434
+ };
435
+
436
+ OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
437
+ result.set(pointB.x - pointA.x, pointB.y - pointA.y);
438
+ result.mulScalar(0.5);
439
+ result.x += pointA.x;
440
+ result.y += pointA.y;
441
+ };
442
+
443
+ OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
444
+ var touches = event.touches;
445
+ if (touches.length === 1) {
446
+ this.lastTouchPoint.set(touches[0].x, touches[0].y);
447
+ } else if (touches.length === 2) {
448
+ this.lastPinchDistance = this.getPinchDistance(touches[0], touches[1]);
449
+ this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
450
+ }
451
+ };
452
+
453
+ OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
454
+ OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
455
+ OrbitCameraInputTouch.worldDiff = new pc.Vec3();
456
+
457
+ OrbitCameraInputTouch.prototype.pan = function (midPoint) {
458
+ var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
459
+ var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
460
+ var worldDiff = OrbitCameraInputTouch.worldDiff;
461
+
462
+ var camera = this.entity.camera;
463
+ var distance = this.orbitCamera.distance;
464
+
465
+ camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
466
+ camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
467
+
468
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
469
+
470
+ var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
471
+ var minY = this.orbitCamera.minY;
472
+ var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
473
+
474
+ if (resultingY >= minY - 1e-4) {
475
+ this.orbitCamera.pivotPoint.add(worldDiff);
476
+ } else {
477
+ worldDiff.y = 0;
478
+ proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
479
+ resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
480
+ if (resultingY >= minY - 1e-4) {
481
+ this.orbitCamera.pivotPoint.add(worldDiff);
482
+ }
483
+ }
484
+ };
485
+
486
+ OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
487
+
488
+ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
489
+ var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
490
+ var touches = event.touches;
491
+
492
+ if (touches.length === 1) {
493
+ var touch = touches[0];
494
+ var sens = this.orbitSensitivity;
495
+
496
+ var deltaPitch = (touch.y - this.lastTouchPoint.y) * sens;
497
+ var deltaYaw = (touch.x - this.lastTouchPoint.x) * sens;
498
+
499
+ var currPitch = this.orbitCamera.pitch;
500
+ var currYaw = this.orbitCamera.yaw;
501
+ var currDist = this.orbitCamera.distance;
502
+ var currPivot = this.orbitCamera.pivotPoint.clone();
503
+
504
+ var camQuat = new pc.Quat();
505
+ camQuat.setFromEulerAngles(currPitch, currYaw, 0);
506
+ var forward = new pc.Vec3();
507
+ camQuat.transformVector(pc.Vec3.FORWARD, forward);
508
+ var preY = currPivot.y + (-forward.y) * currDist;
509
+
510
+ var proposedPitch = currPitch - deltaPitch;
511
+ var testQuat = new pc.Quat();
512
+ testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
513
+ var testForward = new pc.Vec3();
514
+ testQuat.transformVector(pc.Vec3.FORWARD, testForward);
515
+ var proposedY = currPivot.y + (-testForward.y) * currDist;
516
+
517
+ var minY = this.orbitCamera.minY;
518
+ var wouldGoBelow = proposedY < minY - 1e-4;
519
+
520
+ if (wouldGoBelow && (proposedY < preY)) {
521
+ this.orbitCamera.yaw = currYaw - deltaYaw;
522
+ } else {
523
+ this.orbitCamera.pitch = proposedPitch;
524
+ this.orbitCamera.yaw = currYaw - deltaYaw;
525
+ }
526
+
527
+ this.lastTouchPoint.set(touch.x, touch.y);
528
+ } else if (touches.length === 2) {
529
+ var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
530
+ var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
531
+ this.lastPinchDistance = currentPinchDistance;
532
+
533
+ this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
534
+
535
+ this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
536
+ this.pan(pinchMidPoint);
537
+ this.lastPinchMidPoint.copy(pinchMidPoint);
538
+ }
539
+ };
540
+
541
+ // =================== Orbit Camera Input Keyboard ========================
542
+ // Mappages demandés :
543
+ // - Flèches : déplacement avant/arrière et droite/gauche (au sol)
544
+ // - Ctrl+flèches : orbite autour du pivot
545
+ // - Maj+flèches : rotation de la caméra sur place (position fixe)
546
+ var OrbitCameraInputKeyboard = pc.createScript('orbitCameraInputKeyboard');
547
+
548
+ // Vitesses de déplacement (relatives à la distance pour un ressenti constant)
549
+ OrbitCameraInputKeyboard.attributes.add('forwardSpeed', { type: 'number', default: 1.8, title: 'Forward Speed (rel. to distance)' });
550
+ OrbitCameraInputKeyboard.attributes.add('strafeSpeed', { type: 'number', default: 1.8, title: 'Strafe Speed (rel. to distance)' });
551
+
552
+ // Réglages d’orbite / rotation
553
+ OrbitCameraInputKeyboard.attributes.add('orbitPitchSpeedDeg', { type: 'number', default: 90, title: 'Orbit Pitch Speed (deg/s) [Ctrl+Up/Down]' });
554
+ OrbitCameraInputKeyboard.attributes.add('orbitYawSpeedDeg', { type: 'number', default: 120, title: 'Orbit Yaw Speed (deg/s) [Ctrl+Left/Right]' });
555
+ OrbitCameraInputKeyboard.attributes.add('rotatePitchSpeedDeg', { type: 'number', default: 90, title: 'Self Rotate Pitch (deg/s) [Shift+Up/Down]' });
556
+ OrbitCameraInputKeyboard.attributes.add('rotateYawSpeedDeg', { type: 'number', default: 120, title: 'Self Rotate Yaw (deg/s) [Shift+Left/Right]' });
557
+
558
+ OrbitCameraInputKeyboard.prototype.initialize = function () {
559
+ this.orbitCamera = this.entity.script.orbitCamera;
560
+ this.keyboard = this.app.keyboard || null;
561
+ };
562
+
563
+ OrbitCameraInputKeyboard.prototype.update = function (dt) {
564
+ if (!this.orbitCamera || !this.keyboard) return;
565
+
566
+ var up = this.keyboard.isPressed(pc.KEY_UP);
567
+ var dn = this.keyboard.isPressed(pc.KEY_DOWN);
568
+ var lt = this.keyboard.isPressed(pc.KEY_LEFT);
569
+ var rt = this.keyboard.isPressed(pc.KEY_RIGHT);
570
+
571
+ var shift = this.keyboard.isPressed(pc.KEY_SHIFT);
572
+ var ctrl = this.keyboard.isPressed(pc.KEY_CONTROL);
573
+
574
+ // -------- Ctrl + flèches : ORBITER autour du pivot --------
575
+ if (ctrl && (up || dn || lt || rt)) {
576
+ var yawDir = (rt ? 1 : 0) - (lt ? 1 : 0); // droite = +1, gauche = -1
577
+ var pitchDir = (up ? 1 : 0) - (dn ? 1 : 0); // haut = +1, bas = -1
578
+
579
+ if (yawDir !== 0) {
580
+ var dYaw = yawDir * this.orbitYawSpeedDeg * dt;
581
+ this.orbitCamera.yaw = this.orbitCamera.yaw + dYaw;
582
+ }
583
+
584
+ if (pitchDir !== 0) {
585
+ var dPitch = pitchDir * this.orbitPitchSpeedDeg * dt;
586
+
587
+ // Empêche de passer sous minY pendant l’orbite en pitch
588
+ var currPitch = this.orbitCamera.pitch;
589
+ var currYaw = this.orbitCamera.yaw;
590
+ var currDist = this.orbitCamera.distance;
591
+ var currPivot = this.orbitCamera.pivotPoint.clone();
592
+
593
+ var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
594
+ var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
595
+ var preY = currPivot.y + (-forward.y) * currDist;
596
+
597
+ var testPitch = currPitch + dPitch;
598
+ var testQuat = new pc.Quat().setFromEulerAngles(testPitch, currYaw, 0);
599
+ var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
600
+ var proposedY = currPivot.y + (-testForward.y) * currDist;
601
+
602
+ var minY = this.orbitCamera.minY;
603
+ var wouldGoBelow = proposedY < minY - 1e-4;
604
+
605
+ if (!(wouldGoBelow && (proposedY < preY))) {
606
+ this.orbitCamera.pitch = testPitch;
607
+ }
608
+ }
609
+ return;
610
+ }
611
+
612
+ // -------- Maj + flèches : ROTATION SUR PLACE (caméra immobile) --------
613
+ if (shift && (up || dn || lt || rt)) {
614
+ var yawDirR = (rt ? 1 : 0) - (lt ? 1 : 0);
615
+ var pitchDirR = (up ? 1 : 0) - (dn ? 1 : 0);
616
+
617
+ var camPos = this.entity.getPosition().clone();
618
+ var dist = this.orbitCamera.distance;
619
+
620
+ // Applique yaw/pitch désirés
621
+ var newYaw = this.orbitCamera.yaw + yawDirR * this.rotateYawSpeedDeg * dt;
622
+ var newPitch = this.orbitCamera.pitch + pitchDirR * this.rotatePitchSpeedDeg * dt;
623
+
624
+ this.orbitCamera.yaw = newYaw;
625
+ this.orbitCamera.pitch = newPitch;
626
+
627
+ // Force l’absence d’inertie pour conserver la position exacte
628
+ this.orbitCamera._removeInertia();
629
+
630
+ // Recalcule le pivot pour garder la position de la caméra constante
631
+ var q = new pc.Quat().setFromEulerAngles(this.orbitCamera._pitch, this.orbitCamera._yaw, 0);
632
+ var forward = new pc.Vec3(); q.transformVector(pc.Vec3.FORWARD, forward);
633
+ var newPivot = camPos.clone().add(forward.clone().mulScalar(dist));
634
+ this.orbitCamera.pivotPoint = newPivot;
635
+
636
+ // Met à jour immédiatement la position (qui doit rester égale à camPos)
637
+ this.orbitCamera._updatePosition();
638
+ this.entity.setPosition(camPos);
639
+
640
+ return;
641
+ }
642
+
643
+ // -------- Flèches seules : DÉPLACEMENT (avant/arrière & droite/gauche) --------
644
+ var moveForward = (up ? 1 : 0) - (dn ? 1 : 0);
645
+ var moveRight = (rt ? 1 : 0) - (lt ? 1 : 0);
646
+ if (moveForward === 0 && moveRight === 0) return;
647
+
648
+ // Vecteurs avant/droite projetés sur le plan XZ (y=0) pour rester "au sol"
649
+ var fwd = this.entity.forward.clone(); fwd.y = 0;
650
+ var right = this.entity.right.clone(); right.y = 0;
651
+
652
+ if (fwd.lengthSq() > 1e-8) fwd.normalize(); else fwd.set(0, 0, -1);
653
+ if (right.lengthSq() > 1e-8) right.normalize(); else right.set(1, 0, 0);
654
+
655
+ var camDist = Math.max(0.1, this.orbitCamera.distance);
656
+ var speedF = this.forwardSpeed * camDist;
657
+ var speedR = this.strafeSpeed * camDist;
658
+
659
+ var delta = new pc.Vec3();
660
+ if (moveForward !== 0) delta.add(fwd.mulScalar(moveForward * speedF * dt));
661
+ if (moveRight !== 0) delta.add(right.mulScalar(moveRight * speedR * dt));
662
+
663
+ if (delta.lengthSq() > 0) {
664
+ var proposedPivot = this.orbitCamera.pivotPoint.clone().add(delta);
665
+ var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
666
+ if (resultingY >= this.orbitCamera.minY - 1e-4) {
667
+ this.orbitCamera.pivotPoint = proposedPivot;
668
+ } else {
669
+ // Si jamais cela ferait passer la caméra sous minY (peu probable car delta.y=0), on annule
670
+ }
671
+ }
672
+ };