ThorAILabs commited on
Commit
0f3c3d7
·
verified ·
1 Parent(s): 32ce8fe

try different model for code generation

Browse files
Files changed (1) hide show
  1. index.html +723 -285
index.html CHANGED
@@ -3,319 +3,757 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>3D Scene Editor with Raytracing & Materials</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/STLLoader.js"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script>
12
- <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/STLExporter.js"></script>
13
- <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/OBJExporter.js"></script>
14
- <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  </head>
16
- <body class="bg-gray-900">
17
- <div class="flex h-screen">
18
- <!-- Sidebar -->
19
- <div class="w-80 bg-gray-800 p-4 space-y-4 overflow-y-auto">
 
 
 
 
 
 
 
 
20
  <div class="space-y-2">
21
- <input type="file" id="file-input" class="hidden" accept=".stl,.obj">
22
- <button onclick="document.getElementById('file-input').click()"
23
- class="w-full bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg">
24
- <i class="fas fa-upload mr-2"></i>Load Model
25
- </button>
26
 
27
- <button onclick="exportSTL()"
28
- class="w-full bg-green-600 hover:bg-green-700 text-white p-2 rounded-lg">
29
- <i class="fas fa-file-export mr-2"></i>Export STL
30
- </button>
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- <button onclick="exportOBJ()"
33
- class="w-full bg-green-600 hover:bg-green-700 text-white p-2 rounded-lg">
34
- <i class="fas fa-file-export mr-2"></i>Export OBJ
35
- </button>
 
 
 
 
 
 
 
 
 
 
36
 
37
- <button onclick="toggleRaytracing()"
38
- id="raytrace-btn"
39
- class="w-full bg-purple-600 hover:bg-purple-700 text-white p-2 rounded-lg">
40
- <i class="fas fa-raygun mr-2"></i>Raytracing: On
41
- </button>
42
- </div>
43
-
44
- <div class="border-t border-gray-700 pt-4">
45
- <h3 class="text-white mb-2">Lights</h3>
46
- <button onclick="addLight('point')"
47
- class="w-full bg-yellow-600 hover:bg-yellow-700 text-white p-2 rounded-lg mb-2">
48
- <i class="fas fa-lightbulb mr-2"></i>Add Point Light
49
- </button>
50
- <button onclick="addLight('directional')"
51
- class="w-full bg-yellow-600 hover:bg-yellow-700 text-white p-2 rounded-lg">
52
- <i class="fas fa-sun mr-2"></i>Add Directional Light
53
- </button>
54
  </div>
55
-
56
- <div class="border-t border-gray-700 pt-4">
57
- <h3 class="text-white mb-2">Materials</h3>
 
 
 
58
 
59
- <div class="mb-4">
60
- <label class="text-gray-300 block mb-2">Material Type</label>
61
- <select id="material-type" class="w-full bg-gray-700 text-white rounded p-2"
62
- onchange="changeMaterialType(this.value)">
63
- <option value="MeshStandardMaterial">Standard</option>
64
- <option value="MeshPhongMaterial">Phong</option>
65
- <option value="MeshBasicMaterial">Basic</option>
66
- <option value="MeshMatcapMaterial">Matcap</option>
67
- </select>
68
- </div>
69
-
70
- <div class="space-y-2">
71
- <input type="file" id="albedo-map" class="hidden" accept="image/*">
72
- <button onclick="document.getElementById('albedo-map').click()"
73
- class="w-full bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-lg">
74
- <i class="fas fa-image mr-2"></i>Albedo Map
75
  </button>
76
-
77
- <input type="file" id="normal-map" class="hidden" accept="image/*">
78
- <button onclick="document.getElementById('normal-map').click()"
79
- class="w-full bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-lg">
80
- <i class="fas fa-wave-square mr-2"></i>Normal Map
81
  </button>
82
-
83
- <input type="file" id="roughness-map" class="hidden" accept="image/*">
84
- <button onclick="document.getElementById('roughness-map').click()"
85
- class="w-full bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-lg">
86
- <i class="fas fa-sun mr-2"></i>Roughness Map
87
  </button>
88
  </div>
 
 
 
 
 
 
89
  </div>
90
-
91
- <div class="border-t border-gray-700 pt-4">
92
- <h3 class="text-white mb-2">Selected Object</h3>
93
- <div id="selected-object-info" class="text-gray-400 text-sm">
94
- Click an object to select
 
 
 
 
95
  </div>
96
  </div>
97
  </div>
98
-
99
- <!-- Main Viewport -->
100
- <div id="viewport" class="flex-1 relative">
101
- <div id="loading-overlay" class="absolute top-0 left-0 w-full h-full bg-gray-900 bg-opacity-50 hidden items-center justify-center">
102
- <div class="text-white text-2xl"><i class="fas fa-spinner fa-spin"></i> Loading...</div>
103
- </div>
104
  </div>
105
  </div>
