bilca's picture
Update points.js
4d5ccce verified
//points.js
/**
* initializePoints(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.pointsUrl: URL to fetch JSON array of points
* - options.defaultVisible: boolean: whether points are visible initially
* - options.moveDuration: number (seconds) for smooth camera move to selected point
*/
export async function initializePoints(options) {
const {
app,
cameraEntity,
modelEntity,
pointsUrl,
defaultVisible,
moveDuration = 0.6
} = options;
if (!app || !cameraEntity || !pointsUrl) {
console.error("points.js → missing required initialization options");
return;
}
// Load JSON of points:
let pointsData;
try {
const resp = await fetch(pointsUrl);
pointsData = await resp.json();
} catch (e) {
console.error("points.js → failed fetching points.json:", e);
return;
}
if (!Array.isArray(pointsData)) {
console.error("points.js → points.json must be an array");
return;
}
const pointEntities = [];
// Create a material for info-point 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.update();
// Build each sphere + attach custom data
for (let i = 0; i < pointsData.length; i++) {
const pt = pointsData[i];
const { x, y, z, text, imageUrl } = pt;
const sphere = new pc.Entity("point-" + i);
sphere.addComponent("model", { type: "sphere" });
sphere.model.material = mat;
// Scale small (primitive sphere radius = 0.5)
sphere.setLocalScale(0.05, 0.05, 0.05);
sphere.setLocalPosition(x, y, z);
sphere.pointData = { text, imageUrl };
app.root.addChild(sphere);
pointEntities.push(sphere);
}
// Show/hide all point spheres
function setPointsVisibility(visible) {
pointEntities.forEach(ent => {
ent.enabled = visible;
});
}
setPointsVisibility(!!defaultVisible);
// Respond to toggle-points event from interface.js
document.addEventListener("toggle-points", (evt) => {
const { visible } = evt.detail;
setPointsVisibility(!!visible);
});
// Keep track of any in-flight camera tween so we can cancel it
let currentTween = null;
// On mouse down (or touch equivalent), perform manual ray‐sphere intersection
app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => {
// If a tween is running, cancel it immediately
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;
cameraEntity.camera.screenToWorld(x, y, camera.nearClip, from);
cameraEntity.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 pointEntities) {
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 { text, imageUrl } = pickedEntity.pointData;
document.dispatchEvent(new CustomEvent("point-selected", {
detail: { text, imageUrl }
}));
tweenCameraToPoint(pickedEntity, moveDuration);
}
});
// Also close tooltip if user interacts (mouse or touch) on the canvas
const canvasId = app.graphicsDevice.canvas.id;
const htmlCanvas = document.getElementById(canvasId);
if (htmlCanvas) {
htmlCanvas.addEventListener("mousedown", () => {
document.dispatchEvent(new CustomEvent("hide-tooltip"));
});
htmlCanvas.addEventListener("touchstart", () => {
document.dispatchEvent(new CustomEvent("hide-tooltip"));
});
}
// Tween helper: smoothly move and reorient camera to focus the chosen point entity
function tweenCameraToPoint(pointEnt, duration) {
const orbitCam = cameraEntity.script.orbitCamera;
if (!orbitCam) return;
// Compute target pivot exactly at the sphere center
const targetPos = pointEnt.getPosition().clone();
// Compute current state
const startPivot = orbitCam.pivotPoint.clone();
const startYaw = orbitCam._yaw;
const startPitch = orbitCam._pitch;
const startDist = orbitCam._distance;
// Compute direction & candidate distance:
const worldRadius = 0.5 * pointEnt.getLocalScale().x;
const minZoom = orbitCam.distanceMin;
const desiredDistance = Math.max(minZoom * 1.2, worldRadius * 4);
// Compute target yaw/pitch from camera pointing at targetPos
// Reuse reset logic: place a temp entity at camera’s current position, have it look at target
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 tgtYaw = Math.atan2(-forward.x, -forward.z) * pc.math.RAD_TO_DEG;
const yawQuat = new pc.Quat().setFromEulerAngles(0, -tgtYaw, 0);
const rotNoYaw = new pc.Quat().mul2(yawQuat, rotation);
const fNoYaw = new pc.Vec3();
rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
const tgtPitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
tempEnt.destroy();
// Target state:
const endPivot = targetPos.clone();
const endYaw = tgtYaw;
const endPitch = tgtPitch;
const endDist = desiredDistance;
let elapsed = 0;
const orgPivot = startPivot.clone();
const orgYaw = startYaw;
const orgPitch = startPitch;
const orgDist = startDist;
// If another tween is running, cancel it
if (currentTween) {
app.off("update", currentTween);
currentTween = null;
}
// Per-frame update
function lerpUpdate(dt) {
elapsed += dt;
const t = Math.min(elapsed / duration, 1);
// Interpolate pivot (vector lerp)
const newPivot = new pc.Vec3().lerp(orgPivot, endPivot, t);
orbitCam.pivotPoint.copy(newPivot);
// Interpolate yaw/pitch/distance (simple lerp)
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);
}
}