MikaFil commited on
Commit
3af5ba5
·
verified ·
1 Parent(s): 4eefe5f

Update tooltips.js

Browse files
Files changed (1) hide show
  1. tooltips.js +125 -36
tooltips.js CHANGED
@@ -4,17 +4,30 @@
4
  * initializeTooltips(options)
5
  *
6
  * - options.app: the PlayCanvas AppBase instance
7
- * - options.cameraEntity: PlayCanvas camera Entity
8
  * - options.modelEntity: the main model entity (for 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
@@ -22,16 +35,20 @@ export async function initializeTooltips(options) {
22
 
23
  if (!app || !cameraEntity || !tooltipsUrl) return;
24
 
 
25
  let tooltipsData;
26
  try {
27
  const resp = await fetch(tooltipsUrl);
28
  tooltipsData = await resp.json();
29
- } catch (e) { return; }
 
 
 
30
  if (!Array.isArray(tooltipsData)) return;
31
 
32
  const tooltipEntities = [];
33
 
34
- // Create a material for tooltip spheres (still use StandardMaterial, works fine)
35
  const mat = new pc.StandardMaterial();
36
  mat.diffuse = new pc.Color(1, 0.8, 0);
37
  mat.specular = new pc.Color(1, 1, 1);
@@ -41,35 +58,51 @@ export async function initializeTooltips(options) {
41
  mat.useLighting = false;
42
  mat.update();
43
 
44
- // Build each tooltip sphere + attach custom data
45
  for (let i = 0; i < tooltipsData.length; i++) {
46
  const tt = tooltipsData[i];
47
- const { x, y, z, title, description, imgUrl } = tt;
48
 
49
  const sphere = new pc.Entity("tooltip-" + i);
50
  sphere.addComponent("model", { type: "sphere" });
51
  sphere.model.material = mat;
52
 
 
53
  sphere.setLocalScale(0.05, 0.05, 0.05);
54
  sphere.setLocalPosition(x, y, z);
55
- sphere.tooltipData = { title, description, imgUrl };
 
 
 
 
 
 
 
 
 
 
 
56
  app.root.addChild(sphere);
57
  tooltipEntities.push(sphere);
58
  }
59
 
 
60
  function setTooltipsVisibility(visible) {
61
  tooltipEntities.forEach(ent => { ent.enabled = visible; });
62
  }
63
  setTooltipsVisibility(!!defaultVisible);
64
 
 
65
  document.addEventListener("toggle-tooltips", (evt) => {
66
  const { visible } = evt.detail;
67
  setTooltipsVisibility(!!visible);
68
  });
69
 
 
70
  let currentTween = null;
71
 
72
  app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => {
 
73
  if (currentTween) {
74
  app.off("update", currentTween);
75
  currentTween = null;
@@ -79,6 +112,7 @@ export async function initializeTooltips(options) {
79
  const from = new pc.Vec3(), to = new pc.Vec3();
80
  const camera = cameraEntity.camera;
81
 
 
82
  camera.screenToWorld(x, y, camera.nearClip, from);
83
  camera.screenToWorld(x, y, camera.farClip, to);
84
 
@@ -87,6 +121,7 @@ export async function initializeTooltips(options) {
87
  let closestT = Infinity;
88
  let pickedEntity = null;
89
 
 
90
  for (const ent of tooltipEntities) {
91
  if (!ent.enabled) continue;
92
 
@@ -109,77 +144,130 @@ export async function initializeTooltips(options) {
109
  }
110
 
111
  if (pickedEntity) {
 
112
  const { title, description, imgUrl } = pickedEntity.tooltipData;
113
  document.dispatchEvent(new CustomEvent("tooltip-selected", {
114
  detail: { title, description, imgUrl }
115
  }));
116
- tweenCameraToTooltip(pickedEntity, moveDuration);
 
 
 
117
  }
118
  });
119
 
120
- // Camera animation
121
  function shortestAngleDiff(target, current) {
 
122
  let delta = target - current;
123
  delta = ((delta + 180) % 360 + 360) % 360 - 180;
124
  return delta;
125
  }
126
 
127
- function tweenCameraToTooltip(tooltipEnt, duration) {
128
- const orbitCam = cameraEntity.script.orbitCamera;
129
- if (!orbitCam) return;
130
-
131
- const targetPos = tooltipEnt.getPosition().clone();
132
- const startPivot = orbitCam.pivotPoint.clone();
133
- const startYaw = orbitCam._yaw;
134
- const startPitch = orbitCam._pitch;
135
- const startDist = orbitCam._distance;
136
-
137
- const worldRadius = 0.5 * tooltipEnt.getLocalScale().x;
138
- const minZoom = orbitCam.distanceMin;
139
- const desiredDistance = Math.max(minZoom * 1.2, worldRadius * 4);
140
-
141
- const camWorldPos = cameraEntity.getPosition().clone();
142
  const tempEnt = new pc.Entity();
143
- tempEnt.setPosition(camWorldPos);
144
- tempEnt.lookAt(targetPos);
 
145
  const rotation = tempEnt.getRotation();
 
 
146
  const forward = new pc.Vec3();
147
  rotation.transformVector(pc.Vec3.FORWARD, forward);
148
- const rawTgtYaw = Math.atan2(-forward.x, -forward.z) * pc.math.RAD_TO_DEG;
149
- const yawDelta = shortestAngleDiff(rawTgtYaw, startYaw);
150
- const endYaw = startYaw + yawDelta;
151
 
152
- const yawQuat = new pc.Quat().setFromEulerAngles(0, -rawTgtYaw, 0);
 
 
 
 
153
  const rotNoYaw = new pc.Quat().mul2(yawQuat, rotation);
154
  const fNoYaw = new pc.Vec3();
155
  rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
156
- const rawTgtPitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
157
- const pitchDelta = shortestAngleDiff(rawTgtPitch, startPitch);
158
- const endPitch = startPitch + pitchDelta;
 
 
159
 
160
  tempEnt.destroy();
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
- const endPivot = targetPos.clone();
163
- const endDist = desiredDistance;
164
 
165
- let elapsed = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  const orgPivot = startPivot.clone();
167
  const orgYaw = startYaw;
168
  const orgPitch = startPitch;
169
  const orgDist = startDist;
170
 
 
 
 
171
  if (currentTween) {
172
  app.off("update", currentTween);
173
  currentTween = null;
174
  }
175
 
 
176
  function lerpUpdate(dt) {
177
  elapsed += dt;
178
  const t = Math.min(elapsed / duration, 1);
179
 
 
180
  const newPivot = new pc.Vec3().lerp(orgPivot, endPivot, t);
181
  orbitCam.pivotPoint.copy(newPivot);
182
 
 
183
  const newYaw = pc.math.lerp(orgYaw, endYaw, t);
184
  const newPitch = pc.math.lerp(orgPitch, endPitch, t);
185
  const newDist = pc.math.lerp(orgDist, endDist, t);
@@ -191,6 +279,7 @@ export async function initializeTooltips(options) {
191
  orbitCam._targetDistance = newDist;
192
  orbitCam._distance = newDist;
193
 
 
194
  orbitCam._updatePosition();
195
 
196
  if (t >= 1) {
 
4
  * initializeTooltips(options)
5
  *
6
  * - options.app: the PlayCanvas AppBase instance
7
+ * - options.cameraEntity: PlayCanvas camera Entity (utilisant le script orbitCamera)
8
  * - options.modelEntity: the main model entity (for 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
+ * JSON attendu pour chaque tooltip :
14
+ * {
15
+ * x, y, z, // position du tooltip (obligatoire)
16
+ * title, description, imgUrl, // infos UI (optionnelles)
17
+ * camX, camY, camZ // position de caméra cible (optionnelles)
18
+ * }
19
+ *
20
+ * Comportement :
21
+ * - Si camX/camY/camZ sont fournis, la caméra se déplacera exactement
22
+ * vers (camX, camY, camZ) et s'orientera pour regarder le tooltip.
23
+ * - Sinon, on conserve l'ancien comportement : la caméra orbite vers le tooltip
24
+ * avec une distance calculée (zoom minimum + taille du tooltip).
25
  */
26
  export async function initializeTooltips(options) {
27
  const {
28
  app,
29
  cameraEntity,
30
+ modelEntity, // non utilisé directement ici mais conservé pour compat
31
  tooltipsUrl,
32
  defaultVisible,
33
  moveDuration = 0.6
 
35
 
36
  if (!app || !cameraEntity || !tooltipsUrl) return;
37
 
38
+ // --- Chargement du JSON de tooltips ---
39
  let tooltipsData;
40
  try {
41
  const resp = await fetch(tooltipsUrl);
42
  tooltipsData = await resp.json();
43
+ } catch (e) {
44
+ // Échec du fetch/parse JSON -> on abandonne proprement
45
+ return;
46
+ }
47
  if (!Array.isArray(tooltipsData)) return;
48
 
49
  const tooltipEntities = [];
50
 
51
+ // --- Matériau des sphères (tooltips) ---
52
  const mat = new pc.StandardMaterial();
53
  mat.diffuse = new pc.Color(1, 0.8, 0);
54
  mat.specular = new pc.Color(1, 1, 1);
 
58
  mat.useLighting = false;
59
  mat.update();
60
 
61
+ // --- Création des entités sphères pour chaque tooltip ---
62
  for (let i = 0; i < tooltipsData.length; i++) {
63
  const tt = tooltipsData[i];
64
+ const { x, y, z, title, description, imgUrl, camX, camY, camZ } = tt;
65
 
66
  const sphere = new pc.Entity("tooltip-" + i);
67
  sphere.addComponent("model", { type: "sphere" });
68
  sphere.model.material = mat;
69
 
70
+ // Taille par défaut des "pins"
71
  sphere.setLocalScale(0.05, 0.05, 0.05);
72
  sphere.setLocalPosition(x, y, z);
73
+
74
+ // On stocke toutes les infos utiles sur l'entité
75
+ sphere.tooltipData = {
76
+ title,
77
+ description,
78
+ imgUrl,
79
+ // Nouvelle partie : coordonnées de caméra cibles (optionnelles)
80
+ camTarget: (Number.isFinite(camX) && Number.isFinite(camY) && Number.isFinite(camZ))
81
+ ? new pc.Vec3(camX, camY, camZ)
82
+ : null
83
+ };
84
+
85
  app.root.addChild(sphere);
86
  tooltipEntities.push(sphere);
87
  }
88
 
89
+ // --- Gestion de la visibilité des tooltips ---
90
  function setTooltipsVisibility(visible) {
91
  tooltipEntities.forEach(ent => { ent.enabled = visible; });
92
  }
93
  setTooltipsVisibility(!!defaultVisible);
94
 
95
+ // Écouteur externe (ex. UI HTML) pour afficher/masquer les tooltips
96
  document.addEventListener("toggle-tooltips", (evt) => {
97
  const { visible } = evt.detail;
98
  setTooltipsVisibility(!!visible);
99
  });
100
 
101
+ // --- Picking (détection de clic sur un tooltip) ---
102
  let currentTween = null;
103
 
104
  app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => {
105
+ // Si une interpolation est en cours, on l'arrête proprement
106
  if (currentTween) {
107
  app.off("update", currentTween);
108
  currentTween = null;
 
112
  const from = new pc.Vec3(), to = new pc.Vec3();
113
  const camera = cameraEntity.camera;
114
 
115
+ // Ray picking écran -> monde
116
  camera.screenToWorld(x, y, camera.nearClip, from);
117
  camera.screenToWorld(x, y, camera.farClip, to);
118
 
 
121
  let closestT = Infinity;
122
  let pickedEntity = null;
123
 
124
+ // Test d'intersection rayon/sphère (simple et suffisant ici)
125
  for (const ent of tooltipEntities) {
126
  if (!ent.enabled) continue;
127
 
 
144
  }
145
 
146
  if (pickedEntity) {
147
+ // Notifier l'UI (titre, description, image)
148
  const { title, description, imgUrl } = pickedEntity.tooltipData;
149
  document.dispatchEvent(new CustomEvent("tooltip-selected", {
150
  detail: { title, description, imgUrl }
151
  }));
152
+
153
+ // Si on a une position caméra cible, on l'utilise
154
+ const desiredCamPos = pickedEntity.tooltipData.camTarget;
155
+ tweenCameraToTooltip(pickedEntity, moveDuration, desiredCamPos);
156
  }
157
  });
158
 
159
+ // --- Helpers math/angles ---
160
  function shortestAngleDiff(target, current) {
161
+ // Retourne l'écart angulaire [-180, 180] pour interpoler par le plus court chemin
162
  let delta = target - current;
163
  delta = ((delta + 180) % 360 + 360) % 360 - 180;
164
  return delta;
165
  }
166
 
167
+ /**
168
+ * Calcule {yaw, pitch, distance} pour une caméra à cameraPos regardant pivotPos,
169
+ * selon la convention de l'orbitCamera.
170
+ */
171
+ function computeOrbitFromPositions(cameraPos, pivotPos) {
 
 
 
 
 
 
 
 
 
 
172
  const tempEnt = new pc.Entity();
173
+ tempEnt.setPosition(cameraPos);
174
+ tempEnt.lookAt(pivotPos);
175
+
176
  const rotation = tempEnt.getRotation();
177
+
178
+ // Direction "forward" (de la caméra vers le pivot)
179
  const forward = new pc.Vec3();
180
  rotation.transformVector(pc.Vec3.FORWARD, forward);
 
 
 
181
 
182
+ // Yaw : rotation horizontale
183
+ const rawYaw = Math.atan2(-forward.x, -forward.z) * pc.math.RAD_TO_DEG;
184
+
185
+ // Pitch : on retire d'abord l'influence du yaw pour isoler la composante verticale
186
+ const yawQuat = new pc.Quat().setFromEulerAngles(0, -rawYaw, 0);
187
  const rotNoYaw = new pc.Quat().mul2(yawQuat, rotation);
188
  const fNoYaw = new pc.Vec3();
189
  rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
190
+ const rawPitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
191
+
192
+ // Distance : norme du vecteur pivot - caméra
193
+ const toPivot = new pc.Vec3().sub2(pivotPos, cameraPos);
194
+ const dist = toPivot.length();
195
 
196
  tempEnt.destroy();
197
+ return { yaw: rawYaw, pitch: rawPitch, distance: dist };
198
+ }
199
+
200
+ /**
201
+ * Animation caméra vers un tooltip.
202
+ * - tooltipEnt: entité du tooltip cliqué
203
+ * - duration: durée de l'interpolation (s)
204
+ * - overrideCamWorldPos (pc.Vec3|null): si fourni, la caméra ira EXACTEMENT à cette position
205
+ * tout en regardant le tooltip.
206
+ */
207
+ function tweenCameraToTooltip(tooltipEnt, duration, overrideCamWorldPos = null) {
208
+ const orbitCam = cameraEntity.script && cameraEntity.script.orbitCamera;
209
+ if (!orbitCam) return;
210
 
211
+ const targetPos = tooltipEnt.getPosition().clone();
 
212
 
213
+ // État initial (depuis l'orbitCamera)
214
+ const startPivot = orbitCam.pivotPoint.clone();
215
+ const startYaw = orbitCam._yaw;
216
+ const startPitch = orbitCam._pitch;
217
+ const startDist = orbitCam._distance;
218
+
219
+ // Valeurs finales à déterminer
220
+ let endPivot = targetPos.clone();
221
+ let endYaw, endPitch, endDist;
222
+
223
+ if (overrideCamWorldPos) {
224
+ // --- Nouveau mode : position caméra imposée par le JSON ---
225
+ // On calcule l'orbite (yaw/pitch/dist) qui correspond exactement à cette position
226
+ const { yaw, pitch, distance } = computeOrbitFromPositions(overrideCamWorldPos, targetPos);
227
+
228
+ // Interpolation par le plus court chemin depuis l'état courant
229
+ endYaw = startYaw + shortestAngleDiff(yaw, startYaw);
230
+ endPitch = startPitch + shortestAngleDiff(pitch, startPitch);
231
+ endDist = distance;
232
+ } else {
233
+ // --- Comportement historique (aucune camX/Y/Z fournie) ---
234
+ const worldRadius = 0.5 * tooltipEnt.getLocalScale().x;
235
+ const minZoom = orbitCam.distanceMin || 0.1;
236
+ const desiredDistance = Math.max(minZoom * 1.2, worldRadius * 4);
237
+
238
+ // On garde la position caméra actuelle comme point de départ pour calculer les angles
239
+ const camWorldPos = cameraEntity.getPosition().clone();
240
+ const { yaw, pitch } = computeOrbitFromPositions(camWorldPos, targetPos);
241
+
242
+ endYaw = startYaw + shortestAngleDiff(yaw, startYaw);
243
+ endPitch = startPitch + shortestAngleDiff(pitch, startPitch);
244
+ endDist = desiredDistance;
245
+ }
246
+
247
+ // Sauvegarde des origines pour l'interpolation
248
  const orgPivot = startPivot.clone();
249
  const orgYaw = startYaw;
250
  const orgPitch = startPitch;
251
  const orgDist = startDist;
252
 
253
+ let elapsed = 0;
254
+
255
+ // Si une interpolation était déjà en cours, on la débranche
256
  if (currentTween) {
257
  app.off("update", currentTween);
258
  currentTween = null;
259
  }
260
 
261
+ // --- Lerp frame-by-frame ---
262
  function lerpUpdate(dt) {
263
  elapsed += dt;
264
  const t = Math.min(elapsed / duration, 1);
265
 
266
+ // Pivot (regard) vers le tooltip
267
  const newPivot = new pc.Vec3().lerp(orgPivot, endPivot, t);
268
  orbitCam.pivotPoint.copy(newPivot);
269
 
270
+ // Yaw, Pitch, Distance (on met aussi les "target" pour rester cohérent avec le script orbitCamera)
271
  const newYaw = pc.math.lerp(orgYaw, endYaw, t);
272
  const newPitch = pc.math.lerp(orgPitch, endPitch, t);
273
  const newDist = pc.math.lerp(orgDist, endDist, t);
 
279
  orbitCam._targetDistance = newDist;
280
  orbitCam._distance = newDist;
281
 
282
+ // Mise à jour de la position monde de la caméra à partir des paramètres d'orbite
283
  orbitCam._updatePosition();
284
 
285
  if (t >= 1) {