visualiseur_EARCARE / tooltips.js
MikaFil's picture
Update tooltips.js
74df1f0 verified
// tooltips.js
/**
* initializeTooltips(options)
*
* - options.app: the PlayCanvas App instance
* - options.cameraEntity: the PlayCanvas camera Entity
* - options.modelEntity: the main model entity (for any relative positioning; optional)
* - options.tooltipsUrl: URL to fetch JSON array of tooltip definitions
* - options.defaultVisible: boolean: whether tooltips are visible initially
* - options.moveDuration: number (seconds) for smooth camera move to selected tooltip
*/
export async function initializeTooltips(options) {
const {
app,
cameraEntity,
modelEntity,
tooltipsUrl,
defaultVisible,
moveDuration = 0.6
} = options;
if (!app || !cameraEntity || !tooltipsUrl) {
return;
}
let tooltipsData;
try {
const resp = await fetch(tooltipsUrl);
tooltipsData = await resp.json();
} catch (e) {
return;
}
if (!Array.isArray(tooltipsData)) {
return;
}
const tooltipEntities = [];
// Create a material for tooltip spheres
const mat = new pc.StandardMaterial();
mat.diffuse = new pc.Color(1, 0.8, 0);
mat.specular = new pc.Color(1, 1, 1);
mat.shininess = 20;
mat.emissive = new pc.Color(0.85, 0.85, 0.85); // Strong orange emissive
mat.emissiveIntensity = 1;
mat.useLighting = false;
mat.update();
// Build each tooltip sphere + attach custom data
for (let i = 0; i < tooltipsData.length; i++) {
const tt = tooltipsData[i];
const { x, y, z, title, description, imgUrl } = tt;
const sphere = new pc.Entity("tooltip-" + i);
sphere.addComponent("model", { type: "sphere" });
sphere.model.material = mat;
sphere.setLocalScale(0.05, 0.05, 0.05);
sphere.setLocalPosition(x, y, z);
sphere.tooltipData = { title, description, imgUrl };
app.root.addChild(sphere);
tooltipEntities.push(sphere);
}
function setTooltipsVisibility(visible) {
tooltipEntities.forEach(ent => {
ent.enabled = visible;
});
}
setTooltipsVisibility(!!defaultVisible);
document.addEventListener("toggle-tooltips", (evt) => {
const { visible } = evt.detail;
setTooltipsVisibility(!!visible);
});
let currentTween = null;
app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => {
if (currentTween) {
app.off("update", currentTween);
currentTween = null;
}
const x = event.x;
const y = event.y;
const from = new pc.Vec3();
const to = new pc.Vec3();
const camera = cameraEntity.camera;
camera.screenToWorld(x, y, camera.nearClip, from);
camera.screenToWorld(x, y, camera.farClip, to);
const dir = new pc.Vec3().sub2(to, from).normalize();
let closestT = Infinity;
let pickedEntity = null;
for (const ent of tooltipEntities) {
if (!ent.enabled) continue;
const center = ent.getPosition();
const worldRadius = 0.5 * ent.getLocalScale().x;
const oc = new pc.Vec3().sub2(center, from);
const tca = oc.dot(dir);
if (tca < 0) continue;
const d2 = oc.lengthSq() - (tca * tca);
if (d2 > worldRadius * worldRadius) continue;
const thc = Math.sqrt(worldRadius * worldRadius - d2);
const t0 = tca - thc;
if (t0 < closestT && t0 >= 0) {
closestT = t0;
pickedEntity = ent;
}
}
if (pickedEntity) {
const { title, description, imgUrl } = pickedEntity.tooltipData;
document.dispatchEvent(new CustomEvent("tooltip-selected", {
detail: { title, description, imgUrl }
}));
tweenCameraToTooltip(pickedEntity, moveDuration);
}
});
// Do NOT close the tooltip panel on canvas mousedown/touchstart!
// Helper to normalize angle difference into [-180, +180]
function shortestAngleDiff(target, current) {
let delta = target - current;
delta = ((delta + 180) % 360 + 360) % 360 - 180;
return delta;
}
function tweenCameraToTooltip(tooltipEnt, duration) {
const orbitCam = cameraEntity.script.orbitCamera;
if (!orbitCam) return;
const targetPos = tooltipEnt.getPosition().clone();
const startPivot = orbitCam.pivotPoint.clone();
const startYaw = orbitCam._yaw;
const startPitch = orbitCam._pitch;
const startDist = orbitCam._distance;
const worldRadius = 0.5 * tooltipEnt.getLocalScale().x;
const minZoom = orbitCam.distanceMin;
const desiredDistance = Math.max(minZoom * 1.2, worldRadius * 4);
const camWorldPos = cameraEntity.getPosition().clone();
const tempEnt = new pc.Entity();
tempEnt.setPosition(camWorldPos);
tempEnt.lookAt(targetPos);
const rotation = tempEnt.getRotation();
const forward = new pc.Vec3();
rotation.transformVector(pc.Vec3.FORWARD, forward);
const rawTgtYaw = Math.atan2(-forward.x, -forward.z) * pc.math.RAD_TO_DEG;
const yawDelta = shortestAngleDiff(rawTgtYaw, startYaw);
const endYaw = startYaw + yawDelta;
const yawQuat = new pc.Quat().setFromEulerAngles(0, -rawTgtYaw, 0);
const rotNoYaw = new pc.Quat().mul2(yawQuat, rotation);
const fNoYaw = new pc.Vec3();
rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
const rawTgtPitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
const pitchDelta = shortestAngleDiff(rawTgtPitch, startPitch);
const endPitch = startPitch + pitchDelta;
tempEnt.destroy();
const endPivot = targetPos.clone();
const endDist = desiredDistance;
let elapsed = 0;
const orgPivot = startPivot.clone();
const orgYaw = startYaw;
const orgPitch = startPitch;
const orgDist = startDist;
if (currentTween) {
app.off("update", currentTween);
currentTween = null;
}
function lerpUpdate(dt) {
elapsed += dt;
const t = Math.min(elapsed / duration, 1);
const newPivot = new pc.Vec3().lerp(orgPivot, endPivot, t);
orbitCam.pivotPoint.copy(newPivot);
const newYaw = pc.math.lerp(orgYaw, endYaw, t);
const newPitch = pc.math.lerp(orgPitch, endPitch, t);
const newDist = pc.math.lerp(orgDist, endDist, t);
orbitCam._targetYaw = newYaw;
orbitCam._yaw = newYaw;
orbitCam._targetPitch = newPitch;
orbitCam._pitch = newPitch;
orbitCam._targetDistance = newDist;
orbitCam._distance = newDist;
orbitCam._updatePosition();
if (t >= 1) {
app.off("update", lerpUpdate);
currentTween = null;
}
}
currentTween = lerpUpdate;
app.on("update", lerpUpdate);
}
}