MikaFil commited on
Commit
9791406
·
verified ·
1 Parent(s): 471c4f5

Create tooltips_pr_env.js

Browse files
deplacement_dans_env/tooltips_pr_env.js ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // tooltips.js
2
+
3
+ /**
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
34
+ } = 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);
55
+ mat.shininess = 20;
56
+ mat.emissive = new pc.Color(0.85, 0.85, 0.85);
57
+ mat.emissiveIntensity = 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;
109
+ }
110
+
111
+ const x = event.x, y = event.y;
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
+
119
+ const dir = new pc.Vec3().sub2(to, from).normalize();
120
+
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
+
128
+ const center = ent.getPosition();
129
+ const worldRadius = 0.5 * ent.getLocalScale().x;
130
+
131
+ const oc = new pc.Vec3().sub2(center, from);
132
+ const tca = oc.dot(dir);
133
+ if (tca < 0) continue;
134
+
135
+ const d2 = oc.lengthSq() - (tca * tca);
136
+ if (d2 > worldRadius * worldRadius) continue;
137
+
138
+ const thc = Math.sqrt(worldRadius * worldRadius - d2);
139
+ const t0 = tca - thc;
140
+ if (t0 < closestT && t0 >= 0) {
141
+ closestT = t0;
142
+ pickedEntity = ent;
143
+ }
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);
274
+
275
+ orbitCam._targetYaw = newYaw;
276
+ orbitCam._yaw = newYaw;
277
+ orbitCam._targetPitch = newPitch;
278
+ orbitCam._pitch = newPitch;
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) {
286
+ app.off("update", lerpUpdate);
287
+ currentTween = null;
288
+ }
289
+ }
290
+
291
+ currentTween = lerpUpdate;
292
+ app.on("update", lerpUpdate);
293
+ }
294
+ }