MarkArtist21 commited on
Commit
8665e07
·
verified ·
1 Parent(s): a5b2dc3

undefined - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +817 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Shapefinder
3
- emoji: 🔥
4
- colorFrom: red
5
- colorTo: red
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: shapefinder
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: purple
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,817 @@
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>Interactive Bezier-Bands Tool</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/control-p5/1.6.0/controlP5.min.js"></script>
10
+ <style>
11
+ .band-fill-0 { fill: rgba(255, 99, 132, 0.3); }
12
+ .band-fill-1 { fill: rgba(54, 162, 235, 0.3); }
13
+ .band-fill-2 { fill: rgba(255, 206, 86, 0.3); }
14
+ .band-fill-3 { fill: rgba(75, 192, 192, 0.3); }
15
+ .band-fill-4 { fill: rgba(153, 102, 255, 0.3); }
16
+
17
+ .band-stroke-0 { stroke: rgba(255, 99, 132, 1); }
18
+ .band-stroke-1 { stroke: rgba(54, 162, 235, 1); }
19
+ .band-stroke-2 { stroke: rgba(255, 206, 86, 1); }
20
+ .band-stroke-3 { stroke: rgba(75, 192, 192, 1); }
21
+ .band-stroke-4 { stroke: rgba(153, 102, 255, 1); }
22
+
23
+ #canvas-container {
24
+ position: relative;
25
+ width: 100%;
26
+ height: 100%;
27
+ }
28
+
29
+ #processing-canvas {
30
+ width: 100%;
31
+ height: 100%;
32
+ }
33
+
34
+ .tooltip {
35
+ position: relative;
36
+ display: inline-block;
37
+ }
38
+
39
+ .tooltip .tooltiptext {
40
+ visibility: hidden;
41
+ width: 200px;
42
+ background-color: #555;
43
+ color: #fff;
44
+ text-align: center;
45
+ border-radius: 6px;
46
+ padding: 5px;
47
+ position: absolute;
48
+ z-index: 1;
49
+ bottom: 125%;
50
+ left: 50%;
51
+ margin-left: -100px;
52
+ opacity: 0;
53
+ transition: opacity 0.3s;
54
+ }
55
+
56
+ .tooltip:hover .tooltiptext {
57
+ visibility: visible;
58
+ opacity: 1;
59
+ }
60
+ </style>
61
+ </head>
62
+ <body class="bg-gray-100 h-screen flex flex-col">
63
+ <div class="bg-indigo-700 text-white p-4 shadow-md">
64
+ <h1 class="text-2xl font-bold">Interactive Bezier-Bands Tool</h1>
65
+ <p class="text-sm opacity-80">Create smooth curves with offset bands and transformations</p>
66
+ </div>
67
+
68
+ <div class="flex flex-1 overflow-hidden">
69
+ <!-- Control Panel -->
70
+ <div class="w-80 bg-white p-4 shadow-md overflow-y-auto">
71
+ <div class="space-y-6">
72
+ <div>
73
+ <h2 class="text-lg font-semibold mb-2 border-b pb-2">Curve Parameters</h2>
74
+ <div class="space-y-4">
75
+ <div>
76
+ <label for="numCurves" class="block text-sm font-medium text-gray-700 tooltip">
77
+ Number of Curves (N)
78
+ <span class="tooltiptext">Total number of curves including the base curve (min 2, max 50)</span>
79
+ </label>
80
+ <input type="range" id="numCurves" min="2" max="50" value="10" class="w-full mt-1">
81
+ <div class="flex justify-between text-xs text-gray-500">
82
+ <span>2</span>
83
+ <span id="numCurvesValue">10</span>
84
+ <span>50</span>
85
+ </div>
86
+ </div>
87
+
88
+ <div>
89
+ <label for="offsetSpacing" class="block text-sm font-medium text-gray-700 tooltip">
90
+ Offset Spacing (D)
91
+ <span class="tooltiptext">Distance between each offset curve in pixels</span>
92
+ </label>
93
+ <input type="range" id="offsetSpacing" min="1" max="50" value="20" class="w-full mt-1">
94
+ <div class="flex justify-between text-xs text-gray-500">
95
+ <span>1</span>
96
+ <span id="offsetSpacingValue">20</span>
97
+ <span>50</span>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <div>
104
+ <h2 class="text-lg font-semibold mb-2 border-b pb-2">Base Transform</h2>
105
+ <div class="space-y-4">
106
+ <div>
107
+ <label for="baseOffsetX" class="block text-sm font-medium text-gray-700 tooltip">
108
+ Base X Offset
109
+ <span class="tooltiptext">Initial X offset applied to the first band</span>
110
+ </label>
111
+ <input type="range" id="baseOffsetX" min="-100" max="100" value="0" class="w-full mt-1">
112
+ <div class="flex justify-between text-xs text-gray-500">
113
+ <span>-100</span>
114
+ <span id="baseOffsetXValue">0</span>
115
+ <span>100</span>
116
+ </div>
117
+ </div>
118
+
119
+ <div>
120
+ <label for="baseOffsetY" class="block text-sm font-medium text-gray-700 tooltip">
121
+ Base Y Offset
122
+ <span class="tooltiptext">Initial Y offset applied to the first band</span>
123
+ </label>
124
+ <input type="range" id="baseOffsetY" min="-100" max="100" value="0" class="w-full mt-1">
125
+ <div class="flex justify-between text-xs text-gray-500">
126
+ <span>-100</span>
127
+ <span id="baseOffsetYValue">0</span>
128
+ <span>100</span>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ <div>
135
+ <h2 class="text-lg font-semibold mb-2 border-b pb-2">Incremental Transform</h2>
136
+ <div class="space-y-4">
137
+ <div>
138
+ <label for="deltaOffsetX" class="block text-sm font-medium text-gray-700 tooltip">
139
+ X Offset Increment
140
+ <span class="tooltiptext">Additional X offset added to each subsequent band</span>
141
+ </label>
142
+ <input type="range" id="deltaOffsetX" min="-20" max="20" value="0" class="w-full mt-1">
143
+ <div class="flex justify-between text-xs text-gray-500">
144
+ <span>-20</span>
145
+ <span id="deltaOffsetXValue">0</span>
146
+ <span>20</span>
147
+ </div>
148
+ </div>
149
+
150
+ <div>
151
+ <label for="deltaOffsetY" class="block text-sm font-medium text-gray-700 tooltip">
152
+ Y Offset Increment
153
+ <span class="tooltiptext">Additional Y offset added to each subsequent band</span>
154
+ </label>
155
+ <input type="range" id="deltaOffsetY" min="-20" max="20" value="0" class="w-full mt-1">
156
+ <div class="flex justify-between text-xs text-gray-500">
157
+ <span>-20</span>
158
+ <span id="deltaOffsetYValue">0</span>
159
+ <span>20</span>
160
+ </div>
161
+ </div>
162
+
163
+ <div>
164
+ <label for="deltaRotationDeg" class="block text-sm font-medium text-gray-700 tooltip">
165
+ Rotation Increment (deg)
166
+ <span class="tooltiptext">Additional rotation applied to each subsequent band in degrees</span>
167
+ </label>
168
+ <input type="range" id="deltaRotationDeg" min="-15" max="15" value="0" class="w-full mt-1">
169
+ <div class="flex justify-between text-xs text-gray-500">
170
+ <span>-15</span>
171
+ <span id="deltaRotationDegValue">0</span>
172
+ <span>15</span>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <div>
179
+ <h2 class="text-lg font-semibold mb-2 border-b pb-2">Anchor Point</h2>
180
+ <div class="flex items-center space-x-2">
181
+ <input type="radio" id="anchorCentroid" name="anchorType" value="centroid" checked class="h-4 w-4 text-indigo-600">
182
+ <label for="anchorCentroid" class="text-sm font-medium text-gray-700">Centroid</label>
183
+
184
+ <input type="radio" id="anchorBounds" name="anchorType" value="bounds" class="h-4 w-4 text-indigo-600">
185
+ <label for="anchorBounds" class="text-sm font-medium text-gray-700">Bounds Center</label>
186
+ </div>
187
+ </div>
188
+
189
+ <div class="space-y-2">
190
+ <h2 class="text-lg font-semibold mb-2 border-b pb-2">Actions</h2>
191
+ <button id="exportSVG" class="w-full bg-indigo-600 text-white py-2 px-4 rounded hover:bg-indigo-700 transition">
192
+ Export SVG
193
+ </button>
194
+ <button id="savePNG" class="w-full bg-indigo-600 text-white py-2 px-4 rounded hover:bg-indigo-700 transition">
195
+ Save PNG
196
+ </button>
197
+ <button id="clearPoints" class="w-full bg-red-600 text-white py-2 px-4 rounded hover:bg-red-700 transition">
198
+ Clear Points
199
+ </button>
200
+ <button id="resetView" class="w-full bg-gray-600 text-white py-2 px-4 rounded hover:bg-gray-700 transition">
201
+ Reset View
202
+ </button>
203
+ </div>
204
+
205
+ <div class="text-xs text-gray-500">
206
+ <p><strong>Instructions:</strong></p>
207
+ <ul class="list-disc pl-5 space-y-1 mt-1">
208
+ <li>Left-click to add points</li>
209
+ <li>Drag points to move them</li>
210
+ <li>Right-click a point to delete it</li>
211
+ <li>Press 'C' to clear all points</li>
212
+ </ul>
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- Canvas Area -->
218
+ <div class="flex-1 bg-gray-200 relative">
219
+ <div id="canvas-container">
220
+ <div id="processing-canvas"></div>
221
+ </div>
222
+
223
+ <div id="status-message" class="absolute top-4 left-4 bg-white bg-opacity-90 p-2 rounded shadow-md hidden">
224
+ <p id="status-text" class="text-sm"></p>
225
+ </div>
226
+ </div>
227
+ </div>
228
+
229
+ <script>
230
+ // Global variables
231
+ let points = [];
232
+ let selectedPoint = null;
233
+ let baseCurve = [];
234
+ let offsetCurves = [];
235
+ let bands = [];
236
+ let viewOffset = { x: 0, y: 0 };
237
+ let viewScale = 1;
238
+ let isDragging = false;
239
+ let lastMouseX, lastMouseY;
240
+
241
+ // DOM elements
242
+ const numCurvesSlider = document.getElementById('numCurves');
243
+ const offsetSpacingSlider = document.getElementById('offsetSpacing');
244
+ const baseOffsetXSlider = document.getElementById('baseOffsetX');
245
+ const baseOffsetYSlider = document.getElementById('baseOffsetY');
246
+ const deltaOffsetXSlider = document.getElementById('deltaOffsetX');
247
+ const deltaOffsetYSlider = document.getElementById('deltaOffsetY');
248
+ const deltaRotationDegSlider = document.getElementById('deltaRotationDeg');
249
+ const exportSVGBtn = document.getElementById('exportSVG');
250
+ const savePNGBtn = document.getElementById('savePNG');
251
+ const clearPointsBtn = document.getElementById('clearPoints');
252
+ const resetViewBtn = document.getElementById('resetView');
253
+ const statusMessage = document.getElementById('status-message');
254
+ const statusText = document.getElementById('status-text');
255
+
256
+ // Display values
257
+ const numCurvesValue = document.getElementById('numCurvesValue');
258
+ const offsetSpacingValue = document.getElementById('offsetSpacingValue');
259
+ const baseOffsetXValue = document.getElementById('baseOffsetXValue');
260
+ const baseOffsetYValue = document.getElementById('baseOffsetYValue');
261
+ const deltaOffsetXValue = document.getElementById('deltaOffsetXValue');
262
+ const deltaOffsetYValue = document.getElementById('deltaOffsetYValue');
263
+ const deltaRotationDegValue = document.getElementById('deltaRotationDegValue');
264
+
265
+ // Update display values when sliders change
266
+ numCurvesSlider.addEventListener('input', () => {
267
+ numCurvesValue.textContent = numCurvesSlider.value;
268
+ updateCurves();
269
+ });
270
+
271
+ offsetSpacingSlider.addEventListener('input', () => {
272
+ offsetSpacingValue.textContent = offsetSpacingSlider.value;
273
+ updateCurves();
274
+ });
275
+
276
+ baseOffsetXSlider.addEventListener('input', () => {
277
+ baseOffsetXValue.textContent = baseOffsetXSlider.value;
278
+ updateCurves();
279
+ });
280
+
281
+ baseOffsetYSlider.addEventListener('input', () => {
282
+ baseOffsetYValue.textContent = baseOffsetYSlider.value;
283
+ updateCurves();
284
+ });
285
+
286
+ deltaOffsetXSlider.addEventListener('input', () => {
287
+ deltaOffsetXValue.textContent = deltaOffsetXSlider.value;
288
+ updateCurves();
289
+ });
290
+
291
+ deltaOffsetYSlider.addEventListener('input', () => {
292
+ deltaOffsetYValue.textContent = deltaOffsetYSlider.value;
293
+ updateCurves();
294
+ });
295
+
296
+ deltaRotationDegSlider.addEventListener('input', () => {
297
+ deltaRotationDegValue.textContent = deltaRotationDegSlider.value;
298
+ updateCurves();
299
+ });
300
+
301
+ // Button event listeners
302
+ clearPointsBtn.addEventListener('click', () => {
303
+ points = [];
304
+ updateCurves();
305
+ showStatus("All points cleared");
306
+ });
307
+
308
+ resetViewBtn.addEventListener('click', () => {
309
+ viewOffset = { x: 0, y: 0 };
310
+ viewScale = 1;
311
+ updateCurves();
312
+ showStatus("View reset");
313
+ });
314
+
315
+ exportSVGBtn.addEventListener('click', () => {
316
+ showStatus("SVG export would be implemented in Processing");
317
+ });
318
+
319
+ savePNGBtn.addEventListener('click', () => {
320
+ showStatus("PNG save would be implemented in Processing");
321
+ });
322
+
323
+ // Show status message
324
+ function showStatus(message, duration = 3000) {
325
+ statusText.textContent = message;
326
+ statusMessage.classList.remove('hidden');
327
+
328
+ if (duration > 0) {
329
+ setTimeout(() => {
330
+ statusMessage.classList.add('hidden');
331
+ }, duration);
332
+ }
333
+ }
334
+
335
+ // Initialize p5.js sketch
336
+ const sketch = (p) => {
337
+ p.setup = () => {
338
+ const canvas = p.createCanvas(p.windowWidth - 320, p.windowHeight - 60);
339
+ canvas.parent('processing-canvas');
340
+ p.background(240);
341
+ p.noFill();
342
+ p.stroke(0);
343
+ p.strokeWeight(1);
344
+
345
+ // Handle mouse events
346
+ canvas.mousePressed((e) => {
347
+ if (e.button === 0) { // Left click
348
+ handleLeftClick(p.mouseX, p.mouseY);
349
+ }
350
+ });
351
+
352
+ canvas.mouseReleased(() => {
353
+ selectedPoint = null;
354
+ });
355
+
356
+ canvas.mouseMoved(() => {
357
+ if (!isDragging) {
358
+ checkPointHover(p.mouseX, p.mouseY);
359
+ }
360
+ });
361
+
362
+ canvas.mouseDragged((e) => {
363
+ if (e.button === 0 && selectedPoint) {
364
+ // Move the selected point
365
+ selectedPoint.x = (p.mouseX - viewOffset.x) / viewScale;
366
+ selectedPoint.y = (p.mouseY - viewOffset.y) / viewScale;
367
+ updateCurves();
368
+ } else if (e.button === 0) {
369
+ // Pan the view
370
+ if (!isDragging) {
371
+ isDragging = true;
372
+ lastMouseX = p.mouseX;
373
+ lastMouseY = p.mouseY;
374
+ } else {
375
+ const dx = p.mouseX - lastMouseX;
376
+ const dy = p.mouseY - lastMouseY;
377
+ viewOffset.x += dx;
378
+ viewOffset.y += dy;
379
+ lastMouseX = p.mouseX;
380
+ lastMouseY = p.mouseY;
381
+ p.redraw();
382
+ }
383
+ }
384
+ });
385
+
386
+ canvas.mouseWheel((e) => {
387
+ // Zoom the view
388
+ const zoomFactor = e.delta > 0 ? 0.9 : 1.1;
389
+ const mouseX = p.mouseX;
390
+ const mouseY = p.mouseY;
391
+
392
+ // Calculate the mouse position in world coordinates
393
+ const worldX = (mouseX - viewOffset.x) / viewScale;
394
+ const worldY = (mouseY - viewOffset.y) / viewScale;
395
+
396
+ // Apply zoom
397
+ viewScale *= zoomFactor;
398
+
399
+ // Adjust the offset so the zoom is centered on the mouse
400
+ viewOffset.x = mouseX - worldX * viewScale;
401
+ viewOffset.y = mouseY - worldY * viewScale;
402
+
403
+ p.redraw();
404
+ return false;
405
+ });
406
+
407
+ // Handle right click (context menu)
408
+ canvas.elt.addEventListener('contextmenu', (e) => {
409
+ e.preventDefault();
410
+ handleRightClick(p.mouseX, p.mouseY);
411
+ return false;
412
+ });
413
+
414
+ // Handle keyboard events
415
+ document.addEventListener('keydown', (e) => {
416
+ if (e.key === 'c' || e.key === 'C') {
417
+ points = [];
418
+ updateCurves();
419
+ showStatus("All points cleared");
420
+ } else if ((e.key === 'Backspace' || e.key === 'Delete') && selectedPoint) {
421
+ points = points.filter(p => p !== selectedPoint);
422
+ selectedPoint = null;
423
+ updateCurves();
424
+ showStatus("Point deleted");
425
+ }
426
+ });
427
+ };
428
+
429
+ p.draw = () => {
430
+ p.background(240);
431
+ p.push();
432
+ p.translate(viewOffset.x, viewOffset.y);
433
+ p.scale(viewScale);
434
+
435
+ // Draw the bands
436
+ drawBands(p);
437
+
438
+ // Draw the base curve
439
+ if (baseCurve.length > 0) {
440
+ p.stroke(0, 0, 255);
441
+ p.strokeWeight(2 / viewScale);
442
+ p.noFill();
443
+ p.beginShape();
444
+ for (const pt of baseCurve) {
445
+ p.vertex(pt.x, pt.y);
446
+ }
447
+ p.endShape();
448
+ }
449
+
450
+ // Draw the control points
451
+ for (const pt of points) {
452
+ if (pt === selectedPoint) {
453
+ p.fill(255, 0, 0);
454
+ p.stroke(255, 0, 0);
455
+ } else if (pt.hovered) {
456
+ p.fill(255, 165, 0);
457
+ p.stroke(255, 165, 0);
458
+ } else {
459
+ p.fill(0);
460
+ p.stroke(0);
461
+ }
462
+ p.strokeWeight(1 / viewScale);
463
+ p.ellipse(pt.x, pt.y, 8 / viewScale, 8 / viewScale);
464
+ }
465
+
466
+ p.pop();
467
+
468
+ // Reset dragging state when mouse is released
469
+ if (!p.mouseIsPressed) {
470
+ isDragging = false;
471
+ }
472
+ };
473
+
474
+ p.windowResized = () => {
475
+ p.resizeCanvas(p.windowWidth - 320, p.windowHeight - 60);
476
+ };
477
+ };
478
+
479
+ new p5(sketch);
480
+
481
+ // Handle left click to add points
482
+ function handleLeftClick(mouseX, mouseY) {
483
+ // Convert to world coordinates
484
+ const worldX = (mouseX - viewOffset.x) / viewScale;
485
+ const worldY = (mouseY - viewOffset.y) / viewScale;
486
+
487
+ // Check if we're clicking near an existing point
488
+ let clickedOnPoint = false;
489
+ for (const pt of points) {
490
+ const d = Math.sqrt((pt.x - worldX) ** 2 + (pt.y - worldY) ** 2);
491
+ if (d < 10 / viewScale) {
492
+ selectedPoint = pt;
493
+ clickedOnPoint = true;
494
+ break;
495
+ }
496
+ }
497
+
498
+ if (!clickedOnPoint) {
499
+ // Add a new point
500
+ points.push({ x: worldX, y: worldY, hovered: false });
501
+ updateCurves();
502
+ showStatus("Point added");
503
+ }
504
+ }
505
+
506
+ // Handle right click to delete points
507
+ function handleRightClick(mouseX, mouseY) {
508
+ // Convert to world coordinates
509
+ const worldX = (mouseX - viewOffset.x) / viewScale;
510
+ const worldY = (mouseY - viewOffset.y) / viewScale;
511
+
512
+ // Find the closest point
513
+ let closestPoint = null;
514
+ let minDist = Infinity;
515
+
516
+ for (const pt of points) {
517
+ const d = Math.sqrt((pt.x - worldX) ** 2 + (pt.y - worldY) ** 2);
518
+ if (d < 10 / viewScale && d < minDist) {
519
+ closestPoint = pt;
520
+ minDist = d;
521
+ }
522
+ }
523
+
524
+ if (closestPoint) {
525
+ points = points.filter(p => p !== closestPoint);
526
+ if (selectedPoint === closestPoint) {
527
+ selectedPoint = null;
528
+ }
529
+ updateCurves();
530
+ showStatus("Point deleted");
531
+ }
532
+ }
533
+
534
+ // Check if mouse is hovering over a point
535
+ function checkPointHover(mouseX, mouseY) {
536
+ // Convert to world coordinates
537
+ const worldX = (mouseX - viewOffset.x) / viewScale;
538
+ const worldY = (mouseY - viewOffset.y) / viewScale;
539
+
540
+ let anyHovered = false;
541
+
542
+ for (const pt of points) {
543
+ const d = Math.sqrt((pt.x - worldX) ** 2 + (pt.y - worldY) ** 2);
544
+ pt.hovered = d < 10 / viewScale;
545
+ if (pt.hovered) anyHovered = true;
546
+ }
547
+
548
+ document.body.style.cursor = anyHovered ? 'pointer' : 'default';
549
+ }
550
+
551
+ // Update the curves and bands based on current points and settings
552
+ function updateCurves() {
553
+ if (points.length < 2) {
554
+ baseCurve = [];
555
+ offsetCurves = [];
556
+ bands = [];
557
+ return;
558
+ }
559
+
560
+ // Compute the base curve as a Catmull-Rom spline
561
+ computeBaseCurve();
562
+
563
+ // Compute offset curves
564
+ computeOffsetCurves();
565
+
566
+ // Compute bands
567
+ computeBands();
568
+ }
569
+
570
+ // Compute the base curve using Catmull-Rom spline
571
+ function computeBaseCurve() {
572
+ baseCurve = [];
573
+
574
+ if (points.length < 2) return;
575
+
576
+ // Number of samples per segment
577
+ const samplesPerSegment = 50;
578
+
579
+ for (let i = 0; i < points.length - 1; i++) {
580
+ const p0 = i === 0 ? points[0] : points[i - 1];
581
+ const p1 = points[i];
582
+ const p2 = points[i + 1];
583
+ const p3 = i === points.length - 2 ? points[points.length - 1] : points[i + 2];
584
+
585
+ for (let j = 0; j < samplesPerSegment; j++) {
586
+ const t = j / samplesPerSegment;
587
+ const point = catmullRom(t, p0, p1, p2, p3);
588
+ baseCurve.push(point);
589
+ }
590
+ }
591
+
592
+ // Add the last point
593
+ baseCurve.push({ x: points[points.length - 1].x, y: points[points.length - 1].y });
594
+ }
595
+
596
+ // Catmull-Rom interpolation
597
+ function catmullRom(t, p0, p1, p2, p3) {
598
+ // tension = 0.5 for Catmull-Rom
599
+ const tension = 0.5;
600
+
601
+ const t2 = t * t;
602
+ const t3 = t2 * t;
603
+
604
+ // The matrix coefficients
605
+ const m0 = (-tension * t3) + (2 * tension * t2) + (-tension * t);
606
+ const m1 = ((2 - tension) * t3) + (tension - 3) * t2 + 1;
607
+ const m2 = (tension - 2) * t3 + (3 - 2 * tension) * t2 + tension * t;
608
+ const m3 = tension * t3 - tension * t2;
609
+
610
+ return {
611
+ x: m0 * p0.x + m1 * p1.x + m2 * p2.x + m3 * p3.x,
612
+ y: m0 * p0.y + m1 * p1.y + m2 * p2.y + m3 * p3.y
613
+ };
614
+ }
615
+
616
+ // Compute offset curves
617
+ function computeOffsetCurves() {
618
+ offsetCurves = [];
619
+
620
+ if (baseCurve.length < 2) return;
621
+
622
+ const numCurves = parseInt(numCurvesSlider.value);
623
+ const offsetSpacing = parseInt(offsetSpacingSlider.value);
624
+
625
+ // First curve is the base curve
626
+ offsetCurves.push([...baseCurve]);
627
+
628
+ // Compute normals for the base curve
629
+ const normals = computeNormals(baseCurve);
630
+
631
+ // Create offset curves
632
+ for (let k = 1; k < numCurves; k++) {
633
+ const offsetDistance = k * offsetSpacing;
634
+ const offsetCurve = [];
635
+
636
+ for (let i = 0; i < baseCurve.length; i++) {
637
+ const pt = baseCurve[i];
638
+ const normal = normals[i];
639
+
640
+ offsetCurve.push({
641
+ x: pt.x + normal.x * offsetDistance,
642
+ y: pt.y + normal.y * offsetDistance
643
+ });
644
+ }
645
+
646
+ offsetCurves.push(offsetCurve);
647
+ }
648
+ }
649
+
650
+ // Compute normals for a polyline
651
+ function computeNormals(polyline) {
652
+ const normals = [];
653
+
654
+ for (let i = 0; i < polyline.length; i++) {
655
+ let normal;
656
+
657
+ if (i === 0) {
658
+ // First point - use next segment
659
+ const dx = polyline[i + 1].x - polyline[i].x;
660
+ const dy = polyline[i + 1].y - polyline[i].y;
661
+ normal = normalize({ x: -dy, y: dx });
662
+ } else if (i === polyline.length - 1) {
663
+ // Last point - use previous segment
664
+ const dx = polyline[i].x - polyline[i - 1].x;
665
+ const dy = polyline[i].y - polyline[i - 1].y;
666
+ normal = normalize({ x: -dy, y: dx });
667
+ } else {
668
+ // Middle point - average adjacent segments
669
+ const dx1 = polyline[i].x - polyline[i - 1].x;
670
+ const dy1 = polyline[i].y - polyline[i - 1].y;
671
+ const n1 = normalize({ x: -dy1, y: dx1 });
672
+
673
+ const dx2 = polyline[i + 1].x - polyline[i].x;
674
+ const dy2 = polyline[i + 1].y - polyline[i].y;
675
+ const n2 = normalize({ x: -dy2, y: dx2 });
676
+
677
+ normal = normalize({
678
+ x: (n1.x + n2.x) / 2,
679
+ y: (n1.y + n2.y) / 2
680
+ });
681
+ }
682
+
683
+ normals.push(normal);
684
+ }
685
+
686
+ return normals;
687
+ }
688
+
689
+ // Normalize a vector
690
+ function normalize(v) {
691
+ const length = Math.sqrt(v.x * v.x + v.y * v.y);
692
+ if (length === 0) return { x: 0, y: 0 };
693
+ return { x: v.x / length, y: v.y / length };
694
+ }
695
+
696
+ // Compute bands between offset curves
697
+ function computeBands() {
698
+ bands = [];
699
+
700
+ if (offsetCurves.length < 2) return;
701
+
702
+ const baseOffsetX = parseInt(baseOffsetXSlider.value);
703
+ const baseOffsetY = parseInt(baseOffsetYSlider.value);
704
+ const deltaOffsetX = parseInt(deltaOffsetXSlider.value);
705
+ const deltaOffsetY = parseInt(deltaOffsetYSlider.value);
706
+ const deltaRotationDeg = parseInt(deltaRotationDegSlider.value);
707
+ const anchorType = document.querySelector('input[name="anchorType"]:checked').value;
708
+
709
+ for (let i = 0; i < offsetCurves.length - 1; i++) {
710
+ const band = {
711
+ polygon: [],
712
+ centroid: { x: 0, y: 0 }
713
+ };
714
+
715
+ // Create polygon by combining curve i (forward) and curve i+1 (reverse)
716
+ for (const pt of offsetCurves[i]) {
717
+ band.polygon.push({ x: pt.x, y: pt.y });
718
+ }
719
+
720
+ for (let j = offsetCurves[i + 1].length - 1; j >= 0; j--) {
721
+ band.polygon.push({
722
+ x: offsetCurves[i + 1][j].x,
723
+ y: offsetCurves[i + 1][j].y
724
+ });
725
+ }
726
+
727
+ // Close the polygon
728
+ band.polygon.push({
729
+ x: offsetCurves[i][0].x,
730
+ y: offsetCurves[i][0].y
731
+ });
732
+
733
+ // Compute centroid or bounds center
734
+ band.centroid = computeCentroid(band.polygon);
735
+
736
+ // Apply transforms
737
+ const offsetX = baseOffsetX + i * deltaOffsetX;
738
+ const offsetY = baseOffsetY + i * deltaOffsetY;
739
+ const rotation = i * deltaRotationDeg * Math.PI / 180;
740
+
741
+ for (const pt of band.polygon) {
742
+ // Translate to origin, rotate, then translate back
743
+ const translatedX = pt.x - band.centroid.x;
744
+ const translatedY = pt.y - band.centroid.y;
745
+
746
+ const rotatedX = translatedX * Math.cos(rotation) - translatedY * Math.sin(rotation);
747
+ const rotatedY = translatedX * Math.sin(rotation) + translatedY * Math.cos(rotation);
748
+
749
+ pt.x = rotatedX + band.centroid.x + offsetX;
750
+ pt.y = rotatedY + band.centroid.y + offsetY;
751
+ }
752
+
753
+ // Update centroid after transform
754
+ band.centroid.x += offsetX;
755
+ band.centroid.y += offsetY;
756
+
757
+ bands.push(band);
758
+ }
759
+ }
760
+
761
+ // Compute centroid of a polygon
762
+ function computeCentroid(polygon) {
763
+ let area = 0;
764
+ let cx = 0;
765
+ let cy = 0;
766
+
767
+ for (let i = 0; i < polygon.length - 1; i++) {
768
+ const x0 = polygon[i].x;
769
+ const y0 = polygon[i].y;
770
+ const x1 = polygon[i + 1].x;
771
+ const y1 = polygon[i + 1].y;
772
+
773
+ const a = x0 * y1 - x1 * y0;
774
+ area += a;
775
+ cx += (x0 + x1) * a;
776
+ cy += (y0 + y1) * a;
777
+ }
778
+
779
+ area /= 2;
780
+ const centroid = {
781
+ x: cx / (6 * area),
782
+ y: cy / (6 * area)
783
+ };
784
+
785
+ return centroid;
786
+ }
787
+
788
+ // Draw all bands
789
+ function drawBands(p) {
790
+ if (bands.length === 0) return;
791
+
792
+ // Draw from inner to outer
793
+ for (let i = bands.length - 1; i >= 0; i--) {
794
+ const band = bands[i];
795
+
796
+ // Alternate colors
797
+ const colorIndex = i % 5;
798
+
799
+ p.fill(0, 0, 255, 30);
800
+ p.stroke(0, 0, 255);
801
+ p.strokeWeight(1 / viewScale);
802
+
803
+ p.beginShape();
804
+ for (const pt of band.polygon) {
805
+ p.vertex(pt.x, pt.y);
806
+ }
807
+ p.endShape(p.CLOSE);
808
+
809
+ // Draw centroid
810
+ p.fill(255, 0, 0);
811
+ p.noStroke();
812
+ p.ellipse(band.centroid.x, band.centroid.y, 5 / viewScale, 5 / viewScale);
813
+ }
814
+ }
815
+ </script>
816
+ <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=MarkArtist21/shapefinder" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
817
+ </html>