ThorAILabs commited on
Commit
32ce8fe
·
verified ·
1 Parent(s): 4a7eadc

add material handling

Browse files
Files changed (1) hide show
  1. index.html +170 -16
index.html CHANGED
@@ -3,7 +3,7 @@
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>
@@ -11,11 +11,12 @@
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
  </head>
15
  <body class="bg-gray-900">
16
  <div class="flex h-screen">
17
  <!-- Sidebar -->
18
- <div class="w-64 bg-gray-800 p-4 space-y-4">
19
  <div class="space-y-2">
20
  <input type="file" id="file-input" class="hidden" accept=".stl,.obj">
21
  <button onclick="document.getElementById('file-input').click()"
@@ -51,35 +52,100 @@
51
  <i class="fas fa-sun mr-2"></i>Add Directional Light
52
  </button>
53
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  </div>
55
 
56
  <!-- Main Viewport -->
57
- <div id="viewport" class="flex-1"></div>
 
 
 
 
58
  </div>
59
 
60
  <script>
61
  let scene, camera, renderer, raytracingEnabled = true;
62
  let objects = [];
 
 
 
63
 
64
  function init() {
65
  // Scene setup
66
  scene = new THREE.Scene();
 
 
67
  camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
68
  renderer = new THREE.WebGLRenderer({ antialias: true });
69
- renderer.setSize(window.innerWidth-256, window.innerHeight);
 
 
70
  document.getElementById('viewport').appendChild(renderer.domElement);
71
 
72
- // Basic lighting
73
  const ambientLight = new THREE.AmbientLight(0x404040);
74
  scene.add(ambientLight);
 
 
 
 
75
 
76
- // Camera position
77
- camera.position.z = 5;
 
 
 
78
 
79
- // File input handler
80
  document.getElementById('file-input').addEventListener('change', handleFileUpload);
 
 
 
 
 
 
81
 
82
- // Animation loop
83
  animate();
84
  }
85
 
@@ -87,14 +153,29 @@ function handleFileUpload(event) {
87
  const file = event.target.files[0];
88
  const reader = new FileReader();
89
 
 
90
  reader.onload = function(e) {
91
  const loader = file.name.endsWith('.stl') ?
92
  new THREE.STLLoader() : new THREE.OBJLoader();
93
 
94
  const model = loader.parse(e.target.result);
95
  model.scale.set(0.1, 0.1, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  scene.add(model);
97
  objects.push(model);
 
98
  };
99
 
100
  reader.readAsArrayBuffer(file);
@@ -133,22 +214,93 @@ function addLight(type) {
133
  if (type === 'point') {
134
  light = new THREE.PointLight(0xffffff, 1, 100);
135
  light.position.set(5, 5, 5);
 
136
  } else {
137
  light = new THREE.DirectionalLight(0xffffff, 1);
138
  light.position.set(5, 5, 5);
 
139
  }
140
  scene.add(light);
141
  }
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  function animate() {
144
  requestAnimationFrame(animate);
 
145
  renderer.render(scene, camera);
146
  }
147
 
148
- // Start the application
149
- init();
150
-
151
- // Helper function to save files
152
  function saveAs(blob, filename) {
153
  const link = document.createElement('a');
154
  link.href = URL.createObjectURL(blob);
@@ -156,12 +308,14 @@ function saveAs(blob, filename) {
156
  link.click();
157
  }
158
 
159
- // Window resize handler
160
  window.addEventListener('resize', () => {
161
- camera.aspect = (window.innerWidth-256) / window.innerHeight;
162
  camera.updateProjectionMatrix();
163
- renderer.setSize(window.innerWidth-256, window.innerHeight);
164
  });
 
 
 
165
  </script>
166
  </body>
167
  </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 & 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>
 
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()"
 
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
 
 
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);
 
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);
 
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>