106
-
107
- <script>
108
- let scene, camera, renderer, raytracingEnabled = true;
109
- let objects = [];
110
- let selectedObject = null;
111
- const textureLoader = new THREE.TextureLoader();
112
- let controls;
113
-
114
- function init() {
115
- // Scene setup
116
- scene = new THREE.Scene();
117
- scene.background = new THREE.Color(0x1a1a1a);
118
 
119
- camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
120
- renderer = new THREE.WebGLRenderer({ antialias: true });
121
- renderer.setSize(window.innerWidth-320, window.innerHeight);
122
- renderer.shadowMap.enabled = true;
123
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;
124
- document.getElementById('viewport').appendChild(renderer.domElement);
125
-
126
- // Lighting
127
- const ambientLight = new THREE.AmbientLight(0x404040);
128
- scene.add(ambientLight);
129
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
130
- directionalLight.position.set(5, 5, 5);
131
- directionalLight.castShadow = true;
132
- scene.add(directionalLight);
133
-
134
- // Camera and controls
135
- camera.position.z = 8;
136
- controls = new THREE.OrbitControls(camera, renderer.domElement);
137
- controls.enableDamping = true;
138
- controls.dampingFactor = 0.05;
139
-
140
- // Event listeners
141
- document.getElementById('file-input').addEventListener('change', handleFileUpload);
142
- renderer.domElement.addEventListener('click', onCanvasClick, false);
143
 
144
- // Texture input handlers
145
- document.getElementById('albedo-map').addEventListener('change', e => loadTexture(e, 'map'));
146
- document.getElementById('normal-map').addEventListener('change', e => loadTexture(e, 'normalMap'));
147
- document.getElementById('roughness-map').addEventListener('change', e => loadTexture(e, 'roughnessMap'));
148
-
149
- animate();
150
- }
151
-
152
- function handleFileUpload(event) {
153
- const file = event.target.files[0];
154
- const reader = new FileReader();
155
 
156
- showLoading(true);
157
- reader.onload = function(e) {
158
- const loader = file.name.endsWith('.stl') ?
159
- new THREE.STLLoader() : new THREE.OBJLoader();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
- const model = loader.parse(e.target.result);
162
- model.scale.set(0.1, 0.1, 0.1);
 
 
 
 
 
 
163
 
164
- model.traverse(child => {
165
- if (child.isMesh) {
166
- child.material = new THREE.MeshStandardMaterial({
167
- color: 0x888888,
168
- metalness: 0.2,
169
- roughness: 0.8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  });
171
- child.castShadow = true;
172
- child.receiveShadow = true;
173
  }
174
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- scene.add(model);
177
- objects.push(model);
178
- showLoading(false);
179
- };
180
-
181
- reader.readAsArrayBuffer(file);
182
- }
183
-
184
- function exportSTL() {
185
- const exporter = new THREE.STLExporter();
186
- const stlString = exporter.parse(scene);
187
- const blob = new Blob([stlString], {type: 'text/plain'});
188
- saveAs(blob, 'scene.stl');
189
- }
190
-
191
- function exportOBJ() {
192
- const exporter = new THREE.OBJExporter();
193
- const objString = exporter.parse(scene);
194
- const blob = new Blob([objString], {type: 'text/plain'});
195
- saveAs(blob, 'scene.obj');
196
- }
197
-
198
- function toggleRaytracing() {
199
- raytracingEnabled = !raytracingEnabled;
200
- document.getElementById('raytrace-btn').innerHTML =
201
- `<i class="fas fa-raygun mr-2"></i>Raytracing: ${raytracingEnabled ? 'On' : 'Off'}`;
202
-
203
- objects.forEach(obj => {
204
- obj.traverse(child => {
205
- if (child.isMesh) {
206
- child.material.wireframe = !raytracingEnabled;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  }
208
- });
209
- });
210
- }
211
-
212
- function addLight(type) {
213
- let light;
214
- if (type === 'point') {
215
- light = new THREE.PointLight(0xffffff, 1, 100);
216
- light.position.set(5, 5, 5);
217
- light.castShadow = true;
218
- } else {
219
- light = new THREE.DirectionalLight(0xffffff, 1);
220
- light.position.set(5, 5, 5);
221
- light.castShadow = true;
222
- }
223
- scene.add(light);
224
- }
225
-
226
- function changeMaterialType(type) {
227
- if (!selectedObject) return;
228
-
229
- const currentMaterial = selectedObject.material;
230
- const newMaterial = new THREE[type]({
231
- color: currentMaterial?.color || 0xffffff,
232
- map: currentMaterial?.map,
233
- normalMap: currentMaterial?.normalMap,
234
- roughnessMap: currentMaterial?.roughnessMap,
235
- metalness: 0.5,
236
- roughness: 0.5
237
- });
238
-
239
- if (currentMaterial) currentMaterial.dispose();
240
- selectedObject.material = newMaterial;
241
- selectedObject.material.needsUpdate = true;
242
- }
243
-
244
- async function loadTexture(event, mapType) {
245
- if (!selectedObject) return;
246
-
247
- const file = event.target.files[0];
248
- if (!file) return;
249
-
250
- showLoading(true);
251
- try {
252
- const texture = await textureLoader.loadAsync(URL.createObjectURL(file));
253
- texture.wrapS = THREE.RepeatWrapping;
254
- texture.wrapT = THREE.RepeatWrapping;
255
- selectedObject.material[mapType] = texture;
256
- selectedObject.material.needsUpdate = true;
257
- } catch (error) {
258
- console.error('Error loading texture:', error);
259
- }
260
- showLoading(false);
261
- }
262
-
263
- function onCanvasClick(event) {
264
- const rect = renderer.domElement.getBoundingClientRect();
265
- const mouse = new THREE.Vector2(
266
- ((event.clientX - rect.left) / rect.width) * 2 - 1,
267
- -((event.clientY - rect.top) / rect.height) * 2 + 1
268
- );
269
-
270
- const raycaster = new THREE.Raycaster();
271
- raycaster.setFromCamera(mouse, camera);
272
-
273
- const intersects = raycaster.intersectObjects(scene.children, true);
274
-
275
- if (intersects.length > 0) {
276
- selectedObject = intersects[0].object;
277
- updateSelectedObjectInfo();
278
- }
279
- }
280
-
281
- function updateSelectedObjectInfo() {
282
- const infoDiv = document.getElementById('selected-object-info');
283
- if (!selectedObject) return;
284
-
285
- infoDiv.innerHTML = `
286
- <div class="truncate">Type: ${selectedObject.type}</div>
287
- <div>Material: ${selectedObject.material.type}</div>
288
- <div>Position: ${selectedObject.position.x.toFixed(2)},
289
- ${selectedObject.position.y.toFixed(2)},
290
- ${selectedObject.position.z.toFixed(2)}</div>
291
- `;
292
- }
293
-
294
- function showLoading(show) {
295
- document.getElementById('loading-overlay').style.display = show ? 'flex' : 'none';
296
- }
297
-
298
- function animate() {
299
- requestAnimationFrame(animate);
300
- controls.update();
301
- renderer.render(scene, camera);
302
- }
303
-
304
- function saveAs(blob, filename) {
305
- const link = document.createElement('a');
306
- link.href = URL.createObjectURL(blob);
307
- link.download = filename;
308
- link.click();
309
- }
310
-
311
- window.addEventListener('resize', () => {
312
- camera.aspect = (window.innerWidth-320) / window.innerHeight;
313
- camera.updateProjectionMatrix();
314
- renderer.setSize(window.innerWidth-320, window.innerHeight);
315
- });
316
-
317
- // Initialize the application
318
- init();
319
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  </body>
321
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Scene Editor with Raytracing</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/STLLoader.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/OBJExporter.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/STLExporter.min.js"></script>
15
+ <style>
16
+ #renderCanvas {
17
+ width: 100%;
18
+ height: 100%;
19
+ display: block;
20
+ }
21
+ .dragover {
22
+ border: 2px dashed #3b82f6 !important;
23
+ background-color: rgba(59, 130, 246, 0.1) !important;
24
+ }
25
+ .sidebar {
26
+ transition: all 0.3s ease;
27
+ }
28
+ .sidebar.collapsed {
29
+ transform: translateX(-100%);
30
+ }
31
+ </style>
32
  </head>
33
+ <body class="bg-gray-900 text-white h-screen flex overflow-hidden">
34
+ <!-- Sidebar -->
35
+ <div id="sidebar" class="sidebar w-64 bg-gray-800 h-full flex flex-col border-r border-gray-700">
36
+ <div class="p-4 border-b border-gray-700 flex justify-between items-center">
37
+ <h1 class="text-xl font-bold">3D Scene Editor</h1>
38
+ <button id="toggleSidebar" class="text-gray-400 hover:text-white">
39
+ <i class="fas fa-chevron-left"></i>
40
+ </button>
41
+ </div>
42
+
43
+ <div class="p-4 space-y-4 overflow-y-auto flex-grow">
44
+ <!-- Scene Controls -->
45
  <div class="space-y-2">
46
+ <h2 class="font-semibold text-blue-400 flex items-center">
47
+ <i class="fas fa-cube mr-2"></i> Scene Controls
48
+ </h2>
 
 
49
 
50
+ <div class="flex space-x-2">
51
+ <button id="toggleRaytracing" class="bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded flex items-center">
52
+ <i class="fas fa-lightbulb mr-1"></i> Toggle Raytracing
53
+ </button>
54
+ <button id="clearScene" class="bg-red-600 hover:bg-red-700 px-3 py-1 rounded flex items-center">
55
+ <i class="fas fa-trash mr-1"></i> Clear
56
+ </button>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- Lighting Controls -->
61
+ <div class="space-y-2">
62
+ <h2 class="font-semibold text-yellow-400 flex items-center">
63
+ <i class="fas fa-lightbulb mr-2"></i> Lighting
64
+ </h2>
65
 
66
+ <div class="grid grid-cols-2 gap-2">
67
+ <button id="addAmbientLight" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
68
+ <i class="fas fa-sun mr-1"></i> Ambient
69
+ </button>
70
+ <button id="addDirectionalLight" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
71
+ <i class="fas fa-sun mr-1"></i> Directional
72
+ </button>
73
+ <button id="addPointLight" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
74
+ <i class="fas fa-lightbulb mr-1"></i> Point
75
+ </button>
76
+ <button id="addSpotLight" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
77
+ <i class="fas fa-lightbulb mr-1"></i> Spot
78
+ </button>
79
+ </div>
80
 
81
+ <div id="lightControls" class="space-y-2 mt-2">
82
+ <!-- Light controls will be added here dynamically -->
83
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  </div>
85
+
86
+ <!-- Import/Export -->
87
+ <div class="space-y-2">
88
+ <h2 class="font-semibold text-green-400 flex items-center">
89
+ <i class="fas fa-file-import mr-2"></i> Import/Export
90
+ </h2>
91
 
92
+ <div class="grid grid-cols-2 gap-2">
93
+ <button id="importSTL" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
94
+ <i class="fas fa-file-upload mr-1"></i> Import STL
95
+ </button>
96
+ <button id="importOBJ" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
97
+ <i class="fas fa-file-upload mr-1"></i> Import OBJ
 
 
 
 
 
 
 
 
 
 
98
  </button>
99
+ <button id="exportSTL" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
100
+ <i class="fas fa-file-download mr-1"></i> Export STL
 
 
 
101
  </button>
102
+ <button id="exportOBJ" class="bg-gray-700 hover:bg-gray-600 px-2 py-1 rounded text-sm flex items-center">
103
+ <i class="fas fa-file-download mr-1"></i> Export OBJ
 
 
 
104
  </button>
105
  </div>
106
+
107
+ <div id="dropZone" class="border-2 border-dashed border-gray-600 rounded p-4 text-center mt-2 cursor-pointer hover:border-blue-500 transition">
108
+ <i class="fas fa-cloud-upload-alt text-3xl mb-2 text-gray-400"></i>
109
+ <p class="text-sm text-gray-400">Drop 3D files here</p>
110
+ <p class="text-xs text-gray-500">(STL, OBJ)</p>
111
+ </div>
112
  </div>
113
+
114
+ <!-- Object List -->
115
+ <div class="space-y-2">
116
+ <h2 class="font-semibold text-purple-400 flex items-center">
117
+ <i class="fas fa-shapes mr-2"></i> Scene Objects
118
+ </h2>
119
+
120
+ <div id="objectList" class="space-y-1 max-h-40 overflow-y-auto">
121
+ <!-- Objects will be listed here -->
122
  </div>
123
  </div>
124
  </div>
125
+
126
+ <div class="p-4 border-t border-gray-700 text-xs text-gray-500">
127
+ <p>3D Scene Editor v1.0</p>
128
+ <p>With Raytracing support</p>
 
 
129
  </div>
130
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ <!-- Main Content -->
133
+ <div class="flex-grow relative">
134
+ <div id="renderCanvas"></div>
135
+
136
+ <!-- Stats Panel -->
137
+ <div id="stats" class="absolute top-2 right-2 bg-gray-800 bg-opacity-70 p-2 rounded text-xs">
138
+ <div>FPS: <span id="fps">0</span></div>
139
+ <div>Objects: <span id="objectCount">0</span></div>
140
+ <div>Vertices: <span id="vertexCount">0</span></div>
141
+ </div>
142
+
143
+ <!-- Loading Indicator -->
144
+ <div id="loadingIndicator" class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
145
+ <div class="bg-gray-800 p-6 rounded-lg shadow-lg text-center">
146
+ <i class="fas fa-spinner fa-spin text-4xl text-blue-500 mb-4"></i>
147
+ <p class="text-xl">Loading...</p>
148
+ </div>
149
+ </div>
150
+ </div>
 
 
 
 
 
151
 
152
+ <!-- File Input (hidden) -->
153
+ <input type="file" id="fileInput" accept=".stl,.obj" class="hidden" multiple>
 
 
 
 
 
 
 
 
 
154
 
155
+ <script>
156
+ // Initialize Three.js scene
157
+ let scene, camera, renderer, controls;
158
+ let raytracingEnabled = false;
159
+ let objects = [];
160
+ let lights = [];
161
+ let clock = new THREE.Clock();
162
+ let stats = {
163
+ fps: 0,
164
+ objectCount: 0,
165
+ vertexCount: 0
166
+ };
167
+
168
+ // Initialize the application
169
+ function init() {
170
+ // Create scene
171
+ scene = new THREE.Scene();
172
+ scene.background = new THREE.Color(0x111111);
173
+
174
+ // Create camera
175
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
176
+ camera.position.set(5, 5, 5);
177
+ camera.lookAt(0, 0, 0);
178
+
179
+ // Create renderer
180
+ renderer = new THREE.WebGLRenderer({ antialias: true });
181
+ renderer.setSize(window.innerWidth, window.innerHeight);
182
+ renderer.shadowMap.enabled = true;
183
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
184
+ document.getElementById('renderCanvas').appendChild(renderer.domElement);
185
+
186
+ // Add orbit controls
187
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
188
+ controls.enableDamping = true;
189
+ controls.dampingFactor = 0.05;
190
+
191
+ // Add axes helper
192
+ const axesHelper = new THREE.AxesHelper(5);
193
+ scene.add(axesHelper);
194
+
195
+ // Add grid helper
196
+ const gridHelper = new THREE.GridHelper(20, 20);
197
+ scene.add(gridHelper);
198
+
199
+ // Add default ambient light
200
+ addAmbientLight();
201
+
202
+ // Add default directional light
203
+ addDirectionalLight();
204
+
205
+ // Event listeners
206
+ setupEventListeners();
207
+
208
+ // Start animation loop
209
+ animate();
210
+ }
211
+
212
+ // Animation loop
213
+ function animate() {
214
+ requestAnimationFrame(animate);
215
+
216
+ // Update controls
217
+ controls.update();
218
+
219
+ // Calculate FPS
220
+ const delta = clock.getDelta();
221
+ stats.fps = Math.round(1 / delta);
222
+
223
+ // Update stats
224
+ updateStats();
225
+
226
+ // Render scene
227
+ renderer.render(scene, camera);
228
+ }
229
+
230
+ // Update statistics display
231
+ function updateStats() {
232
+ document.getElementById('fps').textContent = stats.fps;
233
+ document.getElementById('objectCount').textContent = objects.length;
234
+
235
+ // Calculate total vertices
236
+ let vertexCount = 0;
237
+ objects.forEach(obj => {
238
+ if (obj.geometry) {
239
+ vertexCount += obj.geometry.attributes.position.count;
240
+ }
241
+ });
242
+ document.getElementById('vertexCount').textContent = vertexCount;
243
+ }
244
+
245
+ // Setup event listeners
246
+ function setupEventListeners() {
247
+ // Window resize
248
+ window.addEventListener('resize', () => {
249
+ camera.aspect = window.innerWidth / window.innerHeight;
250
+ camera.updateProjectionMatrix();
251
+ renderer.setSize(window.innerWidth, window.innerHeight);
252
+ });
253
+
254
+ // Sidebar toggle
255
+ document.getElementById('toggleSidebar').addEventListener('click', () => {
256
+ document.getElementById('sidebar').classList.toggle('collapsed');
257
+ document.getElementById('toggleSidebar').innerHTML =
258
+ document.getElementById('sidebar').classList.contains('collapsed') ?
259
+ '<i class="fas fa-chevron-right"></i>' : '<i class="fas fa-chevron-left"></i>';
260
+ });
261
+
262
+ // Raytracing toggle
263
+ document.getElementById('toggleRaytracing').addEventListener('click', () => {
264
+ raytracingEnabled = !raytracingEnabled;
265
+ // In a real implementation, you would switch to a raytracing renderer here
266
+ alert(raytracingEnabled ? 'Raytracing enabled (simulated)' : 'Raytracing disabled');
267
+ });
268
+
269
+ // Clear scene
270
+ document.getElementById('clearScene').addEventListener('click', () => {
271
+ clearScene();
272
+ });
273
+
274
+ // Light buttons
275
+ document.getElementById('addAmbientLight').addEventListener('click', addAmbientLight);
276
+ document.getElementById('addDirectionalLight').addEventListener('click', addDirectionalLight);
277
+ document.getElementById('addPointLight').addEventListener('click', addPointLight);
278
+ document.getElementById('addSpotLight').addEventListener('click', addSpotLight);
279
+
280
+ // Import/Export buttons
281
+ document.getElementById('importSTL').addEventListener('click', () => {
282
+ document.getElementById('fileInput').accept = '.stl';
283
+ document.getElementById('fileInput').click();
284
+ });
285
+
286
+ document.getElementById('importOBJ').addEventListener('click', () => {
287
+ document.getElementById('fileInput').accept = '.obj';
288
+ document.getElementById('fileInput').click();
289
+ });
290
+
291
+ document.getElementById('exportSTL').addEventListener('click', exportSTL);
292
+ document.getElementById('exportOBJ').addEventListener('click', exportOBJ);
293
+
294
+ // File input handling
295
+ document.getElementById('fileInput').addEventListener('change', handleFileSelect);
296
+
297
+ // Drag and drop
298
+ const dropZone = document.getElementById('dropZone');
299
+ dropZone.addEventListener('dragover', (e) => {
300
+ e.preventDefault();
301
+ dropZone.classList.add('dragover');
302
+ });
303
+
304
+ dropZone.addEventListener('dragleave', () => {
305
+ dropZone.classList.remove('dragover');
306
+ });
307
+
308
+ dropZone.addEventListener('drop', (e) => {
309
+ e.preventDefault();
310
+ dropZone.classList.remove('dragover');
311
+
312
+ if (e.dataTransfer.files.length > 0) {
313
+ handleFileSelect({ target: { files: e.dataTransfer.files } });
314
+ }
315
+ });
316
+
317
+ dropZone.addEventListener('click', () => {
318
+ document.getElementById('fileInput').accept = '.stl,.obj';
319
+ document.getElementById('fileInput').click();
320
+ });
321
+ }
322
 
323
+ // Add ambient light
324
+ function addAmbientLight(color = 0x404040, intensity = 0.5) {
325
+ const light = new THREE.AmbientLight(color, intensity);
326
+ scene.add(light);
327
+ lights.push(light);
328
+
329
+ addLightControl(light, 'Ambient Light');
330
+ }
331
 
332
+ // Add directional light
333
+ function addDirectionalLight(color = 0xffffff, intensity = 1, x = 5, y = 5, z = 5) {
334
+ const light = new THREE.DirectionalLight(color, intensity);
335
+ light.position.set(x, y, z);
336
+ light.castShadow = true;
337
+ light.shadow.mapSize.width = 1024;
338
+ light.shadow.mapSize.height = 1024;
339
+ scene.add(light);
340
+ lights.push(light);
341
+
342
+ // Add helper
343
+ const helper = new THREE.DirectionalLightHelper(light, 1);
344
+ scene.add(helper);
345
+
346
+ addLightControl(light, 'Directional Light', helper);
347
+ }
348
+
349
+ // Add point light
350
+ function addPointLight(color = 0xffffff, intensity = 1, distance = 10, x = 0, y = 5, z = 0) {
351
+ const light = new THREE.PointLight(color, intensity, distance);
352
+ light.position.set(x, y, z);
353
+ light.castShadow = true;
354
+ scene.add(light);
355
+ lights.push(light);
356
+
357
+ // Add helper
358
+ const helper = new THREE.PointLightHelper(light, 1);
359
+ scene.add(helper);
360
+
361
+ addLightControl(light, 'Point Light', helper);
362
+ }
363
+
364
+ // Add spot light
365
+ function addSpotLight(color = 0xffffff, intensity = 1, distance = 10, angle = Math.PI / 4, penumbra = 0.1, x = 0, y = 5, z = 0) {
366
+ const light = new THREE.SpotLight(color, intensity, distance, angle, penumbra);
367
+ light.position.set(x, y, z);
368
+ light.castShadow = true;
369
+ scene.add(light);
370
+ lights.push(light);
371
+
372
+ // Add helper
373
+ const helper = new THREE.SpotLightHelper(light);
374
+ scene.add(helper);
375
+
376
+ addLightControl(light, 'Spot Light', helper);
377
+ }
378
+
379
+ // Add light control UI
380
+ function addLightControl(light, name, helper = null) {
381
+ const lightControls = document.getElementById('lightControls');
382
+ const lightId = lights.length - 1;
383
+
384
+ const controlDiv = document.createElement('div');
385
+ controlDiv.className = 'bg-gray-700 p-2 rounded';
386
+ controlDiv.id = `lightControl-${lightId}`;
387
+
388
+ controlDiv.innerHTML = `
389
+ <div class="flex justify-between items-center mb-1">
390
+ <span class="text-sm font-medium">${name}</span>
391
+ <button class="text-red-400 hover:text-red-300 text-xs" data-light-id="${lightId}">
392
+ <i class="fas fa-times"></i>
393
+ </button>
394
+ </div>
395
+ <div class="grid grid-cols-2 gap-1 text-xs">
396
+ <div>
397
+ <label>Intensity</label>
398
+ <input type="range" min="0" max="2" step="0.1" value="${light.intensity}"
399
+ class="w-full" data-light-id="${lightId}" data-property="intensity">
400
+ </div>
401
+ <div>
402
+ <label>Color</label>
403
+ <input type="color" value="#${light.color.getHexString()}"
404
+ class="w-full" data-light-id="${lightId}" data-property="color">
405
+ </div>
406
+ </div>
407
+ `;
408
+
409
+ lightControls.appendChild(controlDiv);
410
+
411
+ // Add event listeners for the controls
412
+ controlDiv.querySelectorAll('input').forEach(input => {
413
+ input.addEventListener('input', (e) => {
414
+ const id = parseInt(e.target.dataset.lightId);
415
+ const property = e.target.dataset.property;
416
+ const value = e.target.value;
417
+
418
+ if (property === 'color') {
419
+ lights[id].color.setHex(parseInt(value.substring(1), 16));
420
+ } else if (property === 'intensity') {
421
+ lights[id].intensity = parseFloat(value);
422
+ }
423
+
424
+ if (helper) helper.update();
425
+ });
426
+ });
427
+
428
+ // Add delete button event
429
+ controlDiv.querySelector('button').addEventListener('click', (e) => {
430
+ const id = parseInt(e.target.dataset.lightId || e.target.closest('button').dataset.lightId);
431
+ scene.remove(lights[id]);
432
+ if (helper) scene.remove(helper);
433
+ lights.splice(id, 1);
434
+ controlDiv.remove();
435
+
436
+ // Update remaining controls
437
+ document.querySelectorAll('[data-light-id]').forEach(el => {
438
+ const currentId = parseInt(el.dataset.lightId);
439
+ if (currentId > id) {
440
+ el.dataset.lightId = currentId - 1;
441
+ }
442
+ });
443
+ });
444
+ }
445
+
446
+ // Clear the scene
447
+ function clearScene() {
448
+ // Remove all objects
449
+ objects.forEach(obj => {
450
+ scene.remove(obj);
451
+ });
452
+ objects = [];
453
+
454
+ // Remove all lights except the first two (ambient and directional)
455
+ while (lights.length > 2) {
456
+ const light = lights.pop();
457
+ scene.remove(light);
458
+
459
+ // Remove helper if exists
460
+ light.children.forEach(child => {
461
+ if (child instanceof THREE.LightHelper) {
462
+ scene.remove(child);
463
+ }
464
  });
 
 
465
  }
466
+
467
+ // Clear light controls except the first two
468
+ const lightControls = document.getElementById('lightControls');
469
+ while (lightControls.children.length > 2) {
470
+ lightControls.removeChild(lightControls.lastChild);
471
+ }
472
+
473
+ // Reset the first two lights to default
474
+ if (lights.length > 0) {
475
+ lights[0].color.setHex(0x404040);
476
+ lights[0].intensity = 0.5;
477
+ }
478
+
479
+ if (lights.length > 1) {
480
+ lights[1].color.setHex(0xffffff);
481
+ lights[1].intensity = 1;
482
+ lights[1].position.set(5, 5, 5);
483
+ }
484
+
485
+ // Update UI controls
486
+ document.querySelectorAll('#lightControls input[data-property="color"]').forEach((input, index) => {
487
+ if (index === 0) input.value = '#404040';
488
+ if (index === 1) input.value = '#ffffff';
489
+ });
490
+
491
+ document.querySelectorAll('#lightControls input[data-property="intensity"]').forEach((input, index) => {
492
+ if (index === 0) input.value = 0.5;
493
+ if (index === 1) input.value = 1;
494
+ });
495
+
496
+ // Update object list
497
+ updateObjectList();
498
+ }
499
 
500
+ // Handle file selection
501
+ function handleFileSelect(event) {
502
+ const files = event.target.files;
503
+ if (!files || files.length === 0) return;
504
+
505
+ showLoading(true);
506
+
507
+ // Process each file
508
+ Array.from(files).forEach(file => {
509
+ const fileName = file.name.toLowerCase();
510
+ const reader = new FileReader();
511
+
512
+ reader.onload = function(e) {
513
+ if (fileName.endsWith('.stl')) {
514
+ loadSTL(e.target.result, file.name);
515
+ } else if (fileName.endsWith('.obj')) {
516
+ loadOBJ(e.target.result, file.name);
517
+ }
518
+ };
519
+
520
+ reader.onerror = function() {
521
+ console.error('Error reading file');
522
+ showLoading(false);
523
+ };
524
+
525
+ if (fileName.endsWith('.stl') || fileName.endsWith('.obj')) {
526
+ reader.readAsArrayBuffer(file);
527
+ } else {
528
+ alert('Unsupported file format. Please upload STL or OBJ files.');
529
+ showLoading(false);
530
+ }
531
+ });
532
+
533
+ // Reset file input
534
+ event.target.value = '';
535
+ }
536
+
537
+ // Load STL file
538
+ function loadSTL(buffer, name) {
539
+ try {
540
+ const loader = new THREE.STLLoader();
541
+ const geometry = loader.parse(buffer);
542
+
543
+ const material = new THREE.MeshStandardMaterial({
544
+ color: 0xaaaaaa,
545
+ metalness: 0.5,
546
+ roughness: 0.5
547
+ });
548
+
549
+ const mesh = new THREE.Mesh(geometry, material);
550
+ mesh.castShadow = true;
551
+ mesh.receiveShadow = true;
552
+ mesh.name = name;
553
+
554
+ // Center and scale the model
555
+ geometry.computeBoundingBox();
556
+ const boundingBox = geometry.boundingBox;
557
+ const center = new THREE.Vector3();
558
+ boundingBox.getCenter(center);
559
+ mesh.position.sub(center);
560
+
561
+ // Scale to reasonable size
562
+ const size = boundingBox.getSize(new THREE.Vector3());
563
+ const maxDim = Math.max(size.x, size.y, size.z);
564
+ const scale = 5 / maxDim;
565
+ mesh.scale.set(scale, scale, scale);
566
+
567
+ scene.add(mesh);
568
+ objects.push(mesh);
569
+
570
+ updateObjectList();
571
+ showLoading(false);
572
+ } catch (e) {
573
+ console.error('Error loading STL:', e);
574
+ alert('Error loading STL file');
575
+ showLoading(false);
576
  }
577
+ }
578
+
579
+ // Load OBJ file
580
+ function loadOBJ(buffer, name) {
581
+ try {
582
+ const loader = new THREE.OBJLoader();
583
+ const obj = loader.parse(buffer);
584
+
585
+ // Apply material to all children
586
+ obj.traverse(child => {
587
+ if (child instanceof THREE.Mesh) {
588
+ child.material = new THREE.MeshStandardMaterial({
589
+ color: 0xaaaaaa,
590
+ metalness: 0.5,
591
+ roughness: 0.5
592
+ });
593
+ child.castShadow = true;
594
+ child.receiveShadow = true;
595
+ }
596
+ });
597
+
598
+ obj.name = name;
599
+
600
+ // Center and scale the model
601
+ const box = new THREE.Box3().setFromObject(obj);
602
+ const center = new THREE.Vector3();
603
+ box.getCenter(center);
604
+ obj.position.sub(center);
605
+
606
+ // Scale to reasonable size
607
+ const size = box.getSize(new THREE.Vector3());
608
+ const maxDim = Math.max(size.x, size.y, size.z);
609
+ const scale = 5 / maxDim;
610
+ obj.scale.set(scale, scale, scale);
611
+
612
+ scene.add(obj);
613
+ objects.push(obj);
614
+
615
+ updateObjectList();
616
+ showLoading(false);
617
+ } catch (e) {
618
+ console.error('Error loading OBJ:', e);
619
+ alert('Error loading OBJ file');
620
+ showLoading(false);
621
+ }
622
+ }
623
+
624
+ // Export STL
625
+ function exportSTL() {
626
+ if (objects.length === 0) {
627
+ alert('No objects to export');
628
+ return;
629
+ }
630
+
631
+ const exporter = new THREE.STLExporter();
632
+ let stlString = '';
633
+
634
+ // Export all objects
635
+ objects.forEach(obj => {
636
+ stlString += exporter.parse(obj);
637
+ });
638
+
639
+ // Create download link
640
+ const blob = new Blob([stlString], { type: 'text/plain' });
641
+ const url = URL.createObjectURL(blob);
642
+ const link = document.createElement('a');
643
+ link.href = url;
644
+ link.download = 'scene.stl';
645
+ document.body.appendChild(link);
646
+ link.click();
647
+ document.body.removeChild(link);
648
+ URL.revokeObjectURL(url);
649
+ }
650
+
651
+ // Export OBJ
652
+ function exportOBJ() {
653
+ if (objects.length === 0) {
654
+ alert('No objects to export');
655
+ return;
656
+ }
657
+
658
+ const exporter = new THREE.OBJExporter();
659
+ let objString = '';
660
+
661
+ // Export all objects
662
+ objects.forEach(obj => {
663
+ objString += exporter.parse(obj);
664
+ });
665
+
666
+ // Create download link
667
+ const blob = new Blob([objString], { type: 'text/plain' });
668
+ const url = URL.createObjectURL(blob);
669
+ const link = document.createElement('a');
670
+ link.href = url;
671
+ link.download = 'scene.obj';
672
+ document.body.appendChild(link);
673
+ link.click();
674
+ document.body.removeChild(link);
675
+ URL.revokeObjectURL(url);
676
+ }
677
+
678
+ // Update object list in UI
679
+ function updateObjectList() {
680
+ const objectList = document.getElementById('objectList');
681
+ objectList.innerHTML = '';
682
+
683
+ if (objects.length === 0) {
684
+ objectList.innerHTML = '<p class="text-gray-500 text-sm">No objects in scene</p>';
685
+ return;
686
+ }
687
+
688
+ objects.forEach((obj, index) => {
689
+ const item = document.createElement('div');
690
+ item.className = 'flex justify-between items-center bg-gray-700 px-2 py-1 rounded text-sm';
691
+
692
+ item.innerHTML = `
693
+ <span class="truncate">${obj.name || 'Unnamed Object'}</span>
694
+ <div class="flex space-x-1">
695
+ <button class="text-blue-400 hover:text-blue-300" data-action="focus" data-index="${index}">
696
+ <i class="fas fa-crosshairs"></i>
697
+ </button>
698
+ <button class="text-red-400 hover:text-red-300" data-action="remove" data-index="${index}">
699
+ <i class="fas fa-trash"></i>
700
+ </button>
701
+ </div>
702
+ `;
703
+
704
+ objectList.appendChild(item);
705
+ });
706
+
707
+ // Add event listeners
708
+ document.querySelectorAll('[data-action="focus"]').forEach(btn => {
709
+ btn.addEventListener('click', (e) => {
710
+ const index = parseInt(e.target.dataset.index || e.target.closest('button').dataset.index);
711
+ focusObject(objects[index]);
712
+ });
713
+ });
714
+
715
+ document.querySelectorAll('[data-action="remove"]').forEach(btn => {
716
+ btn.addEventListener('click', (e) => {
717
+ const index = parseInt(e.target.dataset.index || e.target.closest('button').dataset.index);
718
+ removeObject(index);
719
+ });
720
+ });
721
+ }
722
+
723
+ // Focus on object
724
+ function focusObject(obj) {
725
+ const box = new THREE.Box3().setFromObject(obj);
726
+ const size = box.getSize(new THREE.Vector3());
727
+ const center = box.getCenter(new THREE.Vector3());
728
+
729
+ // Position camera to view the entire object
730
+ const maxDim = Math.max(size.x, size.y, size.z);
731
+ const cameraDistance = maxDim * 2;
732
+
733
+ camera.position.copy(center);
734
+ camera.position.z += cameraDistance;
735
+ camera.lookAt(center);
736
+
737
+ controls.target.copy(center);
738
+ controls.update();
739
+ }
740
+
741
+ // Remove object
742
+ function removeObject(index) {
743
+ if (index >= 0 && index < objects.length) {
744
+ scene.remove(objects[index]);
745
+ objects.splice(index, 1);
746
+ updateObjectList();
747
+ }
748
+ }
749
+
750
+ // Show/hide loading indicator
751
+ function showLoading(show) {
752
+ document.getElementById('loadingIndicator').classList.toggle('hidden', !show);
753
+ }
754
+
755
+ // Initialize the app when DOM is loaded
756
+ document.addEventListener('DOMContentLoaded', init);
757
+ </script>
758
  </body>
759
  </html>