viewer_sgos / viewer.js
MikaFil's picture
Update viewer.js
902940d verified
raw
history blame
30.3 kB
// viewer.js
// ==============================
/* Util: charge une image comme texture PlayCanvas */
async function loadImageAsTexture(url, app) {
return new Promise((resolve, reject) => {
const img = new window.Image();
img.crossOrigin = "anonymous";
img.onload = function () {
const tex = new pc.Texture(app.graphicsDevice, {
width: img.width,
height: img.height,
format: pc.PIXELFORMAT_R8_G8_B8_A8
});
tex.setSource(img);
resolve(tex);
};
img.onerror = reject;
img.src = url;
});
}
/* Patch global: toutes les Image() -> crossOrigin="anonymous" */
(function () {
const OriginalImage = window.Image;
window.Image = function (...args) {
const img = new OriginalImage(...args);
img.crossOrigin = "anonymous";
return img;
};
})();
/* Couleur hex -> tableau RGBA [0..1] */
function hexToRgbaArray(hex) {
try {
hex = hex.replace("#", "");
if (hex.length === 6) hex += "FF";
if (hex.length !== 8) return [1, 1, 1, 1];
const num = parseInt(hex, 16);
return [
((num >> 24) & 0xff) / 255,
((num >> 16) & 0xff) / 255,
((num >> 8) & 0xff) / 255,
(num & 0xff) / 255
];
} catch (e) {
alert("hexToRgbaArray error: " + e);
return [1, 1, 1, 1];
}
}
/* Parcours récursif d'entités */
function traverse(entity, callback) {
callback(entity);
if (entity.children) {
entity.children.forEach((child) => traverse(child, callback));
}
}
/* --- État module (1 import = 1 instance) --- */
let pc;
export let app = null;
let cameraEntity = null;
let modelEntity = null;
let viewerInitialized = false;
let resizeObserver = null;
/* Contexte instance */
let chosenCameraX, chosenCameraY, chosenCameraZ;
let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
let presentoirScaleX, presentoirScaleY, presentoirScaleZ;
let sogsUrl, glbUrl, presentoirUrl;
let color_bg_hex, color_bg, espace_expo_bool;
/* Nom dynamique des scripts d'orbit pour CETTE instance */
let ORBIT_BASE = ""; // ex: orbitCamera__abcd1234
let ORBIT_MOUSE = "";
let ORBIT_TOUCH = "";
let ORBIT_KEYS = "";
/* Enregistre les 4 scripts d’orbit avec des NOMS UNIQUES par instance */
function registerOrbitScriptsForInstance(suffix) {
ORBIT_BASE = `orbitCamera__${suffix}`;
ORBIT_MOUSE = `orbitCameraInputMouse__${suffix}`;
ORBIT_TOUCH = `orbitCameraInputTouch__${suffix}`;
ORBIT_KEYS = `orbitCameraInputKeyboard__${suffix}`;
// ============== Orbit Camera (base) =================
const OrbitCamera = pc.createScript(ORBIT_BASE);
OrbitCamera.attributes.add("distanceMax", { type: "number", default: 20, title: "Distance Max" });
OrbitCamera.attributes.add("distanceMin", { type: "number", default: 1, title: "Distance Min" });
OrbitCamera.attributes.add("pitchAngleMax", { type: "number", default: 90, title: "Pitch Angle Max (degrees)" });
OrbitCamera.attributes.add("pitchAngleMin", { type: "number", default: 0, title: "Pitch Angle Min (degrees)" });
OrbitCamera.attributes.add("yawAngleMax", { type: "number", default: 360, title: "Yaw Angle Max (degrees)" });
OrbitCamera.attributes.add("yawAngleMin", { type: "number", default: -360, title: "Yaw Angle Min (degrees)" });
OrbitCamera.attributes.add("minY", { type: "number", default: 0, title: "Minimum Y" });
OrbitCamera.attributes.add("inertiaFactor", { type: "number", default: 0.2, title: "Inertia Factor" });
OrbitCamera.attributes.add("focusEntity", { type: "entity", title: "Focus Entity" });
OrbitCamera.attributes.add("frameOnStart", { type: "boolean", default: true, title: "Frame on Start" });
Object.defineProperty(OrbitCamera.prototype, "distance", {
get: function () { return this._targetDistance; },
set: function (value) { this._targetDistance = this._clampDistance(value); }
});
Object.defineProperty(OrbitCamera.prototype, "orthoHeight", {
get: function () { return this.entity.camera.orthoHeight; },
set: function (value) { this.entity.camera.orthoHeight = Math.max(0, value); }
});
Object.defineProperty(OrbitCamera.prototype, "pitch", {
get: function () { return this._targetPitch; },
set: function (value) { this._targetPitch = this._clampPitchAngle(value); }
});
Object.defineProperty(OrbitCamera.prototype, "yaw", {
get: function () { return this._targetYaw; },
set: function (value) { this._targetYaw = this._clampYawAngle(value); }
});
Object.defineProperty(OrbitCamera.prototype, "pivotPoint", {
get: function () { return this._pivotPoint; },
set: function (value) { this._pivotPoint.copy(value); }
});
OrbitCamera.prototype.focus = function (focusEntity) {
this._buildAabb(focusEntity);
const halfExtents = this._modelsAabb.halfExtents;
const radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
this._removeInertia();
this._pivotPoint.copy(this._modelsAabb.center);
};
OrbitCamera.distanceBetween = new pc.Vec3();
OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
this.pivotPoint.copy(lookAtPoint);
this.entity.setPosition(resetPoint);
this.entity.lookAt(lookAtPoint);
const distance = OrbitCamera.distanceBetween;
distance.sub2(lookAtPoint, resetPoint);
this.distance = distance.length();
this.pivotPoint.copy(lookAtPoint);
const cameraQuat = this.entity.getRotation();
this.yaw = this._calcYaw(cameraQuat);
this.pitch = this._calcPitch(cameraQuat, this.yaw);
this._removeInertia();
this._updatePosition();
};
OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
this._buildAabb(entity);
this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
};
OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
this.pitch = pitch;
this.yaw = yaw;
this.distance = distance;
this._removeInertia();
};
OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
this.entity.setPosition(position);
this.entity.lookAt(lookAtPoint);
this._pivotPoint.copy(lookAtPoint);
const distanceVec = new pc.Vec3();
distanceVec.sub2(position, lookAtPoint);
this._targetDistance = this._distance = distanceVec.length();
const cameraQuat = this.entity.getRotation();
this._targetYaw = this._yaw = this._calcYaw(cameraQuat);
this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
this._removeInertia();
this._updatePosition();
};
// Helper: calc cam Y if pivot became 'pivot' (enforce minY)
OrbitCamera.prototype.worldCameraYForPivot = function (pivot) {
const quat = new pc.Quat().setFromEulerAngles(this._pitch, this._yaw, 0);
const forward = new pc.Vec3();
quat.transformVector(pc.Vec3.FORWARD, forward);
const camPos = pivot.clone().add(forward.clone().scale(-this._distance));
return camPos.y;
};
// --------- Private ----------
OrbitCamera.prototype.initialize = function () {
const self = this;
const onWindowResize = function () { self._checkAspectRatio(); };
window.addEventListener("resize", onWindowResize, false);
this._checkAspectRatio();
this._modelsAabb = new pc.BoundingBox();
this._buildAabb(this.focusEntity || this.app.root);
this.entity.lookAt(this._modelsAabb.center);
this._pivotPoint = new pc.Vec3().copy(this._modelsAabb.center);
const cameraQuat = this.entity.getRotation();
this._yaw = this._calcYaw(cameraQuat);
this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
this._distance = 0;
this._targetYaw = this._yaw;
this._targetPitch = this._pitch;
if (this.frameOnStart) {
this.focus(this.focusEntity || this.app.root);
} else {
const distanceBetween = new pc.Vec3();
distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
this._distance = this._clampDistance(distanceBetween.length());
}
this._targetDistance = this._distance;
this.on("attr:distanceMin", function () { this._distance = this._clampDistance(this._distance); });
this.on("attr:distanceMax", function () { this._distance = this._clampDistance(this._distance); });
this.on("attr:pitchAngleMin", function () { this._pitch = this._clampPitchAngle(this._pitch); });
this.on("attr:pitchAngleMax", function () { this._pitch = this._clampPitchAngle(this._pitch); });
this.on("attr:focusEntity", function (value) {
if (this.frameOnStart) this.focus(value || this.app.root);
else this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root);
});
this.on("attr:frameOnStart", function (value) {
if (value) this.focus(this.focusEntity || this.app.root);
});
this.on("destroy", function () { window.removeEventListener("resize", onWindowResize, false); });
};
OrbitCamera.prototype.update = function (dt) {
const t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
this._updatePosition();
};
OrbitCamera.prototype._updatePosition = function () {
this.entity.setLocalPosition(0, 0, 0);
this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
const position = this.entity.getPosition();
position.copy(this.entity.forward);
position.mulScalar(-this._distance);
position.add(this.pivotPoint);
// Ne jamais passer sous minY
position.y = Math.max(position.y, this.minY);
this.entity.setPosition(position);
};
OrbitCamera.prototype._removeInertia = function () {
this._yaw = this._targetYaw;
this._pitch = this._targetPitch;
this._distance = this._targetDistance;
};
OrbitCamera.prototype._checkAspectRatio = function () {
const h = this.app.graphicsDevice.height;
const w = this.app.graphicsDevice.width;
this.entity.camera.horizontalFov = (h > w);
};
OrbitCamera.prototype._buildAabb = function (entity) {
let i, m;
const meshInstances = [];
const renders = entity.findComponents("render");
for (i = 0; i < renders.length; i++) {
const render = renders[i];
for (m = 0; m < render.meshInstances.length; m++) {
meshInstances.push(render.meshInstances[m]);
}
}
const models = entity.findComponents("model");
for (i = 0; i < models.length; i++) {
const model = models[i];
for (m = 0; m < model.meshInstances.length; m++) {
meshInstances.push(model.meshInstances[m]);
}
}
const gsplats = entity.findComponents("gsplat");
for (i = 0; i < gsplats.length; i++) {
const gsplat = gsplats[i];
const instance = gsplat.instance;
if (instance?.meshInstance) meshInstances.push(instance.meshInstance);
}
for (i = 0; i < meshInstances.length; i++) {
if (i === 0) this._modelsAabb.copy(meshInstances[i].aabb);
else this._modelsAabb.add(meshInstances[i].aabb);
}
};
OrbitCamera.prototype._calcYaw = function (quat) {
const transformedForward = new pc.Vec3();
quat.transformVector(pc.Vec3.FORWARD, transformedForward);
return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
};
OrbitCamera.prototype._clampDistance = function (distance) {
if (this.distanceMax > 0) return pc.math.clamp(distance, this.distanceMin, this.distanceMax);
return Math.max(distance, this.distanceMin);
};
OrbitCamera.prototype._clampPitchAngle = function (pitch) {
return pc.math.clamp(pitch, this.pitchAngleMin, this.pitchAngleMax);
};
OrbitCamera.prototype._clampYawAngle = function (yaw) {
return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
};
const quatWithoutYaw = new pc.Quat();
const yawOffset = new pc.Quat();
OrbitCamera.prototype._calcPitch = function (quat, yaw) {
yawOffset.setFromEulerAngles(0, -yaw, 0);
quatWithoutYaw.mul2(yawOffset, quat);
const transformedForward = new pc.Vec3();
quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
};
// ============== Orbit Camera Input Mouse =================
const OrbitCameraInputMouse = pc.createScript(ORBIT_MOUSE);
OrbitCameraInputMouse.attributes.add("orbitSensitivity", { type: "number", default: 0.3, title: "Orbit Sensitivity" });
OrbitCameraInputMouse.attributes.add("distanceSensitivity", { type: "number", default: 0.4, title: "Distance Sensitivity" });
OrbitCameraInputMouse.prototype.initialize = function () {
this.orbitCamera = this.entity.script[ORBIT_BASE];
if (this.orbitCamera) {
const self = this;
const onMouseOut = function () { self.onMouseOut(); };
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);
window.addEventListener("mouseout", onMouseOut, false);
this.on("destroy", function () {
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", onMouseOut, false);
});
}
this.app.mouse.disableContextMenu();
this.lookButtonDown = false;
this.panButtonDown = false;
this.lastPoint = new pc.Vec2();
};
OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
OrbitCameraInputMouse.worldDiff = new pc.Vec3();
OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
const fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
const toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
const worldDiff = OrbitCameraInputMouse.worldDiff;
const camera = this.entity.camera;
const distance = this.orbitCamera.distance;
camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
worldDiff.sub2(toWorldPoint, fromWorldPoint);
// Respecter minY (comme les flèches)
let proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
const minY = this.orbitCamera.minY;
let resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
if (resultingY >= minY - 1e-4) {
this.orbitCamera.pivotPoint.add(worldDiff);
} else {
worldDiff.y = 0;
proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
if (resultingY >= minY - 1e-4) this.orbitCamera.pivotPoint.add(worldDiff);
}
};
OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
switch (event.button) {
case pc.MOUSEBUTTON_LEFT: this.panButtonDown = true; break;
case pc.MOUSEBUTTON_MIDDLE:
case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = true; break;
}
};
OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
switch (event.button) {
case pc.MOUSEBUTTON_LEFT: this.panButtonDown = false; break;
case pc.MOUSEBUTTON_MIDDLE:
case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = false; break;
}
};
OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
if (this.lookButtonDown) {
const sens = this.orbitSensitivity;
const deltaPitch = event.dy * sens;
const deltaYaw = event.dx * sens;
const currPitch = this.orbitCamera.pitch;
const currYaw = this.orbitCamera.yaw;
const currDist = this.orbitCamera.distance;
const currPivot = this.orbitCamera.pivotPoint.clone();
const camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
const forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
const preY = currPivot.y + (-forward.y) * currDist;
const proposedPitch = currPitch - deltaPitch;
const testQuat = new pc.Quat().setFromEulerAngles(proposedPitch, currYaw, 0);
const testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
const proposedY = currPivot.y + (-testForward.y) * currDist;
const minY = this.orbitCamera.minY;
const wouldGoBelow = proposedY < minY - 1e-4;
if (wouldGoBelow && (proposedY < preY)) {
this.orbitCamera.yaw = currYaw - deltaYaw;
} else {
this.orbitCamera.pitch = proposedPitch;
this.orbitCamera.yaw = currYaw - deltaYaw;
}
} else if (this.panButtonDown) {
this.pan(new pc.Vec2(event.x, event.y));
}
this.lastPoint.set(event.x, event.y);
};
OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
this.orbitCamera.distance -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
} else {
this.orbitCamera.orthoHeight -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.orthoHeight * 0.1);
}
event.event.preventDefault();
};
OrbitCameraInputMouse.prototype.onMouseOut = function () {
this.lookButtonDown = false;
this.panButtonDown = false;
};
// ============== Orbit Camera Input Touch =================
const OrbitCameraInputTouch = pc.createScript(ORBIT_TOUCH);
OrbitCameraInputTouch.attributes.add("orbitSensitivity", { type: "number", default: 0.6, title: "Orbit Sensitivity" });
OrbitCameraInputTouch.attributes.add("distanceSensitivity", { type: "number", default: 0.5, title: "Distance Sensitivity" });
OrbitCameraInputTouch.prototype.initialize = function () {
this.orbitCamera = this.entity.script[ORBIT_BASE];
this.lastTouchPoint = new pc.Vec2();
this.lastPinchMidPoint = new pc.Vec2();
this.lastPinchDistance = 0;
if (this.orbitCamera && 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", function () {
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);
});
}
};
OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
const dx = pointA.x - pointB.x;
const dy = pointA.y - pointB.y;
return Math.sqrt((dx * dx) + (dy * dy));
};
OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
result.set(pointB.x - pointA.x, pointB.y - pointA.y);
result.mulScalar(0.5);
result.x += pointA.x;
result.y += pointA.y;
};
OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
const touches = event.touches;
if (touches.length === 1) {
this.lastTouchPoint.set(touches[0].x, touches[0].y);
} else if (touches.length === 2) {
this.lastPinchDistance = this.getPinchDistance(touches[0], touches[1]);
this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
}
};
OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
OrbitCameraInputTouch.worldDiff = new pc.Vec3();
OrbitCameraInputTouch.prototype.pan = function (midPoint) {
const fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
const toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
const worldDiff = OrbitCameraInputTouch.worldDiff;
const camera = this.entity.camera;
const distance = this.orbitCamera.distance;
camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
worldDiff.sub2(toWorldPoint, fromWorldPoint);
let proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
const minY = this.orbitCamera.minY;
let resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
if (resultingY >= minY - 1e-4) {
this.orbitCamera.pivotPoint.add(worldDiff);
} else {
worldDiff.y = 0;
proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
if (resultingY >= minY - 1e-4) this.orbitCamera.pivotPoint.add(worldDiff);
}
};
OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
const pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
const touches = event.touches;
if (touches.length === 1) {
const touch = touches[0];
const sens = this.orbitSensitivity;
const deltaPitch = (touch.y - this.lastTouchPoint.y) * sens;
const deltaYaw = (touch.x - this.lastTouchPoint.x) * sens;
const currPitch = this.orbitCamera.pitch;
const currYaw = this.orbitCamera.yaw;
const currDist = this.orbitCamera.distance;
const currPivot = this.orbitCamera.pivotPoint.clone();
const camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
const forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
const preY = currPivot.y + (-forward.y) * currDist;
const proposedPitch = currPitch - deltaPitch;
const testQuat = new pc.Quat().setFromEulerAngles(proposedPitch, currYaw, 0);
const testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
const proposedY = currPivot.y + (-testForward.y) * currDist;
const minY = this.orbitCamera.minY;
const wouldGoBelow = proposedY < minY - 1e-4;
if (wouldGoBelow && (proposedY < preY)) {
this.orbitCamera.yaw = currYaw - deltaYaw;
} else {
this.orbitCamera.pitch = proposedPitch;
this.orbitCamera.yaw = currYaw - deltaYaw;
}
this.lastTouchPoint.set(touch.x, touch.y);
} else if (touches.length === 2) {
const currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
const diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
this.lastPinchDistance = currentPinchDistance;
this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
this.pan(pinchMidPoint);
this.lastPinchMidPoint.copy(pinchMidPoint);
}
};
// ============== Orbit Camera Input Keyboard =================
const OrbitCameraInputKeyboard = pc.createScript(ORBIT_KEYS);
OrbitCameraInputKeyboard.attributes.add("forwardSpeed", { type: "number", default: 1.2, title: "Vertical Speed (rel. to distance)" });
OrbitCameraInputKeyboard.attributes.add("strafeSpeed", { type: "number", default: 1.2, title: "Left/Right Speed (rel. to distance)" });
// Fine-tuning
OrbitCameraInputKeyboard.attributes.add("orbitPitchSpeedDeg", { type: "number", default: 90, title: "Orbit Pitch Speed (deg/s) [Shift+Up/Down]" });
OrbitCameraInputKeyboard.attributes.add("orbitYawSpeedDeg", { type: "number", default: 120, title: "Orbit Yaw Speed (deg/s) [Shift+Left/Right]" });
OrbitCameraInputKeyboard.attributes.add("zoomKeySensitivity", { type: "number", default: 3.0, title: "Zoom Sensitivity (1/s) [Ctrl+Up/Down]" });
OrbitCameraInputKeyboard.prototype.initialize = function () {
this.orbitCamera = this.entity.script[ORBIT_BASE];
this.keyboard = this.app.keyboard || null;
};
OrbitCameraInputKeyboard.prototype.update = function (dt) {
if (!this.orbitCamera || !this.keyboard) return;
const up = this.keyboard.isPressed(pc.KEY_UP);
const dn = this.keyboard.isPressed(pc.KEY_DOWN);
const lt = this.keyboard.isPressed(pc.KEY_LEFT);
const rt = this.keyboard.isPressed(pc.KEY_RIGHT);
const shift = this.keyboard.isPressed(pc.KEY_SHIFT);
const ctrl = this.keyboard.isPressed(pc.KEY_CONTROL);
// ---- SHIFT: Orbit (pitch / yaw) ----
if (shift && (up || dn || lt || rt)) {
// Yaw: Shift+Right => orbit à droite
const yawDir = (rt ? 1 : 0) - (lt ? 1 : 0);
if (yawDir !== 0) {
const dYaw = yawDir * this.attributes.orbitYawSpeedDeg * dt;
this.orbitCamera.yaw = this.orbitCamera.yaw + dYaw;
}
// Pitch: Shift+Up => orbit up
const pitchDir = (up ? 1 : 0) - (dn ? 1 : 0);
if (pitchDir !== 0) {
const dPitch = pitchDir * this.attributes.orbitPitchSpeedDeg * dt;
const currPitch = this.orbitCamera.pitch;
const currYaw = this.orbitCamera.yaw;
const currDist = this.orbitCamera.distance;
const currPivot = this.orbitCamera.pivotPoint.clone();
const camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
const forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
const preY = currPivot.y + (-forward.y) * currDist;
const testPitch = currPitch + dPitch;
const testQuat = new pc.Quat().setFromEulerAngles(testPitch, currYaw, 0);
const testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
const proposedY = currPivot.y + (-testForward.y) * currDist;
const minY = this.orbitCamera.minY;
const wouldGoBelow = proposedY < minY - 1e-4;
if (!(wouldGoBelow && (proposedY < preY))) {
this.orbitCamera.pitch = testPitch;
}
}
return;
}
// ---- CTRL: Zoom (up/down) ----
if (ctrl && (up || dn)) {
const zoomSign = (up ? 1 : 0) - (dn ? 1 : 0);
if (zoomSign !== 0) {
if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
const dz = zoomSign * this.attributes.zoomKeySensitivity * (this.orbitCamera.distance * 0.5) * dt;
this.orbitCamera.distance -= dz;
} else {
const doh = zoomSign * this.attributes.zoomKeySensitivity * (this.orbitCamera.orthoHeight * 0.5) * dt;
this.orbitCamera.orthoHeight -= doh;
}
}
return;
}
// ---- Sans modif: translation (vertical + strafe) ----
const moveVert = (up ? 1 : 0) - (dn ? 1 : 0);
const moveRight = (rt ? 1 : 0) - (lt ? 1 : 0);
if (moveVert === 0 && moveRight === 0) return;
const dist = Math.max(0.1, this.orbitCamera.distance);
const speedV = this.attributes.forwardSpeed * dist;
const speedR = this.attributes.strafeSpeed * dist;
// Vertical pur Y (respect minY)
let dy = moveVert * speedV * dt;
if (dy !== 0) {
const currentCamY = this.orbitCamera.worldCameraYForPivot(this.orbitCamera.pivotPoint);
const minY = this.orbitCamera.minY;
const proposedCamY = currentCamY + dy;
if (proposedCamY < minY) dy = Math.max(dy, minY - currentCamY);
if (dy !== 0) this.orbitCamera._pivotPoint.y += dy;
}
// Strafe en XZ
const right = this.entity.right.clone(); right.y = 0;
if (right.lengthSq() > 1e-8) right.normalize();
const dx = moveRight * speedR * dt;
if (dx !== 0) this.orbitCamera._pivotPoint.add(right.mulScalar(dx));
};
}
/* ======================= INITIALISATION VIEWER =========================== */
export async function initializeViewer(config, instanceId) {
// 1 import = 1 instance; garde-fou en plus
if (viewerInitialized) return;
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
// --- Config ---
sogsUrl = config.sogs_json_url;
glbUrl = (config.glb_url !== undefined)
? config.glb_url
: "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
presentoirUrl = (config.presentoir_url !== undefined)
? config.presentoir_url
: "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
minZoom = parseFloat(config.minZoom || "1");
maxZoom = parseFloat(config.maxZoom || "20");
minAngle = parseFloat(config.minAngle || "-45");
maxAngle = parseFloat(config.maxAngle || "90");
minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
minPivotY = parseFloat(config.minPivotY || "0");
minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
presentoirScaleX = (config.presentoirScaleX !== undefined) ? parseFloat(config.presentoirScaleX) : 0;
presentoirScaleY = (config.presentoirScaleY !== undefined) ? parseFloat(config.presentoirScaleY) : 0;
presentoirScaleZ = (config.presentoirScaleZ !== undefined) ? parseFloat(config.presentoirScaleZ) : 0;
const cameraX = (config.camera