kokixdx commited on
Commit
a5c13ae
·
verified ·
1 Parent(s): 84eb5c6

Upload 2 files

Browse files
Files changed (3) hide show
  1. .gitattributes +1 -0
  2. index.html +628 -0
  3. model18.glb +3 -0
.gitattributes CHANGED
@@ -69,3 +69,4 @@ model9.glb filter=lfs diff=lfs merge=lfs -text
69
  tc_fullscreen_5103103.png filter=lfs diff=lfs merge=lfs -text
70
  ch_fullscreen_100401.png filter=lfs diff=lfs merge=lfs -text
71
  model17.glb filter=lfs diff=lfs merge=lfs -text
 
 
69
  tc_fullscreen_5103103.png filter=lfs diff=lfs merge=lfs -text
70
  ch_fullscreen_100401.png filter=lfs diff=lfs merge=lfs -text
71
  model17.glb filter=lfs diff=lfs merge=lfs -text
72
+ model18.glb filter=lfs diff=lfs merge=lfs -text
index.html ADDED
@@ -0,0 +1,628 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Model Animation Selector - Responsive</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ html, body, #app-container {
10
+ height: 100%;
11
+ margin: 0;
12
+ overflow: hidden;
13
+ }
14
+ canvas {
15
+ display: block;
16
+ }
17
+ .character-icon-container {
18
+ @apply w-14 h-14 lg:w-20 lg:h-20 rounded-full p-0.5 flex items-center justify-center cursor-pointer transition-all duration-200 border-2 border-transparent bg-gray-700/50 hover:bg-gray-600/70;
19
+ overflow: hidden;
20
+ box-shadow: 0 4px 10px rgba(0,0,0,0.3);
21
+ flex-shrink: 0;
22
+ }
23
+ .character-icon-container.active {
24
+ @apply border-accent bg-accent/20 ring-4 ring-accent/50;
25
+ }
26
+ .character-icon {
27
+ @apply w-full h-full object-cover rounded-full;
28
+ }
29
+
30
+ #mobile-controls-drawer {
31
+ transform: translateX(100%);
32
+ transition: transform 0.3s ease-in-out;
33
+ z-index: 50;
34
+ }
35
+
36
+ #mobile-controls-drawer.open {
37
+ transform: translateX(0);
38
+ }
39
+
40
+ #help-modal-overlay {
41
+ @apply fixed inset-0 bg-black/70 flex items-center justify-center z-[100] transition-opacity duration-300 opacity-0 pointer-events-none;
42
+ }
43
+
44
+ #help-modal-overlay.visible {
45
+ @apply opacity-100 pointer-events-auto;
46
+ }
47
+
48
+ #help-modal-content {
49
+ @apply bg-secondary-bg rounded-xl shadow-2xl p-6 m-4 w-full max-w-lg transform translate-y-[-50px] transition-transform duration-300;
50
+ }
51
+
52
+ #help-modal-overlay.visible #help-modal-content {
53
+ @apply translate-y-0;
54
+ }
55
+ </style>
56
+ <script>
57
+ tailwind.config = {
58
+ theme: {
59
+ extend: {
60
+ fontFamily: {
61
+ sans: ['Inter', 'sans-serif'],
62
+ },
63
+ colors: {
64
+ 'primary-bg': '#1f2937',
65
+ 'secondary-bg': '#374151',
66
+ 'accent': '#60a5fa',
67
+ }
68
+ }
69
+ }
70
+ }
71
+ </script>
72
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
73
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
74
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
75
+
76
+ </head>
77
+ <body class="bg-primary-bg font-sans text-gray-100">
78
+
79
+ <div class="lg:hidden p-3 bg-secondary-bg flex items-center justify-between shadow-lg flex-shrink-0 order-1">
80
+ <h1 class="text-xl font-bold text-accent">3D Player</h1>
81
+
82
+ <button id="burger-menu-btn" class="p-2 rounded-md hover:bg-gray-600 transition duration-150">
83
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
84
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
85
+ </svg>
86
+ </button>
87
+ </div>
88
+
89
+ <div id="app-container" class="flex flex-col lg:flex-row flex-grow h-full overflow-hidden">
90
+
91
+ <div id="character-selector" class="p-4 bg-secondary-bg shadow-lg w-full flex-shrink-0
92
+ order-3 lg:order-1 lg:w-40 h-auto lg:h-full
93
+ flex flex-row lg:flex-col space-x-4 lg:space-x-0 lg:space-y-4
94
+ overflow-x-auto lg:overflow-x-hidden lg:overflow-y-auto border-t lg:border-t-0 lg:border-r border-gray-700">
95
+ </div>
96
+
97
+ <div id="canvas-container" class="flex-grow order-2 relative h-full">
98
+ </div>
99
+
100
+ <div id="desktop-controls-panel" class="p-6 bg-secondary-bg shadow-lg lg:w-80 w-full flex-shrink-0 lg:block hidden lg:order-3">
101
+ </div>
102
+
103
+ </div>
104
+
105
+ <div id="mobile-controls-drawer" class="fixed top-0 right-0 w-3/4 h-full bg-secondary-bg shadow-2xl overflow-y-auto lg:hidden">
106
+ <div class="p-6">
107
+ <div class="flex justify-between items-center mb-6 border-b border-gray-600 pb-3">
108
+ <h2 class="text-xl font-bold text-accent">Animation Options</h2>
109
+ <button id="close-menu-btn" class="text-gray-400 hover:text-white transition duration-150">
110
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
111
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
112
+ </svg>
113
+ </button>
114
+ </div>
115
+
116
+ <div id="controls-content">
117
+ <p class="text-sm mb-6 text-gray-300">
118
+ Set animation, freeze frames, and camera options here.
119
+ </p>
120
+
121
+ <div class="mb-4">
122
+ <label for="animation-select" class="block text-sm font-medium mb-2">Select Animation:</label>
123
+ <select id="animation-select"
124
+ class="w-full p-2 border border-gray-600 bg-gray-700 text-white rounded-md focus:ring-accent focus:border-accent shadow-inner transition duration-150 ease-in-out"
125
+ disabled>
126
+ <option value="" disabled selected>Loading...</option>
127
+ </select>
128
+ </div>
129
+
130
+ <div class="mb-6 flex items-center justify-between p-3 bg-gray-700 rounded-md">
131
+ <label for="freeze-toggle" class="text-sm font-medium">Freeze Last Frame</label>
132
+ <input type="checkbox" id="freeze-toggle" class="h-4 w-4 text-accent border-gray-600 rounded focus:ring-accent bg-gray-800">
133
+ </div>
134
+
135
+ <div class="space-y-4 mb-6">
136
+ <button id="reset-camera-btn" class="w-full bg-accent hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md transition duration-150 shadow-lg">
137
+ Reset Camera View
138
+ </button>
139
+ </div>
140
+
141
+ <div id="loading-indicator" class="mt-4 p-3 bg-blue-900/50 text-accent rounded-lg flex items-center space-x-2">
142
+ <svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
143
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
144
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
145
+ </svg>
146
+ <span>Loading 3D model...</span>
147
+ </div>
148
+
149
+ <div class="mt-8 pt-4 border-t border-gray-600">
150
+ <p class="text-xs text-gray-400" id="model-credit">Model: model.glb (Your Custom Model)</p>
151
+ </div>
152
+ </div>
153
+
154
+ </div>
155
+ </div>
156
+
157
+ <div id="mobile-menu-backdrop" class="fixed inset-0 bg-black/50 hidden z-40 lg:hidden"></div>
158
+
159
+ <div id="help-modal-overlay" class="fixed inset-0 bg-black/70 flex items-center justify-center z-[100] transition-opacity duration-300 opacity-0 pointer-events-none">
160
+ <div id="help-modal-content" class="bg-secondary-bg rounded-xl shadow-2xl p-6 m-4 w-full max-w-lg transform translate-y-[-50px] transition-transform duration-300">
161
+ <div class="flex justify-between items-center border-b border-gray-600 pb-3 mb-4">
162
+ <h3 class="text-xl font-bold text-accent">How to Use the Viewer</h3>
163
+ <button onclick="hideHelpModal()" class="text-gray-400 hover:text-white transition duration-150">
164
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
165
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
166
+ </svg>
167
+ </button>
168
+ </div>
169
+ <div class="space-y-4 text-gray-300">
170
+ <div>
171
+ <h4 class="font-semibold text-white mb-2">Desktop (PC/Mac)</h4>
172
+ <ul class="list-disc list-inside space-y-1 ml-4 text-sm">
173
+ <li>**Rotate Camera:** Click and drag the model area.</li>
174
+ <li>**Pan Camera:** Right-click and drag the model area.</li>
175
+ <li>**Zoom In/Out:** Use the mouse scroll wheel.</li>
176
+ <li>**Change Character:** Click the icons on the left panel.</li>
177
+ <li>**Change Animation:** Use the **Select Animation** dropdown on the right panel.</li>
178
+ </ul>
179
+ </div>
180
+ <div>
181
+ <h4 class="font-semibold text-white mb-2">Mobile (iPhone/Android)</h4>
182
+ <ul class="list-disc list-inside space-y-1 ml-4 text-sm">
183
+ <li>**Rotate Camera:** Drag a single finger across the screen.</li>
184
+ <li>**Pan Camera:** Drag two fingers across the screen.</li>
185
+ <li>**Zoom In/Out:** Pinch the screen with two fingers.</li>
186
+ <li>**Change Character:** Scroll the character bar at the bottom and tap an icon.</li>
187
+ <li>**Controls:** Tap the **Menu (☰)** icon to access the **Select Animation** and other controls.</li>
188
+ </ul>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <button id="help-btn" onclick="showHelpModal()" class="fixed bottom-4 right-4 bg-accent hover:bg-blue-600 text-white p-3 rounded-full shadow-xl transition-all duration-300 z-50 transform hover:scale-105">
195
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
196
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c0-.424.43-.808 1.054-.925l.848-.15c.624-.117 1.054-.501 1.054-.925v-1c0-.424-.43-.808-1.054-.925l-.848-.15c-.624-.117-1.054-.501-1.054-.925v-1c0-.424.43-.808 1.054-.925l.848-.15c.624-.117 1.054-.501 1.054-.925zM12 20v-4" />
197
+ </svg>
198
+ </button>
199
+
200
+
201
+ <script>
202
+ let scene, camera, renderer, controls;
203
+ let mixer;
204
+ let actions = {};
205
+ let activeAction;
206
+ let currentModel = null;
207
+ const clock = new THREE.Clock();
208
+
209
+ let modelTargetCenter = new THREE.Vector3(0, 1.0, 0);
210
+ let initialCameraPosition = new THREE.Vector3(0, 1.5, 3);
211
+
212
+ const TARGET_HEIGHT = 2;
213
+ let freezeLastFrame = false;
214
+
215
+ const CHARACTER_DATA = {
216
+ 'model': {
217
+ name: 'Character 1 (model)',
218
+ url: 'model.glb',
219
+ image: 'ch_fullscreen_101101.png',
220
+ num: 1
221
+ },
222
+ 'model1': {
223
+ name: 'Character 2 (model1)',
224
+ url: 'model1.glb',
225
+ image: 'ch_fullscreen_102901.png',
226
+ num: 2
227
+ }
228
+ };
229
+
230
+ const imageMap = [
231
+ 'ch_fullscreen_101401.png',
232
+ 'ch_fullscreen_103001.png',
233
+ 'ch_fullscreen_100101.png',
234
+ 'ch_fullscreen_101501.png',
235
+ 'ch_fullscreen_100201.png',
236
+ 'ch_fullscreen_100701.png',
237
+ 'ch_fullscreen_100901.png',
238
+ 'ch_fullscreen_100601.png',
239
+ 'ch_fullscreen_101201.png',
240
+ 'tc_fullscreen_5103103.png',
241
+ 'ch_fullscreen_100501.png',
242
+ 'ch_fullscreen_100301.png',
243
+ 'ch_fullscreen_100801.png',
244
+ 'ch_fullscreen_101001.png',
245
+ 'ch_fullscreen_101301.png',
246
+ 'ch_fullscreen_100401.png', // Index 15 (for model17)
247
+ // Added 6 more placeholder entries for model18 to model23
248
+ 'ch_fullscreen_101601.png',
249
+ 'ch_fullscreen_101701.png',
250
+ 'ch_fullscreen_101801.png',
251
+ 'ch_fullscreen_101901.png',
252
+ 'ch_fullscreen_102001.png',
253
+ 'ch_fullscreen_102101.png',
254
+ ];
255
+
256
+ // The loop condition is updated from i <= 17 to i <= 23 to include model18 up to model23.
257
+ for (let i = 2; i <= 23; i++) {
258
+ const modelKey = `model${i}`;
259
+ const charNumber = i + 1;
260
+
261
+ let imageUrl = imageMap[i - 2];
262
+
263
+ CHARACTER_DATA[modelKey] = {
264
+ name: `Character ${charNumber} (${modelKey})`,
265
+ url: `${modelKey}.glb`,
266
+ image: imageUrl,
267
+ num: charNumber
268
+ };
269
+ }
270
+
271
+ let currentCharacterId = 'model';
272
+
273
+ const container = document.getElementById('canvas-container');
274
+ const selectorContainer = document.getElementById('character-selector');
275
+
276
+ const mobileDrawer = document.getElementById('mobile-controls-drawer');
277
+ const desktopPanel = document.getElementById('desktop-controls-panel');
278
+ const burgerMenuBtn = document.getElementById('burger-menu-btn');
279
+ const closeMenuBtn = document.getElementById('close-menu-btn');
280
+ const menuBackdrop = document.getElementById('mobile-menu-backdrop');
281
+ const helpModalOverlay = document.getElementById('help-modal-overlay');
282
+
283
+
284
+ const selectElement = document.getElementById('animation-select');
285
+ const loadingIndicator = document.getElementById('loading-indicator');
286
+ const freezeToggle = document.getElementById('freeze-toggle');
287
+ const modelCredit = document.getElementById('model-credit');
288
+ const resetCameraButton = document.getElementById('reset-camera-btn');
289
+
290
+ function showHelpModal() {
291
+ helpModalOverlay.classList.add('visible');
292
+ document.body.style.overflow = 'hidden';
293
+ }
294
+
295
+ function hideHelpModal() {
296
+ helpModalOverlay.classList.remove('visible');
297
+ document.body.style.overflow = 'auto';
298
+ }
299
+
300
+ function unloadModel() {
301
+ if (currentModel) {
302
+ scene.remove(currentModel);
303
+ currentModel = null;
304
+ }
305
+ mixer = null;
306
+ actions = {};
307
+ activeAction = null;
308
+
309
+ selectElement.innerHTML = '<option value="" disabled selected>Loading...</option>';
310
+ selectElement.disabled = true;
311
+ modelCredit.textContent = 'Model: Loading...';
312
+ }
313
+
314
+ function resetCamera() {
315
+ camera.position.copy(initialCameraPosition);
316
+ controls.target.copy(modelTargetCenter);
317
+ controls.update();
318
+ }
319
+
320
+ function initScene() {
321
+ scene = new THREE.Scene();
322
+ scene.background = new THREE.Color(0x3e2b7a);
323
+
324
+ const aspect = container.clientWidth / container.clientHeight;
325
+ camera = new THREE.PerspectiveCamera(50, aspect, 0.001, 100);
326
+ camera.position.copy(initialCameraPosition);
327
+
328
+ renderer = new THREE.WebGLRenderer({ antialias: true });
329
+ renderer.setPixelRatio(window.devicePixelRatio);
330
+ renderer.setSize(container.clientWidth, container.clientHeight);
331
+ container.appendChild(renderer.domElement);
332
+
333
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
334
+ controls.target.copy(modelTargetCenter);
335
+ controls.minDistance = 0.5;
336
+ controls.update();
337
+
338
+ window.addEventListener('resize', onWindowResize);
339
+
340
+ burgerMenuBtn.addEventListener('click', () => toggleMobileMenu(true));
341
+ closeMenuBtn.addEventListener('click', () => toggleMobileMenu(false));
342
+ menuBackdrop.addEventListener('click', () => toggleMobileMenu(false));
343
+
344
+ desktopPanel.innerHTML = document.getElementById('controls-content').innerHTML;
345
+
346
+ const desktopSelect = desktopPanel.querySelector('#animation-select');
347
+ const desktopFreeze = desktopPanel.querySelector('#freeze-toggle');
348
+ const desktopReset = desktopPanel.querySelector('#reset-camera-btn');
349
+
350
+ selectElement.addEventListener('change', onAnimationChange);
351
+ desktopSelect.addEventListener('change', onAnimationChange);
352
+
353
+ resetCameraButton.addEventListener('click', resetCamera);
354
+ desktopReset.addEventListener('click', resetCamera);
355
+
356
+ freezeToggle.addEventListener('change', (e) => updateAnimationLoop(e.target));
357
+ desktopFreeze.addEventListener('change', (e) => updateAnimationLoop(e.target));
358
+ }
359
+
360
+ function toggleMobileMenu(open) {
361
+ if (open) {
362
+ mobileDrawer.classList.add('open');
363
+ menuBackdrop.classList.remove('hidden');
364
+ document.body.style.overflow = 'hidden';
365
+ } else {
366
+ mobileDrawer.classList.remove('open');
367
+ menuBackdrop.classList.add('hidden');
368
+ document.body.style.overflow = 'auto';
369
+ }
370
+ }
371
+
372
+
373
+ function loadModel(url) {
374
+ unloadModel();
375
+ loadingIndicator.classList.remove('hidden');
376
+
377
+ const loader = new THREE.GLTFLoader();
378
+ const currentCharacter = CHARACTER_DATA[currentCharacterId];
379
+
380
+ loader.load(url, function (gltf) {
381
+ const model = gltf.scene;
382
+ currentModel = model;
383
+
384
+ model.traverse((child) => {
385
+ if (child.isMesh) {
386
+ child.frustumCulled = false;
387
+
388
+ const basicMaterial = new THREE.MeshBasicMaterial();
389
+
390
+ if (child.material.map) {
391
+ basicMaterial.map = child.material.map;
392
+ }
393
+ if (child.material.color) {
394
+ basicMaterial.color.copy(child.material.color);
395
+ }
396
+
397
+ basicMaterial.side = THREE.DoubleSide;
398
+
399
+ if (child.material.skinning === true) {
400
+ basicMaterial.skinning = true;
401
+ }
402
+
403
+ child.material = basicMaterial;
404
+ }
405
+ });
406
+
407
+ model.updateMatrixWorld(true);
408
+ let box = new THREE.Box3().setFromObject(model);
409
+ let size = new THREE.Vector3();
410
+ box.getSize(size);
411
+
412
+ if (size.y > 0) {
413
+ const scaleFactor = TARGET_HEIGHT / size.y;
414
+ model.scale.set(scaleFactor, scaleFactor, scaleFactor);
415
+ }
416
+
417
+ scene.add(model);
418
+
419
+ model.updateMatrixWorld(true);
420
+ box.setFromObject(model);
421
+ const center = new THREE.Vector3();
422
+ box.getCenter(center);
423
+ box.getSize(size);
424
+
425
+ const yShift = -box.min.y;
426
+ model.position.y += yShift;
427
+
428
+ model.updateMatrixWorld(true);
429
+ box.setFromObject(model);
430
+ box.getCenter(center);
431
+ box.getSize(size);
432
+
433
+ modelTargetCenter.copy(center);
434
+
435
+ const maxDim = Math.max(size.x, size.y, size.z);
436
+ const initialDistance = maxDim * 1.5;
437
+
438
+ controls.minDistance = maxDim * 0.001;
439
+ initialCameraPosition.set(center.x, center.y + (maxDim * 0.2), center.z + initialDistance);
440
+
441
+ camera.position.copy(initialCameraPosition);
442
+ controls.target.copy(modelTargetCenter);
443
+
444
+ camera.updateProjectionMatrix();
445
+ controls.update();
446
+
447
+ mixer = new THREE.AnimationMixer(model);
448
+
449
+ if (gltf.animations && gltf.animations.length > 0) {
450
+ selectElement.innerHTML = '';
451
+ desktopPanel.querySelector('#animation-select').innerHTML = '';
452
+
453
+ gltf.animations.forEach((clip) => {
454
+ const action = mixer.clipAction(clip);
455
+ actions[clip.name] = action;
456
+
457
+ const option = document.createElement('option');
458
+ option.value = clip.name;
459
+ option.textContent = clip.name.replace(/_/g, ' ');
460
+
461
+ selectElement.appendChild(option.cloneNode(true));
462
+ desktopPanel.querySelector('#animation-select').appendChild(option);
463
+ });
464
+
465
+ let initialClipName = gltf.animations[0].name;
466
+ const idleClip = gltf.animations.find(clip => clip.name.toLowerCase().includes('idle'));
467
+
468
+ if (idleClip) {
469
+ initialClipName = idleClip.name;
470
+ }
471
+
472
+ selectElement.value = initialClipName;
473
+ desktopPanel.querySelector('#animation-select').value = initialClipName;
474
+
475
+ activeAction = actions[initialClipName];
476
+
477
+ updateAnimationLoop(freezeToggle);
478
+ activeAction.play();
479
+
480
+ loadingIndicator.classList.add('hidden');
481
+ selectElement.disabled = false;
482
+ desktopPanel.querySelector('#animation-select').disabled = false;
483
+ } else {
484
+ loadingIndicator.innerHTML = `Model loaded, but no animations found. (${currentCharacter.url})`;
485
+ selectElement.innerHTML = '<option value="" disabled selected>No Animations</option>';
486
+ desktopPanel.querySelector('#animation-select').innerHTML = '<option value="" disabled selected>No Animations</option>';
487
+ }
488
+
489
+ modelCredit.textContent = `Model: ${currentCharacter.name} (${currentCharacter.url})`;
490
+
491
+ }, undefined, function (error) {
492
+ console.error('An error occurred while loading the model:', error);
493
+ loadingIndicator.innerHTML = `Error loading model (${currentCharacter.name}). The file (${currentCharacter.url}) might be missing.`;
494
+ modelCredit.textContent = `Model: ${currentCharacter.name} (Load Failed)`;
495
+ });
496
+ }
497
+
498
+ function loadCharacter(id) {
499
+ currentCharacterId = id;
500
+ const character = CHARACTER_DATA[id];
501
+
502
+ document.querySelectorAll('.character-icon-container').forEach(icon => {
503
+ icon.classList.remove('active');
504
+ });
505
+ const activeIcon = document.getElementById(`icon-${id}`);
506
+ if(activeIcon) activeIcon.classList.add('active');
507
+
508
+ loadModel(character.url);
509
+ }
510
+
511
+ function createCharacterSelectors() {
512
+ Object.keys(CHARACTER_DATA).forEach(id => {
513
+ const char = CHARACTER_DATA[id];
514
+
515
+ const iconContainer = document.createElement('div');
516
+ iconContainer.id = `icon-${id}`;
517
+ iconContainer.className = 'character-icon-container';
518
+ iconContainer.title = char.name;
519
+
520
+ const iconImage = document.createElement('img');
521
+ iconImage.className = 'character-icon';
522
+ iconImage.src = char.image;
523
+ iconImage.alt = char.name;
524
+
525
+ iconImage.onerror = function() {
526
+ const fallbackText = `C${char.num}`;
527
+ this.src = `https://placehold.co/80x80/555555/dddddd?text=${fallbackText}`;
528
+ this.onerror = null;
529
+ };
530
+
531
+ if (id === currentCharacterId) {
532
+ iconContainer.classList.add('active');
533
+ }
534
+
535
+ iconContainer.addEventListener('click', () => loadCharacter(id));
536
+ iconContainer.appendChild(iconImage);
537
+ selectorContainer.appendChild(iconContainer);
538
+ });
539
+ }
540
+
541
+ function updateAnimationLoop(sourceElement) {
542
+ freezeLastFrame = sourceElement.checked;
543
+
544
+ const desktopFreeze = desktopPanel.querySelector('#freeze-toggle');
545
+ if (sourceElement.id === 'freeze-toggle') {
546
+ desktopFreeze.checked = freezeLastFrame;
547
+ } else {
548
+ freezeToggle.checked = freezeLastFrame;
549
+ }
550
+
551
+ const loopMode = freezeLastFrame ? THREE.LoopOnce : THREE.LoopRepeat;
552
+ const clamp = freezeLastFrame;
553
+
554
+ Object.values(actions).forEach(action => {
555
+ action.setLoop(loopMode);
556
+ action.clampWhenFinished = clamp;
557
+ });
558
+
559
+ if (freezeLastFrame && activeAction) {
560
+ activeAction.stop().play();
561
+ }
562
+ }
563
+
564
+ function onAnimationChange(event) {
565
+ const newClipName = event.target.value;
566
+
567
+ selectElement.value = newClipName;
568
+ desktopPanel.querySelector('#animation-select').value = newClipName;
569
+
570
+ const newAction = actions[newClipName];
571
+
572
+ if (newAction && newAction !== activeAction) {
573
+ const previousAction = activeAction;
574
+ activeAction = newAction;
575
+
576
+ updateAnimationLoop(freezeToggle);
577
+
578
+ previousAction.fadeOut(0.5);
579
+
580
+ activeAction
581
+ .reset()
582
+ .setEffectiveTimeScale(1)
583
+ .setEffectiveWeight(1)
584
+ .fadeIn(0.5)
585
+ .play();
586
+ }
587
+ toggleMobileMenu(false);
588
+ }
589
+
590
+ function animate() {
591
+ requestAnimationFrame(animate);
592
+
593
+ const delta = clock.getDelta();
594
+
595
+ if (mixer) {
596
+ if (!freezeLastFrame || (activeAction && !activeAction.paused)) {
597
+ mixer.update(delta);
598
+ }
599
+ }
600
+
601
+ controls.update();
602
+ renderer.render(scene, camera);
603
+ }
604
+
605
+ function onWindowResize() {
606
+ const width = container.clientWidth;
607
+ const height = container.clientHeight;
608
+
609
+ camera.aspect = width / height;
610
+ camera.updateProjectionMatrix();
611
+
612
+ renderer.setSize(width, height);
613
+ }
614
+
615
+ window.onload = function () {
616
+ try {
617
+ initScene();
618
+ createCharacterSelectors();
619
+ loadCharacter(currentCharacterId);
620
+ animate();
621
+ } catch (e) {
622
+ console.error("Initialization failed:", e);
623
+ loadingIndicator.innerHTML = 'Critical Error: Check console for details.';
624
+ }
625
+ };
626
+ </script>
627
+ </body>
628
+ </html>
model18.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:86b8bc75204683fcb21a429210de03c50f49add2629fb4fa4a54f4712aaab579
3
+ size 18363180