MySafeCode commited on
Commit
cc627f9
·
verified ·
1 Parent(s): e414710

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. assets/css/styles.css +207 -0
  2. assets/js/main.js +446 -0
  3. index.html +78 -19
assets/css/styles.css ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
10
+ color: #333;
11
+ min-height: 100vh;
12
+ }
13
+
14
+ .container {
15
+ max-width: 1400px;
16
+ margin: 0 auto;
17
+ padding: 20px;
18
+ }
19
+
20
+ header {
21
+ text-align: center;
22
+ margin-bottom: 30px;
23
+ color: white;
24
+ }
25
+
26
+ header h1 {
27
+ font-size: 2.5rem;
28
+ margin-bottom: 10px;
29
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
30
+ }
31
+
32
+ .built-with {
33
+ font-size: 0.9rem;
34
+ opacity: 0.9;
35
+ }
36
+
37
+ .built-with a {
38
+ color: #fff;
39
+ text-decoration: underline;
40
+ }
41
+
42
+ .main-content {
43
+ display: grid;
44
+ grid-template-columns: 1fr 1fr;
45
+ gap: 30px;
46
+ margin-top: 20px;
47
+ }
48
+
49
+ .view-section {
50
+ background: white;
51
+ border-radius: 15px;
52
+ padding: 25px;
53
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
54
+ }
55
+
56
+ .view-header {
57
+ display: flex;
58
+ justify-content: space-between;
59
+ align-items: center;
60
+ margin-bottom: 20px;
61
+ }
62
+
63
+ .view-header h2 {
64
+ color: #667eea;
65
+ font-size: 1.5rem;
66
+ }
67
+
68
+ .slice-info {
69
+ background: #f0f0f0;
70
+ padding: 5px 15px;
71
+ border-radius: 20px;
72
+ font-weight: bold;
73
+ color: #667eea;
74
+ }
75
+
76
+ .controls {
77
+ margin-bottom: 20px;
78
+ }
79
+
80
+ .control-group {
81
+ display: flex;
82
+ align-items: center;
83
+ margin-bottom: 15px;
84
+ gap: 15px;
85
+ }
86
+
87
+ .control-group label {
88
+ font-weight: 600;
89
+ min-width: 150px;
90
+ color: #555;
91
+ }
92
+
93
+ .control-group input[type="range"] {
94
+ flex: 1;
95
+ height: 6px;
96
+ background: #ddd;
97
+ border-radius: 3px;
98
+ outline: none;
99
+ -webkit-appearance: none;
100
+ }
101
+
102
+ .control-group input[type="range"]::-webkit-slider-thumb {
103
+ -webkit-appearance: none;
104
+ width: 18px;
105
+ height: 18px;
106
+ background: #667eea;
107
+ border-radius: 50%;
108
+ cursor: pointer;
109
+ }
110
+
111
+ .control-group input[type="checkbox"] {
112
+ width: 20px;
113
+ height: 20px;
114
+ cursor: pointer;
115
+ }
116
+
117
+ .control-group span {
118
+ font-weight: 600;
119
+ color: #667eea;
120
+ min-width: 40px;
121
+ }
122
+
123
+ #canvas3d {
124
+ width: 100%;
125
+ height: 400px;
126
+ border: 2px solid #667eea;
127
+ border-radius: 10px;
128
+ overflow: hidden;
129
+ }
130
+
131
+ #sliceCanvas {
132
+ width: 100%;
133
+ height: 400px;
134
+ border: 2px solid #764ba2;
135
+ border-radius: 10px;
136
+ background: #000;
137
+ }
138
+
139
+ .export-btn {
140
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
141
+ color: white;
142
+ border: none;
143
+ padding: 12px 25px;
144
+ border-radius: 25px;
145
+ font-size: 1rem;
146
+ font-weight: 600;
147
+ cursor: pointer;
148
+ transition: transform 0.2s;
149
+ }
150
+
151
+ .export-btn:hover {
152
+ transform: translateY(-2px);
153
+ }
154
+
155
+ .export-btn:disabled {
156
+ opacity: 0.6;
157
+ cursor: not-allowed;
158
+ }
159
+
160
+ .progress-bar {
161
+ width: 100%;
162
+ height: 30px;
163
+ background: #f0f0f0;
164
+ border-radius: 15px;
165
+ overflow: hidden;
166
+ position: relative;
167
+ margin-top: 15px;
168
+ }
169
+
170
+ .progress-fill {
171
+ height: 100%;
172
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
173
+ width: 0%;
174
+ transition: width 0.3s ease;
175
+ }
176
+
177
+ .progress-text {
178
+ position: absolute;
179
+ top: 50%;
180
+ left: 50%;
181
+ transform: translate(-50%, -50%);
182
+ font-weight: 600;
183
+ color: #333;
184
+ }
185
+
186
+ .hidden {
187
+ display: none;
188
+ }
189
+
190
+ @media (max-width: 768px) {
191
+ .main-content {
192
+ grid-template-columns: 1fr;
193
+ }
194
+
195
+ header h1 {
196
+ font-size: 2rem;
197
+ }
198
+
199
+ .control-group {
200
+ flex-direction: column;
201
+ align-items: flex-start;
202
+ }
203
+
204
+ .control-group label {
205
+ min-width: auto;
206
+ }
207
+ }
assets/js/main.js ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global variables
2
+ let scene, camera, renderer, controls;
3
+ let model, cuttingPlaneMesh;
4
+ let rotationSlider, autoRotateCheckbox, cuttingPlaneSlider;
5
+ let sliceCanvas, sliceCtx;
6
+ let isRecording = false;
7
+ let mediaRecorder;
8
+ let recordedChunks = [];
9
+ let animationId;
10
+ let sliceData = [];
11
+
12
+ // Initialize Three.js scene
13
+ function init3DScene() {
14
+ const container = document.getElementById('canvas3d');
15
+
16
+ // Scene setup
17
+ scene = new THREE.Scene();
18
+ scene.background = new THREE.Color(0x1a1a2e);
19
+
20
+ // Camera setup
21
+ camera = new THREE.PerspectiveCamera(
22
+ 75,
23
+ container.clientWidth / container.clientHeight,
24
+ 0.1,
25
+ 1000
26
+ );
27
+ camera.position.set(0, 0, 100);
28
+
29
+ // Renderer setup
30
+ renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
31
+ renderer.setSize(container.clientWidth, container.clientHeight);
32
+ renderer.localClippingEnabled = true;
33
+ container.appendChild(renderer.domElement);
34
+
35
+ // Controls
36
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
37
+ controls.enableDamping = true;
38
+ controls.dampingFactor = 0.05;
39
+
40
+ // Lighting
41
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
42
+ scene.add(ambientLight);
43
+
44
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
45
+ directionalLight.position.set(10, 10, 10);
46
+ scene.add(directionalLight);
47
+
48
+ // Create 3D model (composite sphere with inner structures)
49
+ createCompositeModel();
50
+
51
+ // Create cutting plane visualization
52
+ createCuttingPlane();
53
+
54
+ // Event listeners
55
+ setupEventListeners();
56
+
57
+ // Initialize slice canvas
58
+ initSliceCanvas();
59
+
60
+ // Start render loop
61
+ animate();
62
+ }
63
+
64
+ // Create a composite 3D model
65
+ function createCompositeModel() {
66
+ // Main outer shell
67
+ const outerGeometry = new THREE.SphereGeometry(30, 32, 32);
68
+ const outerMaterial = new THREE.MeshPhongMaterial({
69
+ color: 0x4a90e2,
70
+ transparent: true,
71
+ opacity: 0.7,
72
+ side: THREE.DoubleSide
73
+ });
74
+
75
+ model = new THREE.Mesh(outerGeometry, outerMaterial);
76
+ model.name = 'compositeModel';
77
+ scene.add(model);
78
+
79
+ // Inner structures
80
+ const innerGeometry1 = new THREE.SphereGeometry(15, 16, 16);
81
+ const innerMaterial1 = new THREE.MeshPhongMaterial({
82
+ color: 0x7ed321,
83
+ transparent: true,
84
+ opacity: 0.8
85
+ });
86
+ const innerSphere1 = new THREE.Mesh(innerGeometry1, innerMaterial1);
87
+ innerSphere1.position.set(5, 5, -5);
88
+ model.add(innerSphere1);
89
+
90
+ const innerGeometry2 = new THREE.BoxGeometry(15, 15, 15);
91
+ const innerMaterial2 = new THREE.MeshPhongMaterial({
92
+ color: 0xf5a623,
93
+ transparent: true,
94
+ opacity: 0.8
95
+ });
96
+ const innerBox = new THREE.Mesh(innerGeometry2, innerMaterial2);
97
+ innerBox.position.set(-8, -3, 8);
98
+ innerBox.rotation.set(0.5, 0.3, 0.7);
99
+ model.add(innerBox);
100
+
101
+ // Additional small structures for complexity
102
+ for (let i = 0; i < 5; i++) {
103
+ const smallGeometry = new THREE.SphereGeometry(3 + Math.random() * 5, 8, 8);
104
+ const smallMaterial = new THREE.MeshPhongMaterial({
105
+ color: new THREE.Color().setHSL(0.5 + Math.random() * 0.3, 0.8, 0.6),
106
+ transparent: true,
107
+ opacity: 0.9
108
+ });
109
+ const smallSphere = new THREE.Mesh(smallGeometry, smallMaterial);
110
+ smallSphere.position.set(
111
+ (Math.random() - 0.5) * 40,
112
+ (Math.random() - 0.5) * 40,
113
+ (Math.random() - 0.5) * 40
114
+ );
115
+ model.add(smallSphere);
116
+ }
117
+
118
+ // Update slice data for all z positions
119
+ updateSliceData();
120
+ }
121
+
122
+ // Generate slice data for 2D view
123
+ function updateSliceData() {
124
+ sliceData = [];
125
+ const resolution = parseInt(document.getElementById('sliceResolution').value);
126
+ const totalSlices = 100;
127
+
128
+ for (let z = 0; z < totalSlices; z++) {
129
+ const zPos = (z - totalSlices / 2) / (totalSlices / 100);
130
+ const sliceArray = [];
131
+
132
+ for (let y = 0; y < resolution; y++) {
133
+ const row = [];
134
+ for (let x = 0; x < resolution; x++) {
135
+ // Convert canvas coordinates to 3D space
136
+ const xPos = (x - resolution / 2) / (resolution / 100);
137
+ const yPos = (y - resolution / 2) / (resolution / 100);
138
+
139
+ // Calculate density based on distance from center for outer sphere
140
+ const distanceFromCenter = Math.sqrt(xPos * xPos + yPos * yPos + zPos * zPos);
141
+ let density = 0;
142
+
143
+ // Outer sphere (main shell)
144
+ if (distanceFromCenter <= 30) {
145
+ density = 0.5 + (1 - distanceFromCenter / 30) * 0.5;
146
+
147
+ // Inner sphere 1
148
+ const dist1 = Math.sqrt(
149
+ (xPos - 5) * (xPos - 5) +
150
+ (yPos - 5) * (yPos - 5) +
151
+ (zPos + 5) * (zPos + 5)
152
+ );
153
+ if (dist1 <= 15) {
154
+ density = Math.max(density, 0.8 + (1 - dist1 / 15) * 0.2);
155
+ }
156
+
157
+ // Inner box (approximated)
158
+ const boxDistX = Math.abs(xPos + 8);
159
+ const boxDistY = Math.abs(yPos + 3);
160
+ const boxDistZ = Math.abs(zPos - 8);
161
+ if (boxDistX <= 12 && boxDistY <= 12 && boxDistZ <= 12) {
162
+ density = Math.max(density, 0.7 + (1 - boxDistX / 12) * 0.3 * 0.3);
163
+ density = Math.max(density, 0.7 + (1 - boxDistY / 12) * 0.3 * 0.3);
164
+ density = Math.max(density, 0.7 + (1 - boxDistZ / 12) * 0.3 * 0.3);
165
+ }
166
+
167
+ // Small random structures
168
+ for (let i = 0; i < 5; i++) {
169
+ const posX = (Math.sin(i * 1.5) - 0.5) * 40;
170
+ const posY = (Math.cos(i * 1.3) - 0.5) * 40;
171
+ const posZ = (Math.sin(i) - 0.5) * 40;
172
+
173
+ const smallDist = Math.sqrt(
174
+ (xPos - posX) * (xPos - posX) +
175
+ (yPos - posY) * (yPos - posY) +
176
+ (zPos - posZ) * (zPos - posZ)
177
+ );
178
+ const radius = 8 + i * 2;
179
+
180
+ if (smallDist <= radius) {
181
+ density = Math.max(density, 0.6 + (1 - smallDist / radius) * 0.4);
182
+ }
183
+ }
184
+ }
185
+
186
+ row.push(density);
187
+ }
188
+ sliceArray.push(row);
189
+ }
190
+
191
+ sliceData.push(sliceArray);
192
+ }
193
+ }
194
+
195
+ // Initialize slice canvas
196
+ function initSliceCanvas() {
197
+ sliceCanvas = document.getElementById('sliceCanvas');
198
+ sliceCtx = sliceCanvas.getContext('2d');
199
+
200
+ const resolution = parseInt(document.getElementById('sliceResolution').value);
201
+ sliceCanvas.width = resolution;
202
+ sliceCanvas.height = resolution;
203
+ }
204
+
205
+ // Create cutting plane visualization
206
+ function createCuttingPlane() {
207
+ const planeGeometry = new THREE.PlaneGeometry(100, 100);
208
+ const planeMaterial = new THREE.MeshBasicMaterial({
209
+ color: 0xff0000,
210
+ transparent: true,
211
+ opacity: 0.3,
212
+ side: THREE.DoubleSide
213
+ });
214
+
215
+ cuttingPlaneMesh = new THREE.Mesh(planeGeometry, planeMaterial);
216
+ cuttingPlaneMesh.rotation.x = -Math.PI / 2;
217
+ cuttingPlaneMesh.visible = true;
218
+ scene.add(cuttingPlaneMesh);
219
+ }
220
+
221
+ // Update 2D slice view
222
+ function updateSliceView(zPosition) {
223
+ if (!sliceCtx || sliceData.length === 0) return;
224
+
225
+ const resolution = parseInt(document.getElementById('sliceResolution').value);
226
+ const sliceIndex = Math.floor((zPosition + 50) / 100 * (sliceData.length - 1));
227
+
228
+ if (sliceIndex < 0 || sliceIndex >= sliceData.length) return;
229
+
230
+ const slice = sliceData[sliceIndex];
231
+ const imageData = sliceCtx.createImageData(resolution, resolution);
232
+ const data = imageData.data;
233
+
234
+ for (let y = 0; y < resolution; y++) {
235
+ for (let x = 0; x < resolution; x++) {
236
+ const density = slice[y][x];
237
+ const index = (y * resolution + x) * 4;
238
+
239
+ if (density > 0) {
240
+ // Color based on density
241
+ const hue = 0.6 - density * 0.3; // Blue to green to yellow
242
+ const rgb = hslToRgb(hue, 0.8, 0.5 + density * 0.3);
243
+
244
+ data[index] = rgb[0]; // R
245
+ data[index + 1] = rgb[1]; // G
246
+ data[index + 2] = rgb[2]; // B
247
+ data[index + 3] = Math.floor(density * 255); // A
248
+ } else {
249
+ data[index] = 0;
250
+ data[index + 1] = 0;
251
+ data[index + 2] = 0;
252
+ data[index + 3] = 0;
253
+ }
254
+ }
255
+ }
256
+
257
+ sliceCtx.putImageData(imageData, 0, 0);
258
+
259
+ // Update slice info
260
+ document.getElementById('sliceInfo').textContent = `Z = ${zPosition.toFixed(1)}`;
261
+ }
262
+
263
+ // Convert HSL to RGB
264
+ function hslToRgb(h, s, l) {
265
+ const c = (1 - Math.abs(2 * l - 1)) * s;
266
+ const x = c * (1 - Math.abs((h * 6) % 2 - 1));
267
+ const m = l - c / 2;
268
+
269
+ let r, g, b;
270
+ if (h < 1/6) {
271
+ r = c; g = x; b = 0;
272
+ } else if (h < 2/6) {
273
+ r = x; g = c; b = 0;
274
+ } else if (h < 3/6) {
275
+ r = 0; g = c; b = x;
276
+ } else if (h < 4/6) {
277
+ r = 0; g = x; b = c;
278
+ } else if (h < 5/6) {
279
+ r = x; g = 0; b = c;
280
+ } else {
281
+ r = c; g = 0; b = x;
282
+ }
283
+
284
+ return [
285
+ Math.floor((r + m) * 255),
286
+ Math.floor((g + m) * 255),
287
+ Math.floor((b + m) * 255)
288
+ ];
289
+ }
290
+
291
+ // Setup event listeners
292
+ function setupEventListeners() {
293
+ rotationSlider = document.getElementById('rotationSlider');
294
+ autoRotateCheckbox = document.getElementById('autoRotate');
295
+ cuttingPlaneSlider = document.getElementById('cuttingPlane');
296
+
297
+ // Rotation slider
298
+ rotationSlider.addEventListener('input', (e) => {
299
+ const value = e.target.value;
300
+ document.getElementById('rotationValue').textContent = value + '°';
301
+ if (model) {
302
+ model.rotation.y = (value * Math.PI) / 180;
303
+ }
304
+ });
305
+
306
+ // Auto rotate checkbox
307
+ autoRotateCheckbox.addEventListener('change', (e) => {
308
+ controls.autoRotate = e.target.checked;
309
+ });
310
+
311
+ // Cutting plane slider
312
+ cuttingPlaneSlider.addEventListener('input', (e) => {
313
+ const value = parseFloat(e.target.value);
314
+ document.getElementById('planeValue').textContent = value.toFixed(1);
315
+ updateCuttingPlane(value);
316
+ updateSliceView(value);
317
+ });
318
+
319
+ // Speed slider
320
+ const speedSlider = document.getElementById('scanSpeed');
321
+ speedSlider.addEventListener('input', (e) => {
322
+ document.getElementById('speedValue').textContent = e.target.value + 'x';
323
+ });
324
+
325
+ // Resolution selector
326
+ document.getElementById('sliceResolution').addEventListener('change', () => {
327
+ updateSliceData();
328
+ initSliceCanvas();
329
+ updateSliceView(parseFloat(cuttingPlaneSlider.value));
330
+ });
331
+
332
+ // Export button
333
+ document.getElementById('exportBtn').addEventListener('click', exportScan);
334
+ }
335
+
336
+ // Update cutting plane position
337
+ function updateCuttingPlane(zPosition) {
338
+ if (cuttingPlaneMesh) {
339
+ cuttingPlaneMesh.position.z = zPosition;
340
+ }
341
+ }
342
+
343
+ // Animation loop
344
+ function animate() {
345
+ animationId = requestAnimationFrame(animate);
346
+
347
+ controls.update();
348
+ renderer.render(scene, camera);
349
+ }
350
+
351
+ // Export scan as video
352
+ async function exportScan() {
353
+ if (isRecording) return;
354
+
355
+ const exportBtn = document.getElementById('exportBtn');
356
+ const progressBar = document.getElementById('exportProgress');
357
+ const progressFill = progressBar.querySelector('.progress-fill');
358
+ const progressText = progressBar.querySelector('.progress-text');
359
+
360
+ exportBtn.disabled = true;
361
+ progressBar.classList.remove('hidden');
362
+ isRecording = true;
363
+
364
+ // Setup MediaRecorder
365
+ const stream = sliceCanvas.captureStream(30);
366
+ const options = {
367
+ mimeType: 'video/webm;codecs=vp8,opus',
368
+ videoBitsPerSecond: 2500000
369
+ };
370
+
371
+ try {
372
+ mediaRecorder = new MediaRecorder(stream, options);
373
+ recordedChunks = [];
374
+
375
+ mediaRecorder.ondataavailable = (event) => {
376
+ if (event.data.size > 0) {
377
+ recordedChunks.push(event.data);
378
+ }
379
+ };
380
+
381
+ mediaRecorder.onstop = () => {
382
+ const blob = new Blob(recordedChunks, { type: 'video/webm' });
383
+ const url = URL.createObjectURL(blob);
384
+ const a = document.createElement('a');
385
+ a.href = url;
386
+ a.download = `3d-scan-${Date.now()}.webm`;
387
+ a.click();
388
+ URL.revokeObjectURL(url);
389
+
390
+ // Reset UI
391
+ exportBtn.disabled = false;
392
+ progressBar.classList.add('hidden');
393
+ isRecording = false;
394
+
395
+ // Reset cutting plane
396
+ cuttingPlaneSlider.value = 0;
397
+ updateCuttingPlane(0);
398
+ updateSliceView(0);
399
+ };
400
+
401
+ // Start recording
402
+ mediaRecorder.start();
403
+
404
+ // Animate scan from top to bottom
405
+ const scanSpeed = parseFloat(document.getElementById('scanSpeed').value);
406
+ const totalSteps = 100;
407
+ const stepDelay = 1000 / scanSpeed / totalSteps;
408
+
409
+ for (let i = 0; i <= totalSteps; i++) {
410
+ const zPosition = 50 - (i * 100 / totalSteps);
411
+ cuttingPlaneSlider.value = zPosition;
412
+ updateCuttingPlane(zPosition);
413
+ updateSliceView(zPosition);
414
+
415
+ // Update progress
416
+ const progress = (i / totalSteps) * 100;
417
+ progressFill.style.width = progress + '%';
418
+ progressText.textContent = Math.floor(progress) + '%';
419
+
420
+ await new Promise(resolve => setTimeout(resolve, stepDelay));
421
+ }
422
+
423
+ // Stop recording
424
+ mediaRecorder.stop();
425
+
426
+ } catch (err) {
427
+ console.error('Recording failed:', err);
428
+ alert('Video recording failed. Please check browser permissions.');
429
+ exportBtn.disabled = false;
430
+ progressBar.classList.add('hidden');
431
+ isRecording = false;
432
+ }
433
+ }
434
+
435
+ // Initialize on load
436
+ window.addEventListener('load', init3DScene);
437
+
438
+ // Handle window resize
439
+ window.addEventListener('resize', () => {
440
+ const container = document.getElementById('canvas3d');
441
+ if (camera && renderer) {
442
+ camera.aspect = container.clientWidth / container.clientHeight;
443
+ camera.updateProjectionMatrix();
444
+ renderer.setSize(container.clientWidth, container.clientHeight);
445
+ }
446
+ });
index.html CHANGED
@@ -1,19 +1,78 @@
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 Slicer with Live 2D View</title>
7
+ <link rel="stylesheet" href="assets/css/styles.css">
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
10
+ </head>
11
+ <body>
12
+ <div class="container">
13
+ <header>
14
+ <h1>3D Model Slicer</h1>
15
+ <p class="built-with">Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a></p>
16
+ </header>
17
+
18
+ <div class="main-content">
19
+ <div class="view-section">
20
+ <div class="view-header">
21
+ <h2>3D Model View</h2>
22
+ </div>
23
+ <div class="controls">
24
+ <div class="control-group">
25
+ <label for="rotationSlider">Manual Rotation:</label>
26
+ <input type="range" id="rotationSlider" min="-180" max="180" value="0" step="1">
27
+ <span id="rotationValue">0°</span>
28
+ </div>
29
+ <div class="control-group">
30
+ <label for="autoRotate">Auto Rotate:</label>
31
+ <input type="checkbox" id="autoRotate">
32
+ </div>
33
+ <div class="control-group">
34
+ <label for="cuttingPlane">Cutting Plane Position:</label>
35
+ <input type="range" id="cuttingPlane" min="-50" max="50" value="0" step="0.5">
36
+ <span id="planeValue">0</span>
37
+ </div>
38
+ </div>
39
+ <div id="canvas3d"></div>
40
+ </div>
41
+
42
+ <div class="view-section">
43
+ <div class="view-header">
44
+ <h2>2D Slice View</h2>
45
+ <div class="slice-info">
46
+ <span id="sliceInfo">Z = 0.0</span>
47
+ </div>
48
+ </div>
49
+ <canvas id="sliceCanvas"></canvas>
50
+ <div class="controls">
51
+ <div class="control-group">
52
+ <button id="exportBtn" class="export-btn">Export Scan as MP4</button>
53
+ </div>
54
+ <div class="control-group">
55
+ <label for="scanSpeed">Scan Speed:</label>
56
+ <input type="range" id="scanSpeed" min="1" max="10" value="5" step="0.5">
57
+ <span id="speedValue">5x</span>
58
+ </div>
59
+ <div class="control-group">
60
+ <label for="sliceResolution">Resolution:</label>
61
+ <select id="sliceResolution">
62
+ <option value="256">256x256</option>
63
+ <option value="512" selected>512x512</option>
64
+ <option value="768">768x768</option>
65
+ </select>
66
+ </div>
67
+ </div>
68
+ <div id="exportProgress" class="progress-bar hidden">
69
+ <div class="progress-fill"></div>
70
+ <span class="progress-text">0%</span>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <script src="assets/js/main.js"></script>
77
+ </body>
78
+ </html>