MikaFil commited on
Commit
7586f13
·
verified ·
1 Parent(s): 13c3fa9

Update tooltips.js

Browse files
Files changed (1) hide show
  1. tooltips.js +36 -125
tooltips.js CHANGED
@@ -4,30 +4,17 @@
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,20 +22,16 @@ export async function initializeTooltips(options) {
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,51 +41,35 @@ export async function initializeTooltips(options) {
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,7 +79,6 @@ export async function initializeTooltips(options) {
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,7 +87,6 @@ export async function initializeTooltips(options) {
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,130 +109,77 @@ export async function initializeTooltips(options) {
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,7 +191,6 @@ export async function initializeTooltips(options) {
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) {
 
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
 
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
  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
  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
  let closestT = Infinity;
88
  let pickedEntity = null;
89
 
 
90
  for (const ent of tooltipEntities) {
91
  if (!ent.enabled) continue;
92
 
 
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
  orbitCam._targetDistance = newDist;
192
  orbitCam._distance = newDist;
193
 
 
194
  orbitCam._updatePosition();
195
 
196
  if (t >= 1) {