| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 = []; |
| |
|
| | |
| | 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); |
| | mat.emissiveIntensity = 1; |
| | mat.useLighting = false; |
| | mat.update(); |
| |
|
| | |
| | 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); |
| | } |
| | }); |
| |
|
| | |
| |
|
| | |
| | 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); |
| | } |
| | } |
| |
|