viewer_sgos / deplacement_dans_env /ctrl_camera_pr_env.js
MikaFil's picture
Update deplacement_dans_env/ctrl_camera_pr_env.js
2ead98e verified
raw
history blame
14.1 kB
// ctrl_camera_pr_env.js
// ============================================================================
// FREE CAMERA (sans orbite) pour PlayCanvas
// - Souris : rotation sur place (comme Shift+flèches auparavant)
// - ZQSD / Flèches : déplacement local (avant/arrière & strafe)
// - Molette : dolly (avance/recul le long du regard)
// - Bounding Box + minY : contraintes sur la POSITION caméra uniquement
// - AUCUNE logique d’orbite (pas de pivot, pas de distance d’orbite)
// ============================================================================
var FreeCamera = pc.createScript('orbitCamera'); // garder le nom public "orbitCamera" pour compat viewer
// ======================== Attributs ===========================
FreeCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.12, title: 'Inertia (rotation)' });
// Limites de pitch (conseil: -89..89 si vue libre)
FreeCamera.attributes.add('pitchAngleMin', { type: 'number', default: -89, title: 'Pitch Min (deg)' });
FreeCamera.attributes.add('pitchAngleMax', { type: 'number', default: 89, title: 'Pitch Max (deg)' });
// minY (altitude min du point CAMÉRA)
FreeCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum camera Y' });
// Vitesse de déplacement (m/s)
FreeCamera.attributes.add('moveSpeed', { type: 'number', default: 2.0, title: 'Move Speed' });
FreeCamera.attributes.add('strafeSpeed', { type: 'number', default: 2.0, title: 'Strafe Speed' });
FreeCamera.attributes.add('dollySpeed', { type: 'number', default: 2.0, title: 'Mouse Wheel Dolly Speed' });
// Bounding Box (active si Xmin<Xmax etc.)
FreeCamera.attributes.add('Xmin', { type: 'number', default: -Infinity, title: 'BBox Xmin' });
FreeCamera.attributes.add('Xmax', { type: 'number', default: Infinity, title: 'BBox Xmax' });
FreeCamera.attributes.add('Ymin', { type: 'number', default: -Infinity, title: 'BBox Ymin' });
FreeCamera.attributes.add('Ymax', { type: 'number', default: Infinity, title: 'BBox Ymax' });
FreeCamera.attributes.add('Zmin', { type: 'number', default: -Infinity, title: 'BBox Zmin' });
FreeCamera.attributes.add('Zmax', { type: 'number', default: Infinity, title: 'BBox Zmax' });
// Compat (ignorés mais laissés pour ne pas casser le viewer existant)
FreeCamera.attributes.add('focusEntity', { type: 'entity', title: 'Compat: Focus Entity (unused)' });
FreeCamera.attributes.add('frameOnStart', { type: 'boolean', default: false, title: 'Compat: Frame on Start (unused)' });
FreeCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Compat: Yaw Min (unused)' });
FreeCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Compat: Yaw Max (unused)' });
FreeCamera.attributes.add('distanceMin', { type: 'number', default: 0.1, title: 'Compat: Distance Min (unused)' });
// ======================== Initialisation ===========================
Object.defineProperty(FreeCamera.prototype, 'pitch', {
get: function () { return this._targetPitch; },
set: function (v) { this._targetPitch = pc.math.clamp(v, this.pitchAngleMin, this.pitchAngleMax); }
});
Object.defineProperty(FreeCamera.prototype, 'yaw', {
get: function () { return this._targetYaw; },
set: function (v) { this._targetYaw = v; } // yaw libre (pas de clamp)
});
FreeCamera.prototype.initialize = function () {
// angles init depuis l'orientation actuelle
var q = this.entity.getRotation();
var f = new pc.Vec3();
q.transformVector(pc.Vec3.FORWARD, f);
// Convention PlayCanvas: setLocalEulerAngles(pitch, yaw, 0)
this._yaw = Math.atan2(f.x, f.z) * pc.math.RAD_TO_DEG;
// retirer le yaw pour extraire un pitch cohérent
var yawQuat = new pc.Quat().setFromEulerAngles(0, -this._yaw, 0);
var noYawQ = new pc.Quat().mul2(yawQuat, q);
var fNoYaw = new pc.Vec3();
noYawQ.transformVector(pc.Vec3.FORWARD, fNoYaw);
this._pitch = Math.atan2(-fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
this._pitch = pc.math.clamp(this._pitch, this.pitchAngleMin, this.pitchAngleMax);
this._targetYaw = this._yaw;
this._targetPitch = this._pitch;
// Appliquer orientation immédiatement
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
// Etat input (partagé par scripts d’input)
this.app.systems.script.app.freeCamState = this.app.systems.script.app.freeCamState || {};
this.state = this.app.systems.script.app.freeCamState; // pointeur commun
// S’assure que la position courante respecte minY + bbox
var p = this.entity.getPosition().clone();
this._clampPosition(p);
this.entity.setPosition(p);
// Aspect ratio (comme avant)
var self = this;
this._onResize = function(){ self._checkAspectRatio(); };
window.addEventListener('resize', this._onResize, false);
this._checkAspectRatio();
};
FreeCamera.prototype.update = function (dt) {
// Rotation inertielle
var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
// Déplacements clavier (gérés dans Keyboard, mais on reclampe chaque frame)
var pos = this.entity.getPosition().clone();
this._clampPosition(pos);
this.entity.setPosition(pos);
};
FreeCamera.prototype._checkAspectRatio = function () {
var gd = this.app.graphicsDevice;
if (!gd) return;
this.entity.camera.horizontalFov = (gd.height > gd.width);
};
// ======================== Contraintes ===========================
FreeCamera.prototype._bboxEnabled = function () {
return (this.Xmin < this.Xmax) && (this.Ymin < this.Ymax) && (this.Zmin < this.Zmax);
};
FreeCamera.prototype._clampPosition = function (p) {
// minY prioritaire
if (p.y < this.minY) p.y = this.minY;
if (!this._bboxEnabled()) return;
p.x = pc.math.clamp(p.x, this.Xmin, this.Xmax);
p.y = pc.math.clamp(p.y, Math.max(this.Ymin, this.minY), this.Ymax);
p.z = pc.math.clamp(p.z, this.Zmin, this.Zmax);
};
// ===================== INPUT SOURIS (rotation + molette dolly) =====================
var FreeCameraInputMouse = pc.createScript('orbitCameraInputMouse'); // garder le nom
FreeCameraInputMouse.attributes.add('lookSensitivity', { type: 'number', default: 0.3, title: 'Look Sensitivity' });
FreeCameraInputMouse.attributes.add('wheelSensitivity',{ type: 'number', default: 1.0, title: 'Wheel Sensitivity' });
FreeCameraInputMouse.prototype.initialize = function () {
this.freeCam = this.entity.script.orbitCamera; // instance FreeCamera
this.last = new pc.Vec2();
this.isLooking = false;
if (this.app.mouse) {
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
this.app.mouse.disableContextMenu();
}
var self = this;
this._onOut = function(){ self.isLooking = false; };
window.addEventListener('mouseout', this._onOut, false);
this.on('destroy', () => {
if (this.app.mouse) {
this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
}
window.removeEventListener('mouseout', this._onOut, false);
});
};
FreeCameraInputMouse.prototype.onMouseDown = function (e) {
// Bouton gauche/milieu/droit -> tous = "look"
this.isLooking = true;
this.last.set(e.x, e.y);
};
FreeCameraInputMouse.prototype.onMouseUp = function () {
this.isLooking = false;
};
FreeCameraInputMouse.prototype.onMouseMove = function (e) {
if (!this.isLooking || !this.freeCam) return;
var sens = this.lookSensitivity;
// Souris = même logique que "Shift + flèches" (rotation sur place)
var deltaYaw = (e.dx) * sens; // droite => +dx => on veut TURN RIGHT (CW) => yaw diminue
var deltaPitch = (e.dy) * sens; // haut => dy<0 => look up => pitch augmente => pitch -= deltaPitch
this.freeCam.yaw = this.freeCam.yaw - deltaYaw; // yaw libre
this.freeCam.pitch = this.freeCam.pitch - deltaPitch; // pitch clampé
this.last.set(e.x, e.y);
};
FreeCameraInputMouse.prototype.onMouseWheel = function (e) {
if (!this.freeCam) return;
// Dolly : avancer/reculer dans l'axe du regard
var cam = this.entity;
var move = -e.wheelDelta * this.wheelSensitivity * this.freeCam.dollySpeed * 0.05; // échelle douce
var forward = cam.forward.clone().normalize().mulScalar(move);
var pos = cam.getPosition().clone().add(forward);
this.freeCam._clampPosition(pos);
cam.setPosition(pos);
e.event.preventDefault();
};
// ===================== INPUT TOUCH (optionnel : look + pinch dolly simple) =====================
var FreeCameraInputTouch = pc.createScript('orbitCameraInputTouch');
FreeCameraInputTouch.attributes.add('lookSensitivity', { type: 'number', default: 0.5, title: 'Look Sensitivity' });
FreeCameraInputTouch.attributes.add('pinchDollyFactor', { type: 'number', default: 0.02, title: 'Pinch Dolly Factor' });
FreeCameraInputTouch.prototype.initialize = function () {
this.freeCam = this.entity.script.orbitCamera;
this.last = new pc.Vec2();
this.isLooking = false;
this.lastPinch = 0;
if (this.app.touch) {
this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
}
this.on('destroy', () => {
if (this.app.touch) {
this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
}
});
};
FreeCameraInputTouch.prototype.onTouchStartEndCancel = function (e) {
var t = e.touches;
if (t.length === 1) {
this.isLooking = (e.event.type === 'touchstart');
this.last.set(t[0].x, t[0].y);
} else if (t.length === 2) {
var dx = t[0].x - t[1].x;
var dy = t[0].y - t[1].y;
this.lastPinch = Math.sqrt(dx*dx + dy*dy);
} else {
this.isLooking = false;
}
};
FreeCameraInputTouch.prototype.onTouchMove = function (e) {
var t = e.touches;
if (!this.freeCam) return;
if (t.length === 1 && this.isLooking) {
var sens = this.lookSensitivity;
var dx = t[0].x - this.last.x;
var dy = t[0].y - this.last.y;
this.freeCam.yaw = this.freeCam.yaw - dx * sens;
this.freeCam.pitch = this.freeCam.pitch - dy * sens;
this.last.set(t[0].x, t[0].y);
} else if (t.length === 2) {
// Pinch dolly
var dx = t[0].x - t[1].x;
var dy = t[0].y - t[1].y;
var dist = Math.sqrt(dx*dx + dy*dy);
var delta = dist - this.lastPinch;
this.lastPinch = dist;
var cam = this.entity;
var forward = cam.forward.clone().normalize().mulScalar(delta * this.pinchDollyFactor * this.freeCam.dollySpeed);
var pos = cam.getPosition().clone().add(forward);
this.freeCam._clampPosition(pos);
cam.setPosition(pos);
}
};
// ===================== INPUT CLAVIER (ZQSD + flèches) =====================
var FreeCameraInputKeyboard = pc.createScript('orbitCameraInputKeyboard');
FreeCameraInputKeyboard.attributes.add('acceleration', { type: 'number', default: 1.0, title: 'Accel (unused, future)' });
FreeCameraInputKeyboard.prototype.initialize = function () {
this.freeCam = this.entity.script.orbitCamera;
this.kb = this.app.keyboard || null;
};
FreeCameraInputKeyboard.prototype.update = function (dt) {
if (!this.freeCam || !this.kb) return;
// Déplacements : flèches OU ZQSD (AZERTY)
var fwd = (this.kb.isPressed(pc.KEY_UP) || this.kb.isPressed(pc.KEY_Z)) ? 1 :
(this.kb.isPressed(pc.KEY_DOWN) || this.kb.isPressed(pc.KEY_S)) ? -1 : 0;
var strf = (this.kb.isPressed(pc.KEY_RIGHT) || this.kb.isPressed(pc.KEY_D)) ? 1 :
(this.kb.isPressed(pc.KEY_LEFT) || this.kb.isPressed(pc.KEY_Q)) ? -1 : 0;
if (fwd !== 0 || strf !== 0) {
var cam = this.entity;
var pos = cam.getPosition().clone();
var forward = cam.forward.clone(); if (forward.lengthSq() > 1e-8) forward.normalize();
var right = cam.right.clone(); if (right.lengthSq() > 1e-8) right.normalize();
pos.add(forward.mulScalar(fwd * this.freeCam.moveSpeed * dt));
pos.add(right .mulScalar(strf * this.freeCam.strafeSpeed * dt));
this.freeCam._clampPosition(pos);
cam.setPosition(pos);
}
// Rotation au clavier (Shift + flèches) — conservée
var shift = this.kb.isPressed(pc.KEY_SHIFT);
if (shift) {
var yawDir = (this.kb.isPressed(pc.KEY_LEFT) ? 1 : 0) - (this.kb.isPressed(pc.KEY_RIGHT) ? 1 : 0); // ← CCW / → CW
var pitchDir = (this.kb.isPressed(pc.KEY_UP) ? 1 : 0) - (this.kb.isPressed(pc.KEY_DOWN) ? 1 : 0); // ↑ up / ↓ down
// Même logique que la souris : rotation sur place
var yawSpeed = 120; // deg/s
var pitchSpeed = 90; // deg/s
if (yawDir !== 0) this.freeCam.yaw = this.freeCam.yaw + yawDir * yawSpeed * dt;
if (pitchDir !== 0) this.freeCam.pitch = this.freeCam.pitch + pitchDir * pitchSpeed * dt;
}
};