Skip to content

Commit 581ae55

Browse files
committed
fix plane drag scaling
1 parent e1eb1a6 commit 581ae55

1 file changed

Lines changed: 100 additions & 30 deletions

File tree

src/void/interact.js

Lines changed: 100 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ const interact = {
1212
selectedPlanes: new Set(), // Set of selected planes (multi-select)
1313
hoveredPlane: null,
1414
draggedHandle: null,
15+
draggedPlane: null, // The plane being dragged
16+
dragHandleName: null, // Which handle (corner) is being dragged
1517
dragStartSizes: new Map(), // Map of plane -> initial size for multi-plane drag
1618
planes: [], // List of all interactive planes
1719
upSelectCalled: false, // Track if upSelect was called for this click
20+
wasHandleDrag: false, // Track if we just finished a handle drag
1821

1922
/**
2023
* Initialize interaction system
@@ -50,13 +53,18 @@ const interact = {
5053
});
5154

5255
space.mouse.downSelect((int, event, ints) => {
56+
// Filter for left-click only (button 0)
57+
if (event && event.button !== 0) {
58+
return;
59+
}
5360
if (!int) {
5461
// First call - return objects for raycasting (includes tracking plane)
5562
const objects = this.getInteractiveObjects();
5663
console.log({ downSelect_returning: objects.length });
5764
return objects;
5865
}
5966
// Second call - handle mouse down (for drag operations)
67+
6068
const obj = int?.object;
6169
const handleType = obj?.userData?.handleType;
6270

@@ -71,23 +79,37 @@ const interact = {
7179

7280
space.mouse.upSelect((int, event, ints) => {
7381
console.log({ upSelect: { int: !!int, isNull: int === null, isUndefined: int === undefined, event: !!event, draggedHandle: !!this.draggedHandle } });
82+
// Filter for left-click only (button 0)
83+
if (event && event.button !== 0) {
84+
return;
85+
}
7486
if (!int && int !== null) {
7587
// First call (no args) - return objects for raycasting
7688
console.log({ upSelect_returning: this.getInteractiveObjects().length });
7789
this.upSelectCalled = false; // Reset flag
90+
this.wasHandleDrag = false; // Reset handle drag flag
7891
return this.getInteractiveObjects();
7992
}
8093
// Second call - handle the selection on mouse UP
8194
this.upSelectCalled = true; // Mark that upSelect was called
82-
this.handleMouseUp(int, event, ints);
95+
96+
// Don't handle selection if we just finished a handle drag
97+
if (!this.wasHandleDrag) {
98+
this.handleMouseUp(int, event, ints);
99+
}
100+
this.wasHandleDrag = false; // Reset for next interaction
83101
});
84102

85103
// Fallback: use regular mouseUp to catch clicks that space.js misses
86104
space.mouse.up((event, ints) => {
87105
console.log({ mouseUp_fallback: { event: !!event, ints: ints?.length, draggedHandle: !!this.draggedHandle, upSelectCalled: this.upSelectCalled } });
88-
// Only handle if upSelect didn't fire
106+
// Filter for left-click only (button 0)
107+
if (event && event.button !== 0) {
108+
return;
109+
}
110+
// Only handle if upSelect didn't fire and we're not in a handle drag
89111
// This catches the case where mouse moved slightly so upSelect was skipped
90-
if (!this.upSelectCalled && !this.draggedHandle && ints && ints.length > 0) {
112+
if (!this.upSelectCalled && !this.draggedHandle && !this.wasHandleDrag && ints && ints.length > 0) {
91113
console.log({ mouseUp_fallback_handling: 'yes, upSelect was never called' });
92114
this.handleMouseUp(ints[0], event, ints);
93115
}
@@ -120,13 +142,16 @@ const interact = {
120142
// If isDone and we have a handle, it was a drag - clean up
121143
if (isDone && this.draggedHandle) {
122144
console.log({ onDrag_done_cleaning_up: true });
145+
this.wasHandleDrag = true; // Mark that we just finished a handle drag
123146
this.draggedHandle = null;
147+
this.draggedPlane = null;
148+
this.dragHandleName = null;
124149
this.dragStartSizes.clear();
125150
return;
126151
}
127152

128153
// If isDone but NO handle and offset is tiny, treat as click
129-
if (isDone && !this.draggedHandle) {
154+
if (isDone && !this.draggedHandle && offset) {
130155
const offsetMag = Math.sqrt(offset.x * offset.x + offset.y * offset.y);
131156
console.log({ onDrag_done_no_handle: { offsetMag } });
132157
if (offsetMag < 5) { // Less than 5 pixels = click
@@ -291,6 +316,8 @@ const interact = {
291316
if (this.draggedHandle) {
292317
console.log({ handle_drag_end: this.draggedHandle.userData.handleName });
293318
this.draggedHandle = null;
319+
this.draggedPlane = null;
320+
this.dragHandleName = null;
294321
this.dragStartSizes.clear();
295322
return;
296323
}
@@ -328,6 +355,10 @@ const interact = {
328355
if (!plane) return;
329356

330357
this.draggedHandle = handle;
358+
this.draggedPlane = plane;
359+
360+
// Store which handle this is (corner name)
361+
this.dragHandleName = handle.userData.handleName;
331362

332363
// Store initial sizes for ALL selected planes
333364
this.dragStartSizes.clear();
@@ -351,45 +382,84 @@ const interact = {
351382
* @param {Array} intersections - raycaster intersections
352383
*/
353384
handleDrag(delta, offset, isDone, intersections) {
354-
if (!this.draggedHandle) return;
385+
if (!this.draggedHandle || !this.draggedPlane) return;
355386
if (isDone) {
356387
console.log({ drag_done: true });
357388
return; // Ignore drag end event
358389
}
359390

360-
const handlePlane = this.draggedHandle.userData.plane;
361-
if (!handlePlane) return;
391+
// Get mouse event from delta object
392+
const event = delta.event;
393+
if (!event) return;
362394

363-
console.log({ drag: { delta, offset } });
395+
// Get space internals for raycasting
396+
const internals = space.internals();
397+
const camera = internals.camera;
398+
const container = internals.container;
399+
400+
// Convert mouse position to NDC and setup raycaster
401+
const rect = container.getBoundingClientRect();
402+
const x = event.clientX - rect.left;
403+
const y = event.clientY - rect.top;
404+
const mouseNDC = new THREE.Vector2(
405+
(x / rect.width) * 2 - 1,
406+
-(y / rect.height) * 2 + 1
407+
);
408+
409+
// Create ray from camera through mouse position
410+
const raycaster = new THREE.Raycaster();
411+
raycaster.setFromCamera(mouseNDC, camera);
412+
413+
// Update world matrices to ensure they're current
414+
this.draggedPlane.group.updateMatrixWorld(true);
415+
416+
// Get plane's world normal - extract Z-axis from world matrix
417+
// The Z-axis of the mesh's orientation is the plane normal
418+
const planeNormal = new THREE.Vector3();
419+
this.draggedPlane.mesh.matrixWorld.extractBasis(
420+
new THREE.Vector3(), // X-axis (discard)
421+
new THREE.Vector3(), // Y-axis (discard)
422+
planeNormal // Z-axis (this is the normal)
423+
);
424+
425+
const planeCenter = new THREE.Vector3();
426+
this.draggedPlane.group.getWorldPosition(planeCenter);
364427

365-
// Use offset magnitude as the resize amount
366-
// offset.x and offset.y represent movement in world space
367-
const dragDistance = Math.sqrt(offset.x * offset.x + offset.y * offset.y);
428+
console.log({
429+
plane: this.draggedPlane.label,
430+
planeNormal: planeNormal.toArray(),
431+
planeCenter: planeCenter.toArray()
432+
});
368433

369-
// Get handle position to determine direction
370-
const handleWorldPos = new THREE.Vector3();
371-
this.draggedHandle.getWorldPosition(handleWorldPos);
372-
const centerWorldPos = new THREE.Vector3();
373-
handlePlane.group.getWorldPosition(centerWorldPos);
434+
// Create THREE.Plane for ray intersection
435+
const intersectPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(planeNormal, planeCenter);
374436

375-
// Calculate which direction the handle is from center
376-
const handleDir = new THREE.Vector3().subVectors(handleWorldPos, centerWorldPos);
377-
handleDir.z = 0; // Ignore Z for size calculation
378-
handleDir.normalize();
437+
// Intersect ray with plane - this is where the handle should be
438+
const newHandlePos = new THREE.Vector3();
439+
raycaster.ray.intersectPlane(intersectPlane, newHandlePos);
379440

380-
// Determine if dragging toward or away from center
381-
const dragDir = new THREE.Vector3(offset.x, offset.y, 0).normalize();
382-
const dot = handleDir.dot(dragDir);
441+
if (!newHandlePos) return;
383442

384-
let sizeChange = dragDistance * 2; // Scale factor for resize
385-
if (dot < 0) {
386-
sizeChange = -sizeChange; // Dragging inward
387-
}
443+
// Calculate distance from plane center to new handle position
444+
const handleVec = new THREE.Vector3().subVectors(newHandlePos, planeCenter);
445+
446+
// Transform to plane's local coordinate system to get X and Y distances
447+
const localHandlePos = this.draggedPlane.group.worldToLocal(newHandlePos.clone());
448+
449+
// Get absolute distances in plane's local X and Y
450+
const absX = Math.abs(localHandlePos.x);
451+
const absY = Math.abs(localHandlePos.y);
452+
453+
// Size is twice the max of X or Y distance (to keep plane square)
454+
const newSize = Math.max(10, Math.max(absX, absY) * 2);
455+
456+
// Apply proportional size change to ALL selected planes
457+
const initialSize = this.dragStartSizes.get(this.draggedPlane);
458+
const sizeRatio = newSize / initialSize;
388459

389-
// Apply size change to ALL selected planes
390460
for (const [plane, startSize] of this.dragStartSizes) {
391-
const newSize = Math.max(10, startSize + sizeChange);
392-
plane.setSize(newSize);
461+
const proportionalSize = Math.max(10, startSize * sizeRatio);
462+
plane.setSize(proportionalSize);
393463
}
394464

395465
// Request refresh to show changes

0 commit comments

Comments
 (0)