Sal-ONE commited on
Commit
85fa8b8
·
verified ·
1 Parent(s): 8273997

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1393 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: 3d Model Viewer
3
- emoji: 🐠
4
- colorFrom: green
5
- colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: 3d-model-viewer
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: red
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1393 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 Viewer</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/OBJLoader.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/DragControls.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/TrackballControls.js"></script>
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
15
+ <style>
16
+ .model-container {
17
+ position: relative;
18
+ width: 100%;
19
+ height: 70vh;
20
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
21
+ border-radius: 10px;
22
+ overflow: hidden;
23
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
24
+ }
25
+
26
+ .loading-overlay {
27
+ position: absolute;
28
+ top: 0;
29
+ left: 0;
30
+ width: 100%;
31
+ height: 100%;
32
+ background: rgba(0, 0, 0, 0.7);
33
+ display: flex;
34
+ justify-content: center;
35
+ align-items: center;
36
+ z-index: 100;
37
+ color: white;
38
+ font-size: 1.5rem;
39
+ flex-direction: column;
40
+ }
41
+
42
+ .spinner {
43
+ border: 5px solid rgba(255, 255, 255, 0.3);
44
+ border-radius: 50%;
45
+ border-top: 5px solid #4f46e5;
46
+ width: 50px;
47
+ height: 50px;
48
+ animation: spin 1s linear infinite;
49
+ margin-bottom: 20px;
50
+ }
51
+
52
+ @keyframes spin {
53
+ 0% { transform: rotate(0deg); }
54
+ 100% { transform: rotate(360deg); }
55
+ }
56
+
57
+ .control-panel {
58
+ position: absolute;
59
+ top: 20px;
60
+ right: 20px;
61
+ background: rgba(30, 30, 60, 0.8);
62
+ backdrop-filter: blur(10px);
63
+ border-radius: 8px;
64
+ padding: 15px;
65
+ z-index: 10;
66
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
67
+ max-height: 80vh;
68
+ overflow-y: auto;
69
+ }
70
+
71
+ .control-panel::-webkit-scrollbar {
72
+ width: 6px;
73
+ }
74
+
75
+ .control-panel::-webkit-scrollbar-thumb {
76
+ background: #4f46e5;
77
+ border-radius: 3px;
78
+ }
79
+
80
+ .control-panel h3 {
81
+ color: white;
82
+ margin-bottom: 15px;
83
+ font-weight: 600;
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 8px;
87
+ }
88
+
89
+ .control-section {
90
+ margin-bottom: 20px;
91
+ }
92
+
93
+ .control-section:last-child {
94
+ margin-bottom: 0;
95
+ }
96
+
97
+ .control-label {
98
+ color: #d1d5db;
99
+ margin-bottom: 8px;
100
+ display: block;
101
+ font-size: 0.9rem;
102
+ }
103
+
104
+ .slider-container {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 10px;
108
+ }
109
+
110
+ .slider {
111
+ flex-grow: 1;
112
+ -webkit-appearance: none;
113
+ height: 6px;
114
+ background: #4b5563;
115
+ border-radius: 3px;
116
+ outline: none;
117
+ }
118
+
119
+ .slider::-webkit-slider-thumb {
120
+ -webkit-appearance: none;
121
+ width: 16px;
122
+ height: 16px;
123
+ border-radius: 50%;
124
+ background: #4f46e5;
125
+ cursor: pointer;
126
+ }
127
+
128
+ .slider-value {
129
+ color: white;
130
+ width: 40px;
131
+ text-align: center;
132
+ font-size: 0.85rem;
133
+ }
134
+
135
+ .toggle-switch {
136
+ position: relative;
137
+ display: inline-block;
138
+ width: 50px;
139
+ height: 24px;
140
+ }
141
+
142
+ .toggle-switch input {
143
+ opacity: 0;
144
+ width: 0;
145
+ height: 0;
146
+ }
147
+
148
+ .toggle-slider {
149
+ position: absolute;
150
+ cursor: pointer;
151
+ top: 0;
152
+ left: 0;
153
+ right: 0;
154
+ bottom: 0;
155
+ background-color: #4b5563;
156
+ transition: .4s;
157
+ border-radius: 24px;
158
+ }
159
+
160
+ .toggle-slider:before {
161
+ position: absolute;
162
+ content: "";
163
+ height: 16px;
164
+ width: 16px;
165
+ left: 4px;
166
+ bottom: 4px;
167
+ background-color: white;
168
+ transition: .4s;
169
+ border-radius: 50%;
170
+ }
171
+
172
+ input:checked + .toggle-slider {
173
+ background-color: #4f46e5;
174
+ }
175
+
176
+ input:checked + .toggle-slider:before {
177
+ transform: translateX(26px);
178
+ }
179
+
180
+ .btn {
181
+ background: #4f46e5;
182
+ color: white;
183
+ border: none;
184
+ padding: 8px 15px;
185
+ border-radius: 6px;
186
+ cursor: pointer;
187
+ font-size: 0.9rem;
188
+ transition: all 0.3s ease;
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 8px;
192
+ }
193
+
194
+ .btn:hover {
195
+ background: #4338ca;
196
+ transform: translateY(-1px);
197
+ }
198
+
199
+ .btn-secondary {
200
+ background: #374151;
201
+ }
202
+
203
+ .btn-secondary:hover {
204
+ background: #4b5563;
205
+ }
206
+
207
+ .btn-group {
208
+ display: flex;
209
+ gap: 10px;
210
+ margin-top: 10px;
211
+ }
212
+
213
+ .file-input {
214
+ display: none;
215
+ }
216
+
217
+ .file-label {
218
+ display: block;
219
+ background: #4f46e5;
220
+ color: white;
221
+ padding: 10px 15px;
222
+ border-radius: 6px;
223
+ cursor: pointer;
224
+ text-align: center;
225
+ transition: all 0.3s ease;
226
+ margin-bottom: 15px;
227
+ }
228
+
229
+ .file-label:hover {
230
+ background: #4338ca;
231
+ }
232
+
233
+ .model-info {
234
+ position: absolute;
235
+ bottom: 20px;
236
+ left: 20px;
237
+ background: rgba(30, 30, 60, 0.8);
238
+ backdrop-filter: blur(10px);
239
+ border-radius: 8px;
240
+ padding: 12px 15px;
241
+ color: white;
242
+ font-size: 0.85rem;
243
+ z-index: 10;
244
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
245
+ }
246
+
247
+ .model-info h4 {
248
+ margin-bottom: 5px;
249
+ font-weight: 600;
250
+ color: #d1d5db;
251
+ }
252
+
253
+ .model-info p {
254
+ margin: 3px 0;
255
+ }
256
+
257
+ .controls-hint {
258
+ position: absolute;
259
+ bottom: 20px;
260
+ right: 20px;
261
+ background: rgba(30, 30, 60, 0.8);
262
+ backdrop-filter: blur(10px);
263
+ border-radius: 8px;
264
+ padding: 12px 15px;
265
+ color: white;
266
+ font-size: 0.85rem;
267
+ z-index: 10;
268
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
269
+ }
270
+
271
+ .controls-hint h4 {
272
+ margin-bottom: 8px;
273
+ font-weight: 600;
274
+ color: #d1d5db;
275
+ }
276
+
277
+ .controls-hint ul {
278
+ list-style-type: none;
279
+ padding: 0;
280
+ margin: 0;
281
+ }
282
+
283
+ .controls-hint li {
284
+ margin-bottom: 5px;
285
+ display: flex;
286
+ align-items: center;
287
+ gap: 8px;
288
+ }
289
+
290
+ .controls-hint li:last-child {
291
+ margin-bottom: 0;
292
+ }
293
+
294
+ .tab-container {
295
+ display: flex;
296
+ border-bottom: 1px solid #374151;
297
+ margin-bottom: 15px;
298
+ }
299
+
300
+ .tab {
301
+ padding: 8px 15px;
302
+ cursor: pointer;
303
+ color: #9ca3af;
304
+ font-size: 0.9rem;
305
+ border-bottom: 2px solid transparent;
306
+ transition: all 0.3s ease;
307
+ }
308
+
309
+ .tab.active {
310
+ color: white;
311
+ border-bottom: 2px solid #4f46e5;
312
+ }
313
+
314
+ .tab-content {
315
+ display: none;
316
+ }
317
+
318
+ .tab-content.active {
319
+ display: block;
320
+ }
321
+
322
+ .preset-btn {
323
+ background: #374151;
324
+ color: white;
325
+ border: none;
326
+ padding: 6px 12px;
327
+ border-radius: 6px;
328
+ cursor: pointer;
329
+ font-size: 0.8rem;
330
+ transition: all 0.3s ease;
331
+ margin-right: 8px;
332
+ margin-bottom: 8px;
333
+ }
334
+
335
+ .preset-btn:hover {
336
+ background: #4b5563;
337
+ }
338
+
339
+ .preset-btn.active {
340
+ background: #4f46e5;
341
+ }
342
+
343
+ .color-picker {
344
+ width: 30px;
345
+ height: 30px;
346
+ border-radius: 50%;
347
+ border: 2px solid #4b5563;
348
+ cursor: pointer;
349
+ transition: all 0.3s ease;
350
+ }
351
+
352
+ .color-picker:hover {
353
+ transform: scale(1.1);
354
+ }
355
+
356
+ .color-picker.active {
357
+ border-color: white;
358
+ box-shadow: 0 0 0 2px #4f46e5;
359
+ }
360
+
361
+ .color-palette {
362
+ display: flex;
363
+ gap: 10px;
364
+ margin-top: 10px;
365
+ }
366
+
367
+ .dropdown {
368
+ position: relative;
369
+ display: inline-block;
370
+ width: 100%;
371
+ }
372
+
373
+ .dropdown-btn {
374
+ background: #374151;
375
+ color: white;
376
+ border: none;
377
+ padding: 8px 15px;
378
+ border-radius: 6px;
379
+ cursor: pointer;
380
+ font-size: 0.9rem;
381
+ width: 100%;
382
+ text-align: left;
383
+ display: flex;
384
+ justify-content: space-between;
385
+ align-items: center;
386
+ }
387
+
388
+ .dropdown-content {
389
+ display: none;
390
+ position: absolute;
391
+ background: #1f2937;
392
+ min-width: 100%;
393
+ box-shadow: 0 8px 16px rgba(0,0,0,0.2);
394
+ z-index: 1;
395
+ border-radius: 6px;
396
+ overflow: hidden;
397
+ margin-top: 5px;
398
+ }
399
+
400
+ .dropdown-content a {
401
+ color: white;
402
+ padding: 10px 15px;
403
+ text-decoration: none;
404
+ display: block;
405
+ font-size: 0.85rem;
406
+ transition: background 0.3s;
407
+ }
408
+
409
+ .dropdown-content a:hover {
410
+ background: #374151;
411
+ }
412
+
413
+ .dropdown:hover .dropdown-content {
414
+ display: block;
415
+ }
416
+
417
+ .checkbox-container {
418
+ display: flex;
419
+ align-items: center;
420
+ margin-bottom: 10px;
421
+ }
422
+
423
+ .checkbox-container input {
424
+ margin-right: 10px;
425
+ }
426
+
427
+ .checkbox-label {
428
+ color: #d1d5db;
429
+ font-size: 0.9rem;
430
+ }
431
+ </style>
432
+ </head>
433
+ <body class="bg-gray-900 text-white min-h-screen">
434
+ <div class="container mx-auto px-4 py-8">
435
+ <header class="mb-8">
436
+ <h1 class="text-3xl font-bold text-center mb-2 bg-gradient-to-r from-purple-500 to-blue-500 bg-clip-text text-transparent">3D Model Viewer</h1>
437
+ <p class="text-center text-gray-400 max-w-2xl mx-auto">Upload and interact with your 3D models. Supports OBJ, GLTF, and more formats with advanced rendering controls.</p>
438
+ </header>
439
+
440
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
441
+ <div class="lg:col-span-2">
442
+ <div class="model-container" id="model-container">
443
+ <div class="loading-overlay" id="loading-overlay">
444
+ <div class="spinner"></div>
445
+ <p>Loading model...</p>
446
+ </div>
447
+
448
+ <div class="model-info" id="model-info">
449
+ <h4>Model Information</h4>
450
+ <p id="model-name">No model loaded</p>
451
+ <p id="model-vertices">Vertices: 0</p>
452
+ <p id="model-faces">Faces: 0</p>
453
+ </div>
454
+
455
+ <div class="controls-hint">
456
+ <h4>Controls</h4>
457
+ <ul>
458
+ <li><i class="fas fa-arrows-alt"></i> Left click + drag: Rotate</li>
459
+ <li><i class="fas fa-hand-paper"></i> Right click + drag: Pan</li>
460
+ <li><i class="fas fa-search-plus"></i> Scroll: Zoom</li>
461
+ </ul>
462
+ </div>
463
+ </div>
464
+
465
+ <div class="mt-4 flex flex-wrap gap-3">
466
+ <label class="file-label">
467
+ <i class="fas fa-upload mr-2"></i> Upload Model
468
+ <input type="file" id="file-input" class="file-input" accept=".obj,.gltf,.glb,.fbx,.dae,.stl,.ply" multiple>
469
+ </label>
470
+
471
+ <button id="reset-view" class="btn btn-secondary">
472
+ <i class="fas fa-sync-alt mr-2"></i> Reset View
473
+ </button>
474
+
475
+ <button id="screenshot" class="btn btn-secondary">
476
+ <i class="fas fa-camera mr-2"></i> Screenshot
477
+ </button>
478
+
479
+ <button id="fullscreen" class="btn btn-secondary">
480
+ <i class="fas fa-expand mr-2"></i> Fullscreen
481
+ </button>
482
+ </div>
483
+ </div>
484
+
485
+ <div class="control-panel">
486
+ <div class="tab-container">
487
+ <div class="tab active" data-tab="display">Display</div>
488
+ <div class="tab" data-tab="lighting">Lighting</div>
489
+ <div class="tab" data-tab="materials">Materials</div>
490
+ </div>
491
+
492
+ <div class="tab-content active" id="display-tab">
493
+ <div class="control-section">
494
+ <h3><i class="fas fa-eye"></i> Rendering Mode</h3>
495
+ <div class="btn-group">
496
+ <button class="preset-btn active" data-mode="solid">Solid</button>
497
+ <button class="preset-btn" data-mode="wireframe">Wireframe</button>
498
+ <button class="preset-btn" data-mode="points">Points</button>
499
+ </div>
500
+ </div>
501
+
502
+ <div class="control-section">
503
+ <h3><i class="fas fa-palette"></i> Background</h3>
504
+ <div class="color-palette">
505
+ <div class="color-picker bg-gray-900 active" data-color="#111827"></div>
506
+ <div class="color-picker bg-gray-800" data-color="#1f2937"></div>
507
+ <div class="color-picker bg-gray-700" data-color="#374151"></div>
508
+ <div class="color-picker bg-black" data-color="#000000"></div>
509
+ <div class="color-picker bg-white" data-color="#ffffff"></div>
510
+ </div>
511
+ </div>
512
+
513
+ <div class="control-section">
514
+ <label class="control-label">Model Opacity</label>
515
+ <div class="slider-container">
516
+ <input type="range" min="0" max="100" value="100" class="slider" id="opacity-slider">
517
+ <span class="slider-value" id="opacity-value">100%</span>
518
+ </div>
519
+ </div>
520
+
521
+ <div class="control-section">
522
+ <label class="control-label">Model Scale</label>
523
+ <div class="slider-container">
524
+ <input type="range" min="10" max="500" value="100" class="slider" id="scale-slider">
525
+ <span class="slider-value" id="scale-value">100%</span>
526
+ </div>
527
+ </div>
528
+
529
+ <div class="control-section">
530
+ <div class="checkbox-container">
531
+ <input type="checkbox" id="grid-toggle" checked>
532
+ <label class="checkbox-label">Show Grid</label>
533
+ </div>
534
+ <div class="checkbox-container">
535
+ <input type="checkbox" id="axes-toggle" checked>
536
+ <label class="checkbox-label">Show Axes</label>
537
+ </div>
538
+ <div class="checkbox-container">
539
+ <input type="checkbox" id="bounding-box-toggle">
540
+ <label class="checkbox-label">Show Bounding Box</label>
541
+ </div>
542
+ </div>
543
+
544
+ <div class="control-section">
545
+ <h3><i class="fas fa-sliders-h"></i> Quality Presets</h3>
546
+ <div>
547
+ <button class="preset-btn active" data-quality="high">High</button>
548
+ <button class="preset-btn" data-quality="medium">Medium</button>
549
+ <button class="preset-btn" data-quality="low">Low</button>
550
+ </div>
551
+ </div>
552
+ </div>
553
+
554
+ <div class="tab-content" id="lighting-tab">
555
+ <div class="control-section">
556
+ <h3><i class="fas fa-lightbulb"></i> Lighting</h3>
557
+ <div class="checkbox-container">
558
+ <input type="checkbox" id="ambient-light-toggle" checked>
559
+ <label class="checkbox-label">Ambient Light</label>
560
+ </div>
561
+ <div class="checkbox-container">
562
+ <input type="checkbox" id="directional-light-toggle" checked>
563
+ <label class="checkbox-label">Directional Light</label>
564
+ </div>
565
+ <div class="checkbox-container">
566
+ <input type="checkbox" id="hemisphere-light-toggle">
567
+ <label class="checkbox-label">Hemisphere Light</label>
568
+ </div>
569
+ </div>
570
+
571
+ <div class="control-section">
572
+ <label class="control-label">Light Intensity</label>
573
+ <div class="slider-container">
574
+ <input type="range" min="0" max="200" value="100" class="slider" id="light-intensity-slider">
575
+ <span class="slider-value" id="light-intensity-value">100%</span>
576
+ </div>
577
+ </div>
578
+
579
+ <div class="control-section">
580
+ <label class="control-label">Light Color</label>
581
+ <input type="color" id="light-color-picker" value="#ffffff" class="w-full h-10">
582
+ </div>
583
+
584
+ <div class="control-section">
585
+ <label class="control-label">Light Position X</label>
586
+ <div class="slider-container">
587
+ <input type="range" min="-10" max="10" value="1" step="0.1" class="slider" id="light-x-slider">
588
+ <span class="slider-value" id="light-x-value">1.0</span>
589
+ </div>
590
+ </div>
591
+
592
+ <div class="control-section">
593
+ <label class="control-label">Light Position Y</label>
594
+ <div class="slider-container">
595
+ <input type="range" min="-10" max="10" value="1" step="0.1" class="slider" id="light-y-slider">
596
+ <span class="slider-value" id="light-y-value">1.0</span>
597
+ </div>
598
+ </div>
599
+
600
+ <div class="control-section">
601
+ <label class="control-label">Light Position Z</label>
602
+ <div class="slider-container">
603
+ <input type="range" min="-10" max="10" value="1" step="0.1" class="slider" id="light-z-slider">
604
+ <span class="slider-value" id="light-z-value">1.0</span>
605
+ </div>
606
+ </div>
607
+
608
+ <div class="control-section">
609
+ <h3><i class="fas fa-moon"></i> Shadows</h3>
610
+ <div class="checkbox-container">
611
+ <input type="checkbox" id="shadow-toggle">
612
+ <label class="checkbox-label">Enable Shadows</label>
613
+ </div>
614
+ </div>
615
+ </div>
616
+
617
+ <div class="tab-content" id="materials-tab">
618
+ <div class="control-section">
619
+ <h3><i class="fas fa-paint-brush"></i> Material Type</h3>
620
+ <div class="dropdown">
621
+ <button class="dropdown-btn">
622
+ <span id="current-material">Standard</span>
623
+ <i class="fas fa-chevron-down"></i>
624
+ </button>
625
+ <div class="dropdown-content">
626
+ <a href="#" data-material="standard">Standard</a>
627
+ <a href="#" data-material="phong">Phong</a>
628
+ <a href="#" data-material="lambert">Lambert</a>
629
+ <a href="#" data-material="basic">Basic</a>
630
+ <a href="#" data-material="physical">Physical</a>
631
+ </div>
632
+ </div>
633
+ </div>
634
+
635
+ <div class="control-section">
636
+ <label class="control-label">Material Color</label>
637
+ <input type="color" id="material-color-picker" value="#888888" class="w-full h-10">
638
+ </div>
639
+
640
+ <div class="control-section">
641
+ <label class="control-label">Roughness</label>
642
+ <div class="slider-container">
643
+ <input type="range" min="0" max="100" value="50" class="slider" id="roughness-slider">
644
+ <span class="slider-value" id="roughness-value">50%</span>
645
+ </div>
646
+ </div>
647
+
648
+ <div class="control-section">
649
+ <label class="control-label">Metalness</label>
650
+ <div class="slider-container">
651
+ <input type="range" min="0" max="100" value="0" class="slider" id="metalness-slider">
652
+ <span class="slider-value" id="metalness-value">0%</span>
653
+ </div>
654
+ </div>
655
+
656
+ <div class="control-section">
657
+ <label class="control-label">Emissive Intensity</label>
658
+ <div class="slider-container">
659
+ <input type="range" min="0" max="100" value="0" class="slider" id="emissive-slider">
660
+ <span class="slider-value" id="emissive-value">0%</span>
661
+ </div>
662
+ </div>
663
+
664
+ <div class="control-section">
665
+ <h3><i class="fas fa-texture"></i> Textures</h3>
666
+ <label for="texture-upload" class="file-label">
667
+ <i class="fas fa-image mr-2"></i> Upload Texture
668
+ <input type="file" id="texture-upload" class="file-input" accept="image/*">
669
+ </label>
670
+ </div>
671
+ </div>
672
+ </div>
673
+ </div>
674
+ </div>
675
+
676
+ <script>
677
+ // Initialize Three.js scene
678
+ let scene, camera, renderer, controls, model, gridHelper, axesHelper, boundingBox;
679
+ let ambientLight, directionalLight, hemisphereLight;
680
+ let isModelLoaded = false;
681
+ let currentFileType = '';
682
+
683
+ // Initialize the 3D viewer
684
+ function init() {
685
+ // Get container dimensions
686
+ const container = document.getElementById('model-container');
687
+ const width = container.clientWidth;
688
+ const height = container.clientHeight;
689
+
690
+ // Create scene
691
+ scene = new THREE.Scene();
692
+ scene.background = new THREE.Color(0x111827);
693
+
694
+ // Create camera
695
+ camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
696
+ camera.position.set(5, 5, 5);
697
+
698
+ // Create renderer
699
+ renderer = new THREE.WebGLRenderer({ antialias: true });
700
+ renderer.setSize(width, height);
701
+ renderer.setPixelRatio(window.devicePixelRatio);
702
+ renderer.shadowMap.enabled = true;
703
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
704
+ container.appendChild(renderer.domElement);
705
+
706
+ // Add controls
707
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
708
+ controls.enableDamping = true;
709
+ controls.dampingFactor = 0.05;
710
+ controls.screenSpacePanning = false;
711
+ controls.minDistance = 1;
712
+ controls.maxDistance = 100;
713
+
714
+ // Add lights
715
+ setupLights();
716
+
717
+ // Add helpers
718
+ setupHelpers();
719
+
720
+ // Event listeners
721
+ setupEventListeners();
722
+
723
+ // Start animation loop
724
+ animate();
725
+ }
726
+
727
+ function setupLights() {
728
+ // Ambient light
729
+ ambientLight = new THREE.AmbientLight(0x404040, 0.5);
730
+ scene.add(ambientLight);
731
+
732
+ // Directional light
733
+ directionalLight = new THREE.DirectionalLight(0xffffff, 1);
734
+ directionalLight.position.set(1, 1, 1);
735
+ directionalLight.castShadow = true;
736
+ directionalLight.shadow.mapSize.width = 1024;
737
+ directionalLight.shadow.mapSize.height = 1024;
738
+ scene.add(directionalLight);
739
+
740
+ // Hemisphere light
741
+ hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 0.5);
742
+ hemisphereLight.visible = false;
743
+ scene.add(hemisphereLight);
744
+ }
745
+
746
+ function setupHelpers() {
747
+ // Grid helper
748
+ gridHelper = new THREE.GridHelper(10, 10);
749
+ gridHelper.position.y = -0.5;
750
+ scene.add(gridHelper);
751
+
752
+ // Axes helper
753
+ axesHelper = new THREE.AxesHelper(5);
754
+ scene.add(axesHelper);
755
+
756
+ // Bounding box (initially hidden)
757
+ boundingBox = new THREE.Box3Helper(new THREE.Box3(), 0xffff00);
758
+ boundingBox.visible = false;
759
+ scene.add(boundingBox);
760
+ }
761
+
762
+ function setupEventListeners() {
763
+ // File input
764
+ const fileInput = document.getElementById('file-input');
765
+ fileInput.addEventListener('change', handleFileUpload);
766
+
767
+ // Texture upload
768
+ const textureUpload = document.getElementById('texture-upload');
769
+ textureUpload.addEventListener('change', handleTextureUpload);
770
+
771
+ // Reset view
772
+ document.getElementById('reset-view').addEventListener('click', resetView);
773
+
774
+ // Screenshot
775
+ document.getElementById('screenshot').addEventListener('click', takeScreenshot);
776
+
777
+ // Fullscreen
778
+ document.getElementById('fullscreen').addEventListener('click', toggleFullscreen);
779
+
780
+ // Tabs
781
+ document.querySelectorAll('.tab').forEach(tab => {
782
+ tab.addEventListener('click', () => {
783
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
784
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
785
+
786
+ tab.classList.add('active');
787
+ document.getElementById(`${tab.dataset.tab}-tab`).classList.add('active');
788
+ });
789
+ });
790
+
791
+ // Rendering mode buttons
792
+ document.querySelectorAll('[data-mode]').forEach(btn => {
793
+ btn.addEventListener('click', () => {
794
+ document.querySelectorAll('[data-mode]').forEach(b => b.classList.remove('active'));
795
+ btn.classList.add('active');
796
+ changeRenderingMode(btn.dataset.mode);
797
+ });
798
+ });
799
+
800
+ // Background color pickers
801
+ document.querySelectorAll('.color-picker').forEach(picker => {
802
+ picker.addEventListener('click', () => {
803
+ document.querySelectorAll('.color-picker').forEach(p => p.classList.remove('active'));
804
+ picker.classList.add('active');
805
+ scene.background = new THREE.Color(picker.dataset.color);
806
+ });
807
+ });
808
+
809
+ // Quality presets
810
+ document.querySelectorAll('[data-quality]').forEach(btn => {
811
+ btn.addEventListener('click', () => {
812
+ document.querySelectorAll('[data-quality]').forEach(b => b.classList.remove('active'));
813
+ btn.classList.add('active');
814
+ applyQualityPreset(btn.dataset.quality);
815
+ });
816
+ });
817
+
818
+ // Material dropdown
819
+ document.querySelectorAll('.dropdown-content a').forEach(item => {
820
+ item.addEventListener('click', (e) => {
821
+ e.preventDefault();
822
+ document.getElementById('current-material').textContent = item.textContent;
823
+ changeMaterialType(item.dataset.material);
824
+ });
825
+ });
826
+
827
+ // Sliders
828
+ document.getElementById('opacity-slider').addEventListener('input', updateOpacity);
829
+ document.getElementById('scale-slider').addEventListener('input', updateScale);
830
+ document.getElementById('light-intensity-slider').addEventListener('input', updateLightIntensity);
831
+ document.getElementById('light-x-slider').addEventListener('input', updateLightPosition);
832
+ document.getElementById('light-y-slider').addEventListener('input', updateLightPosition);
833
+ document.getElementById('light-z-slider').addEventListener('input', updateLightPosition);
834
+ document.getElementById('roughness-slider').addEventListener('input', updateMaterialProperties);
835
+ document.getElementById('metalness-slider').addEventListener('input', updateMaterialProperties);
836
+ document.getElementById('emissive-slider').addEventListener('input', updateMaterialProperties);
837
+
838
+ // Color pickers
839
+ document.getElementById('light-color-picker').addEventListener('input', updateLightColor);
840
+ document.getElementById('material-color-picker').addEventListener('input', updateMaterialColor);
841
+
842
+ // Toggles
843
+ document.getElementById('grid-toggle').addEventListener('change', toggleGrid);
844
+ document.getElementById('axes-toggle').addEventListener('change', toggleAxes);
845
+ document.getElementById('bounding-box-toggle').addEventListener('change', toggleBoundingBox);
846
+ document.getElementById('ambient-light-toggle').addEventListener('change', toggleAmbientLight);
847
+ document.getElementById('directional-light-toggle').addEventListener('change', toggleDirectionalLight);
848
+ document.getElementById('hemisphere-light-toggle').addEventListener('change', toggleHemisphereLight);
849
+ document.getElementById('shadow-toggle').addEventListener('change', toggleShadows);
850
+
851
+ // Window resize
852
+ window.addEventListener('resize', onWindowResize);
853
+ }
854
+
855
+ function handleFileUpload(event) {
856
+ const files = event.target.files;
857
+ if (files.length === 0) return;
858
+
859
+ const file = files[0];
860
+ const fileName = file.name;
861
+ const fileExtension = fileName.split('.').pop().toLowerCase();
862
+
863
+ // Show loading overlay
864
+ const loadingOverlay = document.getElementById('loading-overlay');
865
+ loadingOverlay.style.display = 'flex';
866
+
867
+ // Remove previous model if exists
868
+ if (model) {
869
+ scene.remove(model);
870
+ if (boundingBox) {
871
+ boundingBox.box = new THREE.Box3();
872
+ }
873
+ }
874
+
875
+ // Create file reader
876
+ const reader = new FileReader();
877
+
878
+ reader.onload = function(e) {
879
+ const contents = e.target.result;
880
+
881
+ try {
882
+ // Load based on file type
883
+ switch(fileExtension) {
884
+ case 'obj':
885
+ loadOBJModel(contents, fileName);
886
+ currentFileType = 'obj';
887
+ break;
888
+ case 'gltf':
889
+ case 'glb':
890
+ loadGLTFModel(contents, fileName);
891
+ currentFileType = 'gltf';
892
+ break;
893
+ default:
894
+ alert('Unsupported file format. Please upload an OBJ or GLTF file.');
895
+ loadingOverlay.style.display = 'none';
896
+ return;
897
+ }
898
+ } catch (error) {
899
+ console.error('Error loading model:', error);
900
+ alert('Error loading model. Please check the console for details.');
901
+ loadingOverlay.style.display = 'none';
902
+ }
903
+ };
904
+
905
+ reader.onerror = function() {
906
+ alert('Error reading file');
907
+ loadingOverlay.style.display = 'none';
908
+ };
909
+
910
+ if (fileExtension === 'glb') {
911
+ reader.readAsArrayBuffer(file);
912
+ } else {
913
+ reader.readAsText(file);
914
+ }
915
+ }
916
+
917
+ function loadOBJModel(objContents, fileName) {
918
+ const loader = new THREE.OBJLoader();
919
+
920
+ try {
921
+ model = loader.parse(objContents);
922
+
923
+ // Center and scale the model
924
+ centerAndScaleModel(model);
925
+
926
+ // Add to scene
927
+ scene.add(model);
928
+
929
+ // Update model info
930
+ updateModelInfo(model, fileName);
931
+
932
+ // Hide loading overlay
933
+ document.getElementById('loading-overlay').style.display = 'none';
934
+ isModelLoaded = true;
935
+
936
+ // Update bounding box
937
+ updateBoundingBox(model);
938
+ } catch (error) {
939
+ console.error('Error parsing OBJ:', error);
940
+ document.getElementById('loading-overlay').style.display = 'none';
941
+ alert('Error parsing OBJ file. Please check the console for details.');
942
+ }
943
+ }
944
+
945
+ function loadGLTFModel(gltfContents, fileName) {
946
+ const loader = new THREE.GLTFLoader();
947
+
948
+ try {
949
+ let data;
950
+
951
+ if (currentFileType === 'glb') {
952
+ data = new Uint8Array(gltfContents);
953
+ } else {
954
+ data = gltfContents;
955
+ }
956
+
957
+ loader.parse(data, '', (gltf) => {
958
+ model = gltf.scene;
959
+
960
+ // Center and scale the model
961
+ centerAndScaleModel(model);
962
+
963
+ // Add to scene
964
+ scene.add(model);
965
+
966
+ // Update model info
967
+ updateModelInfo(model, fileName);
968
+
969
+ // Hide loading overlay
970
+ document.getElementById('loading-overlay').style.display = 'none';
971
+ isModelLoaded = true;
972
+
973
+ // Update bounding box
974
+ updateBoundingBox(model);
975
+ }, (error) => {
976
+ console.error('Error loading GLTF:', error);
977
+ document.getElementById('loading-overlay').style.display = 'none';
978
+ alert('Error loading GLTF file. Please check the console for details.');
979
+ });
980
+ } catch (error) {
981
+ console.error('Error parsing GLTF:', error);
982
+ document.getElementById('loading-overlay').style.display = 'none';
983
+ alert('Error parsing GLTF file. Please check the console for details.');
984
+ }
985
+ }
986
+
987
+ function centerAndScaleModel(model) {
988
+ // Compute bounding box
989
+ const box = new THREE.Box3().setFromObject(model);
990
+ const center = box.getCenter(new THREE.Vector3());
991
+ const size = box.getSize(new THREE.Vector3());
992
+
993
+ // Center the model
994
+ model.position.x += (model.position.x - center.x);
995
+ model.position.y += (model.position.y - center.y);
996
+ model.position.z += (model.position.z - center.z);
997
+
998
+ // Scale to fit in view
999
+ const maxDim = Math.max(size.x, size.y, size.z);
1000
+ const scale = 5 / maxDim;
1001
+ model.scale.set(scale, scale, scale);
1002
+ }
1003
+
1004
+ function updateModelInfo(model, fileName) {
1005
+ let vertices = 0;
1006
+ let faces = 0;
1007
+
1008
+ model.traverse(function(child) {
1009
+ if (child.isMesh) {
1010
+ if (child.geometry) {
1011
+ vertices += child.geometry.attributes.position.count;
1012
+ if (child.geometry.index) {
1013
+ faces += child.geometry.index.count / 3;
1014
+ } else {
1015
+ faces += child.geometry.attributes.position.count / 3;
1016
+ }
1017
+ }
1018
+ }
1019
+ });
1020
+
1021
+ document.getElementById('model-name').textContent = fileName;
1022
+ document.getElementById('model-vertices').textContent = `Vertices: ${vertices.toLocaleString()}`;
1023
+ document.getElementById('model-faces').textContent = `Faces: ${faces.toLocaleString()}`;
1024
+ }
1025
+
1026
+ function updateBoundingBox(model) {
1027
+ if (!model || !boundingBox) return;
1028
+
1029
+ const box = new THREE.Box3().setFromObject(model);
1030
+ boundingBox.box = box;
1031
+
1032
+ if (document.getElementById('bounding-box-toggle').checked) {
1033
+ boundingBox.visible = true;
1034
+ }
1035
+ }
1036
+
1037
+ function handleTextureUpload(event) {
1038
+ if (!isModelLoaded) {
1039
+ alert('Please load a model first');
1040
+ return;
1041
+ }
1042
+
1043
+ const file = event.target.files[0];
1044
+ if (!file) return;
1045
+
1046
+ const reader = new FileReader();
1047
+ reader.onload = function(e) {
1048
+ const texture = new THREE.TextureLoader().load(e.target.result, () => {
1049
+ // Apply texture to all materials in the model
1050
+ model.traverse(function(child) {
1051
+ if (child.isMesh && child.material) {
1052
+ child.material.map = texture;
1053
+ child.material.needsUpdate = true;
1054
+ }
1055
+ });
1056
+ });
1057
+ };
1058
+ reader.readAsDataURL(file);
1059
+ }
1060
+
1061
+ function resetView() {
1062
+ if (!isModelLoaded) return;
1063
+
1064
+ // Reset camera position
1065
+ camera.position.set(5, 5, 5);
1066
+ controls.target.set(0, 0, 0);
1067
+ controls.update();
1068
+ }
1069
+
1070
+ function takeScreenshot() {
1071
+ if (!isModelLoaded) {
1072
+ alert('Please load a model first');
1073
+ return;
1074
+ }
1075
+
1076
+ // Render the scene to a data URL
1077
+ renderer.render(scene, camera);
1078
+ const dataURL = renderer.domElement.toDataURL('image/png');
1079
+
1080
+ // Create a download link
1081
+ const link = document.createElement('a');
1082
+ link.href = dataURL;
1083
+ link.download = '3d-model-screenshot.png';
1084
+ document.body.appendChild(link);
1085
+ link.click();
1086
+ document.body.removeChild(link);
1087
+ }
1088
+
1089
+ function toggleFullscreen() {
1090
+ const container = document.getElementById('model-container');
1091
+
1092
+ if (!document.fullscreenElement) {
1093
+ container.requestFullscreen().catch(err => {
1094
+ alert(`Error attempting to enable fullscreen: ${err.message}`);
1095
+ });
1096
+ } else {
1097
+ document.exitFullscreen();
1098
+ }
1099
+ }
1100
+
1101
+ function changeRenderingMode(mode) {
1102
+ if (!isModelLoaded) return;
1103
+
1104
+ model.traverse(function(child) {
1105
+ if (child.isMesh) {
1106
+ switch(mode) {
1107
+ case 'solid':
1108
+ child.material.wireframe = false;
1109
+ child.material.pointCloud = false;
1110
+ break;
1111
+ case 'wireframe':
1112
+ child.material.wireframe = true;
1113
+ child.material.pointCloud = false;
1114
+ break;
1115
+ case 'points':
1116
+ child.material.wireframe = false;
1117
+ child.material.pointCloud = true;
1118
+ break;
1119
+ }
1120
+ }
1121
+ });
1122
+ }
1123
+
1124
+ function applyQualityPreset(quality) {
1125
+ switch(quality) {
1126
+ case 'high':
1127
+ renderer.setPixelRatio(window.devicePixelRatio);
1128
+ renderer.antialias = true;
1129
+ break;
1130
+ case 'medium':
1131
+ renderer.setPixelRatio(1);
1132
+ renderer.antialias = true;
1133
+ break;
1134
+ case 'low':
1135
+ renderer.setPixelRatio(1);
1136
+ renderer.antialias = false;
1137
+ break;
1138
+ }
1139
+
1140
+ // Force resize to apply changes
1141
+ onWindowResize();
1142
+ }
1143
+
1144
+ function changeMaterialType(type) {
1145
+ if (!isModelLoaded) return;
1146
+
1147
+ model.traverse(function(child) {
1148
+ if (child.isMesh) {
1149
+ const currentMaterial = child.material;
1150
+ let newMaterial;
1151
+
1152
+ // Preserve some properties
1153
+ const color = currentMaterial.color || new THREE.Color(0x888888);
1154
+ const map = currentMaterial.map || null;
1155
+ const opacity = currentMaterial.opacity || 1;
1156
+
1157
+ switch(type) {
1158
+ case 'standard':
1159
+ newMaterial = new THREE.MeshStandardMaterial({
1160
+ color: color,
1161
+ map: map,
1162
+ roughness: 0.5,
1163
+ metalness: 0,
1164
+ opacity: opacity,
1165
+ transparent: opacity < 1
1166
+ });
1167
+ break;
1168
+ case 'phong':
1169
+ newMaterial = new THREE.MeshPhongMaterial({
1170
+ color: color,
1171
+ map: map,
1172
+ shininess: 30,
1173
+ opacity: opacity,
1174
+ transparent: opacity < 1
1175
+ });
1176
+ break;
1177
+ case 'lambert':
1178
+ newMaterial = new THREE.MeshLambertMaterial({
1179
+ color: color,
1180
+ map: map,
1181
+ opacity: opacity,
1182
+ transparent: opacity < 1
1183
+ });
1184
+ break;
1185
+ case 'basic':
1186
+ newMaterial = new THREE.MeshBasicMaterial({
1187
+ color: color,
1188
+ map: map,
1189
+ opacity: opacity,
1190
+ transparent: opacity < 1
1191
+ });
1192
+ break;
1193
+ case 'physical':
1194
+ newMaterial = new THREE.MeshPhysicalMaterial({
1195
+ color: color,
1196
+ map: map,
1197
+ roughness: 0.5,
1198
+ metalness: 0,
1199
+ clearcoat: 1,
1200
+ clearcoatRoughness: 0.1,
1201
+ opacity: opacity,
1202
+ transparent: opacity < 1
1203
+ });
1204
+ break;
1205
+ }
1206
+
1207
+ child.material = newMaterial;
1208
+ }
1209
+ });
1210
+
1211
+ // Update material controls to match new material
1212
+ updateMaterialControls();
1213
+ }
1214
+
1215
+ function updateMaterialControls() {
1216
+ if (!isModelLoaded) return;
1217
+
1218
+ // Get the first material in the model to sync controls
1219
+ let firstMaterial = null;
1220
+ model.traverse(function(child) {
1221
+ if (child.isMesh && !firstMaterial) {
1222
+ firstMaterial = child.material;
1223
+ }
1224
+ });
1225
+
1226
+ if (!firstMaterial) return;
1227
+
1228
+ // Update color picker
1229
+ if (firstMaterial.color) {
1230
+ document.getElementById('material-color-picker').value = `#${firstMaterial.color.getHexString()}`;
1231
+ }
1232
+
1233
+ // Update sliders based on material type
1234
+ if (firstMaterial.isMeshStandardMaterial || firstMaterial.isMeshPhysicalMaterial) {
1235
+ document.getElementById('roughness-slider').value = firstMaterial.roughness * 100;
1236
+ document.getElementById('roughness-value').textContent = `${Math.round(firstMaterial.roughness * 100)}%`;
1237
+
1238
+ document.getElementById('metalness-slider').value = firstMaterial.metalness * 100;
1239
+ document.getElementById('metalness-value').textContent = `${Math.round(firstMaterial.metalness * 100)}%`;
1240
+ }
1241
+
1242
+ if (firstMaterial.emissive) {
1243
+ document.getElementById('emissive-slider').value = firstMaterial.emissiveIntensity * 100;
1244
+ document.getElementById('emissive-value').textContent = `${Math.round(firstMaterial.emissiveIntensity * 100)}%`;
1245
+ }
1246
+ }
1247
+
1248
+ function updateOpacity() {
1249
+ if (!isModelLoaded) return;
1250
+
1251
+ const opacity = document.getElementById('opacity-slider').value / 100;
1252
+ document.getElementById('opacity-value').textContent = `${document.getElementById('opacity-slider').value}%`;
1253
+
1254
+ model.traverse(function(child) {
1255
+ if (child.isMesh) {
1256
+ child.material.opacity = opacity;
1257
+ child.material.transparent = opacity < 1;
1258
+ }
1259
+ });
1260
+ }
1261
+
1262
+ function updateScale() {
1263
+ if (!isModelLoaded) return;
1264
+
1265
+ const scale = document.getElementById('scale-slider').value / 100;
1266
+ document.getElementById('scale-value').textContent = `${document.getElementById('scale-slider').value}%`;
1267
+
1268
+ model.scale.set(scale, scale, scale);
1269
+ updateBoundingBox(model);
1270
+ }
1271
+
1272
+ function updateLightIntensity() {
1273
+ const intensity = document.getElementById('light-intensity-slider').value / 100;
1274
+ document.getElementById('light-intensity-value').textContent = `${document.getElementById('light-intensity-slider').value}%`;
1275
+
1276
+ if (ambientLight) ambientLight.intensity = intensity * 0.5;
1277
+ if (directionalLight) directionalLight.intensity = intensity;
1278
+ if (hemisphereLight) hemisphereLight.intensity = intensity * 0.5;
1279
+ }
1280
+
1281
+ function updateLightPosition() {
1282
+ const x = parseFloat(document.getElementById('light-x-slider').value);
1283
+ const y = parseFloat(document.getElementById('light-y-slider').value);
1284
+ const z = parseFloat(document.getElementById('light-z-slider').value);
1285
+
1286
+ document.getElementById('light-x-value').textContent = x.toFixed(1);
1287
+ document.getElementById('light-y-value').textContent = y.toFixed(1);
1288
+ document.getElementById('light-z-value').textContent = z.toFixed(1);
1289
+
1290
+ if (directionalLight) directionalLight.position.set(x, y, z);
1291
+ }
1292
+
1293
+ function updateLightColor() {
1294
+ const color = new THREE.Color(document.getElementById('light-color-picker').value);
1295
+
1296
+ if (directionalLight) directionalLight.color = color;
1297
+ if (hemisphereLight) hemisphereLight.color = color;
1298
+ }
1299
+
1300
+ function updateMaterialColor() {
1301
+ if (!isModelLoaded) return;
1302
+
1303
+ const color = new THREE.Color(document.getElementById('material-color-picker').value);
1304
+
1305
+ model.traverse(function(child) {
1306
+ if (child.isMesh) {
1307
+ child.material.color = color;
1308
+ }
1309
+ });
1310
+ }
1311
+
1312
+ function updateMaterialProperties() {
1313
+ if (!isModelLoaded) return;
1314
+
1315
+ const roughness = document.getElementById('roughness-slider').value / 100;
1316
+ const metalness = document.getElementById('metalness-slider').value / 100;
1317
+ const emissive = document.getElementById('emissive-slider').value / 100;
1318
+
1319
+ document.getElementById('roughness-value').textContent = `${document.getElementById('roughness-slider').value}%`;
1320
+ document.getElementById('metalness-value').textContent = `${document.getElementById('metalness-slider').value}%`;
1321
+ document.getElementById('emissive-value').textContent = `${document.getElementById('emissive-slider').value}%`;
1322
+
1323
+ model.traverse(function(child) {
1324
+ if (child.isMesh) {
1325
+ if (child.material.isMeshStandardMaterial || child.material.isMeshPhysicalMaterial) {
1326
+ child.material.roughness = roughness;
1327
+ child.material.metalness = metalness;
1328
+ }
1329
+
1330
+ if (child.material.emissive) {
1331
+ child.material.emissiveIntensity = emissive;
1332
+ }
1333
+ }
1334
+ });
1335
+ }
1336
+
1337
+ function toggleGrid() {
1338
+ gridHelper.visible = document.getElementById('grid-toggle').checked;
1339
+ }
1340
+
1341
+ function toggleAxes() {
1342
+ axesHelper.visible = document.getElementById('axes-toggle').checked;
1343
+ }
1344
+
1345
+ function toggleBoundingBox() {
1346
+ boundingBox.visible = document.getElementById('bounding-box-toggle').checked;
1347
+ }
1348
+
1349
+ function toggleAmbientLight() {
1350
+ ambientLight.visible = document.getElementById('ambient-light-toggle').checked;
1351
+ }
1352
+
1353
+ function toggleDirectionalLight() {
1354
+ directionalLight.visible = document.getElementById('directional-light-toggle').checked;
1355
+ }
1356
+
1357
+ function toggleHemisphereLight() {
1358
+ hemisphereLight.visible = document.getElementById('hemisphere-light-toggle').checked;
1359
+ }
1360
+
1361
+ function toggleShadows() {
1362
+ renderer.shadowMap.enabled = document.getElementById('shadow-toggle').checked;
1363
+ if (directionalLight) directionalLight.castShadow = document.getElementById('shadow-toggle').checked;
1364
+
1365
+ model.traverse(function(child) {
1366
+ if (child.isMesh) {
1367
+ child.castShadow = document.getElementById('shadow-toggle').checked;
1368
+ child.receiveShadow = document.getElementById('shadow-toggle').checked;
1369
+ }
1370
+ });
1371
+ }
1372
+
1373
+ function onWindowResize() {
1374
+ const container = document.getElementById('model-container');
1375
+ const width = container.clientWidth;
1376
+ const height = container.clientHeight;
1377
+
1378
+ camera.aspect = width / height;
1379
+ camera.updateProjectionMatrix();
1380
+ renderer.setSize(width, height);
1381
+ }
1382
+
1383
+ function animate() {
1384
+ requestAnimationFrame(animate);
1385
+ controls.update();
1386
+ renderer.render(scene, camera);
1387
+ }
1388
+
1389
+ // Initialize the app when the page loads
1390
+ window.addEventListener('load', init);
1391
+ </script>
1392
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Sal-ONE/3d-model-viewer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1393
+ </html>