MikaFil commited on
Commit
677241c
·
verified ·
1 Parent(s): 35f2326

Create tooltips.js

Browse files
Files changed (1) hide show
  1. tooltips.js +234 -0
tooltips.js ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // tooltips.js
2
+
3
+ /**
4
+ * initializeTooltips(options)
5
+ *
6
+ * - options.app: the PlayCanvas App instance
7
+ * - options.cameraEntity: the PlayCanvas camera Entity
8
+ * - options.modelEntity: the main model entity (for any relative positioning; optional)
9
+ * - options.tooltipsUrl: URL to fetch JSON array of tooltip definitions
10
+ * - options.defaultVisible: boolean: whether tooltips are visible initially
11
+ * - options.moveDuration: number (seconds) for smooth camera move to selected tooltip
12
+ */
13
+ export async function initializeTooltips(options) {
14
+ const {
15
+ app,
16
+ cameraEntity,
17
+ modelEntity,
18
+ tooltipsUrl,
19
+ defaultVisible,
20
+ moveDuration = 0.6
21
+ } = options;
22
+
23
+ if (!app || !cameraEntity || !tooltipsUrl) {
24
+ console.error("tooltips.js → missing required initialization options");
25
+ return;
26
+ }
27
+
28
+ // Load JSON of tooltips:
29
+ let tooltipsData;
30
+ try {
31
+ const resp = await fetch(tooltipsUrl);
32
+ tooltipsData = await resp.json();
33
+ } catch (e) {
34
+ console.error("tooltips.js → failed fetching tooltips.json:", e);
35
+ return;
36
+ }
37
+ if (!Array.isArray(tooltipsData)) {
38
+ console.error("tooltips.js → tooltips.json must be an array");
39
+ return;
40
+ }
41
+
42
+ const tooltipEntities = [];
43
+
44
+ // Create a material for tooltip spheres
45
+ const mat = new pc.StandardMaterial();
46
+ mat.diffuse = new pc.Color(1, 0.8, 0);
47
+ mat.specular = new pc.Color(1, 1, 1);
48
+ mat.shininess = 20;
49
+ mat.update();
50
+
51
+ // Build each tooltip sphere + attach custom data
52
+ for (let i = 0; i < tooltipsData.length; i++) {
53
+ const tt = tooltipsData[i];
54
+ const { x, y, z, title, description, imgUrl } = tt;
55
+
56
+ const sphere = new pc.Entity("tooltip-" + i);
57
+ sphere.addComponent("model", { type: "sphere" });
58
+ sphere.model.material = mat;
59
+
60
+ // Scale small (primitive sphere radius = 0.5)
61
+ sphere.setLocalScale(0.05, 0.05, 0.05);
62
+ sphere.setLocalPosition(x, y, z);
63
+ sphere.tooltipData = { title, description, imgUrl };
64
+ app.root.addChild(sphere);
65
+ tooltipEntities.push(sphere);
66
+ }
67
+
68
+ // Show/hide all tooltip spheres
69
+ function setTooltipsVisibility(visible) {
70
+ tooltipEntities.forEach(ent => {
71
+ ent.enabled = visible;
72
+ });
73
+ }
74
+ setTooltipsVisibility(!!defaultVisible);
75
+
76
+ // Respond to toggle-tooltips event from interface.js
77
+ document.addEventListener("toggle-tooltips", (evt) => {
78
+ const { visible } = evt.detail;
79
+ setTooltipsVisibility(!!visible);
80
+ });
81
+
82
+ // Keep track of any in-flight camera tween so we can cancel it
83
+ let currentTween = null;
84
+
85
+ // On mouse down (or touch equivalent), perform manual ray‐sphere intersection
86
+ app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => {
87
+ // If a tween is running, cancel it immediately
88
+ if (currentTween) {
89
+ app.off("update", currentTween);
90
+ currentTween = null;
91
+ }
92
+
93
+ const x = event.x;
94
+ const y = event.y;
95
+ const from = new pc.Vec3();
96
+ const to = new pc.Vec3();
97
+ const camera = cameraEntity.camera;
98
+
99
+ camera.screenToWorld(x, y, camera.nearClip, from);
100
+ camera.screenToWorld(x, y, camera.farClip, to);
101
+
102
+ const dir = new pc.Vec3().sub2(to, from).normalize();
103
+
104
+ let closestT = Infinity;
105
+ let pickedEntity = null;
106
+
107
+ for (const ent of tooltipEntities) {
108
+ if (!ent.enabled) continue;
109
+
110
+ const center = ent.getPosition();
111
+ const worldRadius = 0.5 * ent.getLocalScale().x;
112
+
113
+ const oc = new pc.Vec3().sub2(center, from);
114
+ const tca = oc.dot(dir);
115
+ if (tca < 0) continue;
116
+
117
+ const d2 = oc.lengthSq() - (tca * tca);
118
+ if (d2 > worldRadius * worldRadius) continue;
119
+
120
+ const thc = Math.sqrt(worldRadius * worldRadius - d2);
121
+ const t0 = tca - thc;
122
+ if (t0 < closestT && t0 >= 0) {
123
+ closestT = t0;
124
+ pickedEntity = ent;
125
+ }
126
+ }
127
+
128
+ if (pickedEntity) {
129
+ const { title, description, imgUrl } = pickedEntity.tooltipData;
130
+ document.dispatchEvent(new CustomEvent("tooltip-selected", {
131
+ detail: { title, description, imgUrl }
132
+ }));
133
+ tweenCameraToTooltip(pickedEntity, moveDuration);
134
+ }
135
+ });
136
+
137
+ // Also close tooltip panel if user interacts (mouse or touch) on the canvas
138
+ const canvasId = app.graphicsDevice.canvas.id;
139
+ const htmlCanvas = document.getElementById(canvasId);
140
+ if (htmlCanvas) {
141
+ htmlCanvas.addEventListener("mousedown", () => {
142
+ document.dispatchEvent(new CustomEvent("hide-tooltip"));
143
+ });
144
+ htmlCanvas.addEventListener("touchstart", () => {
145
+ document.dispatchEvent(new CustomEvent("hide-tooltip"));
146
+ });
147
+ }
148
+
149
+ // Tween helper: smoothly move and reorient camera to focus the chosen tooltip sphere
150
+ function tweenCameraToTooltip(tooltipEnt, duration) {
151
+ const orbitCam = cameraEntity.script.orbitCamera;
152
+ if (!orbitCam) return;
153
+
154
+ // Compute target pivot exactly at the sphere center
155
+ const targetPos = tooltipEnt.getPosition().clone();
156
+ // Compute current state
157
+ const startPivot = orbitCam.pivotPoint.clone();
158
+ const startYaw = orbitCam._yaw;
159
+ const startPitch = orbitCam._pitch;
160
+ const startDist = orbitCam._distance;
161
+
162
+ // Compute direction & candidate distance:
163
+ const worldRadius = 0.5 * tooltipEnt.getLocalScale().x;
164
+ const minZoom = orbitCam.distanceMin;
165
+ const desiredDistance = Math.max(minZoom * 1.2, worldRadius * 4);
166
+
167
+ // Compute target yaw/pitch from camera pointing at targetPos
168
+ // Reuse reset logic: place a temp entity at camera’s current position, have it look at target
169
+ const camWorldPos = cameraEntity.getPosition().clone();
170
+ const tempEnt = new pc.Entity();
171
+ tempEnt.setPosition(camWorldPos);
172
+ tempEnt.lookAt(targetPos);
173
+ const rotation = tempEnt.getRotation();
174
+ const forward = new pc.Vec3();
175
+ rotation.transformVector(pc.Vec3.FORWARD, forward);
176
+ const tgtYaw = Math.atan2(-forward.x, -forward.z) * pc.math.RAD_TO_DEG;
177
+ const yawQuat = new pc.Quat().setFromEulerAngles(0, -tgtYaw, 0);
178
+ const rotNoYaw = new pc.Quat().mul2(yawQuat, rotation);
179
+ const fNoYaw = new pc.Vec3();
180
+ rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
181
+ const tgtPitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
182
+ tempEnt.destroy();
183
+
184
+ // Target state:
185
+ const endPivot = targetPos.clone();
186
+ const endYaw = tgtYaw;
187
+ const endPitch = tgtPitch;
188
+ const endDist = desiredDistance;
189
+
190
+ let elapsed = 0;
191
+ const orgPivot = startPivot.clone();
192
+ const orgYaw = startYaw;
193
+ const orgPitch = startPitch;
194
+ const orgDist = startDist;
195
+
196
+ // If another tween is running, cancel it
197
+ if (currentTween) {
198
+ app.off("update", currentTween);
199
+ currentTween = null;
200
+ }
201
+
202
+ // Per-frame update
203
+ function lerpUpdate(dt) {
204
+ elapsed += dt;
205
+ const t = Math.min(elapsed / duration, 1);
206
+
207
+ // Interpolate pivot (vector lerp)
208
+ const newPivot = new pc.Vec3().lerp(orgPivot, endPivot, t);
209
+ orbitCam.pivotPoint.copy(newPivot);
210
+
211
+ // Interpolate yaw/pitch/distance (simple lerp)
212
+ const newYaw = pc.math.lerp(orgYaw, endYaw, t);
213
+ const newPitch = pc.math.lerp(orgPitch, endPitch, t);
214
+ const newDist = pc.math.lerp(orgDist, endDist, t);
215
+
216
+ orbitCam._targetYaw = newYaw;
217
+ orbitCam._yaw = newYaw;
218
+ orbitCam._targetPitch = newPitch;
219
+ orbitCam._pitch = newPitch;
220
+ orbitCam._targetDistance = newDist;
221
+ orbitCam._distance = newDist;
222
+
223
+ orbitCam._updatePosition();
224
+
225
+ if (t >= 1) {
226
+ app.off("update", lerpUpdate);
227
+ currentTween = null;
228
+ }
229
+ }
230
+
231
+ currentTween = lerpUpdate;
232
+ app.on("update", lerpUpdate);
233
+ }
234
+ }