Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions dist/mixins/group_transform.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as THREE from "three";
/**
* Mixin providing rigid group transforms (translate/rotate about a shared centroid pivot) for
* InteractiveStructureEditorMixin's multi-atom selection: a 2+ atom selection drags or rotates
* together as one commit, matching the old editor's MultipleSelectionControls pivot-group
* behavior (decision D-4). Composed alongside InteractiveStructureEditorMixin, which owns the
* TransformControls lifecycle listeners and the direct-drag pointer handlers that call into this
* mixin's helpers, and shares its `this` - selectedMeshes_, transformControls_,
* commitMovedAtoms_, etc. all live on the base mixin.
*/
export declare const GroupTransformMixin: (superclass: any) => {
new (config: any): {
[x: string]: any;
selectionPivot_: THREE.Object3D | null;
groupDragStartPositions_: Map<THREE.Mesh, THREE.Vector3> | null;
isDraggingGroup_: boolean;
/**
* Repositions the group-transform pivot at the current selection's centroid and attaches
* the gizmo to it.
*/
attachPivotToSelection_(): void;
/**
* Shared by attachPivotToSelection_ and the group direct-drag move handler, which must
* keep the pivot (and therefore the visible gizmo) tracking the group's centroid as it
* moves - without this, the gizmo would stay frozen at the pre-drag centroid for the
* whole gesture and only jump to the correct spot once the post-commit rebuild reattaches
* it.
*/
computeCentroid_(meshes: THREE.Mesh[]): THREE.Vector3;
/**
* Snapshots every selected atom's current position, keyed by mesh, so a group gizmo or
* direct drag can compute each frame's delta against a fixed start rather than the
* previous frame (which would accumulate rounding error) and so Esc can revert cleanly.
*/
captureGroupDragStartPositions_(): void;
/**
* Called on every TransformControls "change" event while the group pivot is the object
* being dragged: propagates the pivot's live translate/rotate delta to every selected
* atom so the group moves rigidly together during the gesture, not just once on commit.
* Translate mode applies the pivot's position delta to each atom; rotate mode leaves the
* pivot's position fixed at the centroid and instead rotates each atom's offset from it
* by the pivot's quaternion, so the group rotates rigidly about its shared centroid.
*/
handleGroupPivotChange_(): void;
/**
* Commits a completed group gizmo drag (translate or rotate) as a single delta/commit -
* called from the TransformControls "mouseUp" listener once it's determined the pivot
* (not a lone atom) was the dragged object and it actually moved/rotated.
*/
commitGroupGizmoDrag_(wasRotate: boolean): void;
/**
* If a group direct-drag (not the gizmo) is in progress, moves every selected atom by
* the same delta as the pressed atom and keeps the pivot tracking the live centroid.
* Returns whether it applied - the caller falls back to moving just the pending atom
* when this returns false, matching a lone-atom (or gizmo) drag.
*/
applyGroupDragDelta_(newPosition: THREE.Vector3): boolean;
};
[x: string]: any;
};
146 changes: 146 additions & 0 deletions dist/mixins/group_transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import * as THREE from "three";
/**
* Mixin providing rigid group transforms (translate/rotate about a shared centroid pivot) for
* InteractiveStructureEditorMixin's multi-atom selection: a 2+ atom selection drags or rotates
* together as one commit, matching the old editor's MultipleSelectionControls pivot-group
* behavior (decision D-4). Composed alongside InteractiveStructureEditorMixin, which owns the
* TransformControls lifecycle listeners and the direct-drag pointer handlers that call into this
* mixin's helpers, and shares its `this` - selectedMeshes_, transformControls_,
* commitMovedAtoms_, etc. all live on the base mixin.
*/
export const GroupTransformMixin = (superclass) => class extends superclass {
constructor(config) {
super(config);
this.selectionPivot_ = null;
this.groupDragStartPositions_ = null;
this.isDraggingGroup_ = false;
}
/**
* Repositions the group-transform pivot at the current selection's centroid and attaches
* the gizmo to it.
*/
attachPivotToSelection_() {
if (!this.selectionPivot_ || this.selectedMeshes_.length === 0)
return;
this.selectionPivot_.position.copy(this.computeCentroid_(this.selectedMeshes_));
// A fresh attach always starts unrotated, even if a previous group drag left the
// pivot's quaternion non-identity for any reason (the mouseUp handler already resets
// it after every rotate commit - this is a defensive backstop, not the primary path).
this.selectionPivot_.quaternion.identity();
if (this.transformControls_)
this.transformControls_.attach(this.selectionPivot_);
}
/**
* Shared by attachPivotToSelection_ and the group direct-drag move handler, which must
* keep the pivot (and therefore the visible gizmo) tracking the group's centroid as it
* moves - without this, the gizmo would stay frozen at the pre-drag centroid for the
* whole gesture and only jump to the correct spot once the post-commit rebuild reattaches
* it.
*/
// eslint-disable-next-line class-methods-use-this
computeCentroid_(meshes) {
const centroid = new THREE.Vector3();
meshes.forEach((mesh) => centroid.add(mesh.position));
return centroid.divideScalar(meshes.length);
}
/**
* Snapshots every selected atom's current position, keyed by mesh, so a group gizmo or
* direct drag can compute each frame's delta against a fixed start rather than the
* previous frame (which would accumulate rounding error) and so Esc can revert cleanly.
*/
captureGroupDragStartPositions_() {
this.groupDragStartPositions_ = new Map(this.selectedMeshes_.map((mesh) => [mesh, mesh.position.clone()]));
}
/**
* Called on every TransformControls "change" event while the group pivot is the object
* being dragged: propagates the pivot's live translate/rotate delta to every selected
* atom so the group moves rigidly together during the gesture, not just once on commit.
* Translate mode applies the pivot's position delta to each atom; rotate mode leaves the
* pivot's position fixed at the centroid and instead rotates each atom's offset from it
* by the pivot's quaternion, so the group rotates rigidly about its shared centroid.
*/
handleGroupPivotChange_() {
var _a;
// Captured as a local so its non-null-ness (checked right below) narrows reliably -
// transformControls_/transformDragStartPosition_ are declared on a sibling mixin
// class (InteractiveStructureEditorMixin), not this one, so TS sees them as `any`
// here and won't propagate narrowing from a compound guard into selectionPivot_.
const pivot = this.selectionPivot_;
if (!pivot ||
!((_a = this.transformControls_) === null || _a === void 0 ? void 0 : _a.dragging) ||
this.transformControls_.object !== pivot ||
!this.groupDragStartPositions_ ||
!this.transformDragStartPosition_) {
return;
}
if (this.transformControls_.mode === "rotate") {
const pivotCenter = this.transformDragStartPosition_;
const rotation = pivot.quaternion;
this.selectedMeshes_.forEach((mesh) => {
var _a;
const start = (_a = this.groupDragStartPositions_) === null || _a === void 0 ? void 0 : _a.get(mesh);
if (!start)
return;
const offset = start.clone().sub(pivotCenter).applyQuaternion(rotation);
mesh.position.copy(pivotCenter.clone().add(offset));
});
}
else {
const delta = pivot.position.clone().sub(this.transformDragStartPosition_);
this.selectedMeshes_.forEach((mesh) => {
var _a;
const start = (_a = this.groupDragStartPositions_) === null || _a === void 0 ? void 0 : _a.get(mesh);
if (start)
mesh.position.copy(start.clone().add(delta));
});
}
this.syncHighlightPoolTo_(this.selectedMeshes_);
}
/**
* Commits a completed group gizmo drag (translate or rotate) as a single delta/commit -
* called from the TransformControls "mouseUp" listener once it's determined the pivot
* (not a lone atom) was the dragged object and it actually moved/rotated.
*/
commitGroupGizmoDrag_(wasRotate) {
var _a;
this.commitMovedAtoms_(this.selectedMeshes_.map((mesh) => ({
atomicIndex: mesh.userData.atomicIndex,
position: mesh.position.clone(),
})), "gizmo");
if (wasRotate) {
// The pivot's rotation is relative to each drag, not cumulative across drags -
// the atoms' new positions already encode the rotation, so reset it to identity
// for the next attach/drag. Optional-chained defensively: this is only ever
// called once the caller has confirmed selectionPivot_ was the dragged object,
// but that guard lives in the caller, not in this method's own type narrowing.
(_a = this.selectionPivot_) === null || _a === void 0 ? void 0 : _a.quaternion.identity();
}
}
/**
* If a group direct-drag (not the gizmo) is in progress, moves every selected atom by
* the same delta as the pressed atom and keeps the pivot tracking the live centroid.
* Returns whether it applied - the caller falls back to moving just the pending atom
* when this returns false, matching a lone-atom (or gizmo) drag.
*/
applyGroupDragDelta_(newPosition) {
if (!this.isDraggingGroup_ ||
!this.groupDragStartPositions_ ||
!this.pendingDragAtom_) {
return false;
}
const draggedStart = this.groupDragStartPositions_.get(this.pendingDragAtom_);
if (draggedStart) {
const delta = newPosition.clone().sub(draggedStart);
this.selectedMeshes_.forEach((mesh) => {
var _a;
const start = (_a = this.groupDragStartPositions_) === null || _a === void 0 ? void 0 : _a.get(mesh);
if (start)
mesh.position.copy(start.clone().add(delta));
});
if (this.selectionPivot_) {
this.selectionPivot_.position.copy(this.computeCentroid_(this.selectedMeshes_));
}
}
return true;
}
};
7 changes: 7 additions & 0 deletions dist/mixins/interactive_editor_constants.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Pixel distance a pointer must travel before a press-and-move gesture commits to a drag
* (atom drag or marquee rubber-band) instead of registering as a plain click on release.
* Shared between InteractiveStructureEditorMixin (atom drag) and MarqueeSelectionMixin
* (marquee activation), which otherwise have no dependency on each other.
*/
export declare const DRAG_THRESHOLD_PX = 5;
7 changes: 7 additions & 0 deletions dist/mixins/interactive_editor_constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Pixel distance a pointer must travel before a press-and-move gesture commits to a drag
* (atom drag or marquee rubber-band) instead of registering as a plain click on release.
* Shared between InteractiveStructureEditorMixin (atom drag) and MarqueeSelectionMixin
* (marquee activation), which otherwise have no dependency on each other.
*/
export const DRAG_THRESHOLD_PX = 5;
52 changes: 0 additions & 52 deletions dist/mixins/interactive_structure_editor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,20 @@ export declare const InteractiveStructureEditorMixin: (superclass: any) => {
hoveredMesh_: THREE.Mesh | null;
selectionHighlightPool_: THREE.Mesh[];
hoverHighlightMesh_: THREE.Mesh | null;
selectionPivot_: THREE.Object3D | null;
isEditModeEnabled_: boolean;
pointerDownPosition_: {
x: number;
y: number;
} | null;
pendingDragAtom_: THREE.Mesh | null;
isDraggingAtom_: boolean;
isDraggingGroup_: boolean;
groupDragStartPositions_: Map<THREE.Mesh, THREE.Vector3> | null;
dragPlane_: THREE.Plane | null;
dragOffset_: THREE.Vector3 | null;
dragStartPosition_: THREE.Vector3 | null;
activePointerId_: number | null;
orbitControlsEnabledBeforeDrag_: boolean;
orbitControlsDefaultMouseButtons_: any | null;
lastSelectedAtomicIndices_: number[] | null;
marqueeStartScreen_: {
x: number;
y: number;
} | null;
isMarqueeSelecting_: boolean;
marqueeOverlayElement_: HTMLDivElement | null;
marqueeModifierAdd_: boolean;
marqueeModifierToggle_: boolean;
handlePointerDownCapture_: ((event: PointerEvent) => void) | null;
handlePointerMoveCapture_: ((event: PointerEvent) => void) | null;
handlePointerUpCapture_: ((event: PointerEvent) => void) | null;
Expand Down Expand Up @@ -90,34 +79,6 @@ export declare const InteractiveStructureEditorMixin: (superclass: any) => {
* the hot path of an in-progress drag.
*/
updateHoverFromPointer_(event: PointerEvent): void;
/**
* Grows the marquee's screen-space rectangle as the pointer moves, activating it (and
* showing the overlay) only once the drag exceeds the same click-vs-drag threshold used
* for atom dragging, so a plain click on empty space still falls through to
* finishMarqueeSelection_'s deselect path instead of drawing a zero-size box.
*/
updateMarqueeState_(event: PointerEvent): void;
showMarqueeOverlay_(): void;
updateMarqueeOverlay_(currentX: number, currentY: number): void;
hideMarqueeOverlay_(): void;
/**
* Returns every atom whose projected screen position falls within the given
* (unordered) screen-space rectangle. Atoms behind the camera (or beyond the far
* plane) are excluded via the projected z check.
*/
getAtomsInScreenRect_(rect: {
left: number;
right: number;
top: number;
bottom: number;
}): THREE.Mesh[];
/**
* Resolves a completed (or abandoned) marquee gesture. A release before crossing the
* drag threshold is just a plain click on empty space, so it falls through to the
* existing handlePointerDown click-to-deselect/select path rather than selecting an
* empty rectangle.
*/
finishMarqueeSelection_(event: PointerEvent): void;
/**
* Selects the pending atom and sets up the camera-facing drag plane through its current
* position, offset so the atom doesn't jump to snap its center to the cursor. If the
Expand Down Expand Up @@ -177,19 +138,6 @@ export declare const InteractiveStructureEditorMixin: (superclass: any) => {
* (see beginAtomDrag_/the TransformControls "change"/"mouseUp" listeners).
*/
setSelectedAtomMeshes(meshes: THREE.Mesh[]): void;
/**
* Repositions the group-transform pivot at the current selection's centroid and attaches
* the gizmo to it.
*/
attachPivotToSelection_(): void;
/**
* Shared by attachPivotToSelection_ and the group direct-drag move handler, which must
* keep the pivot (and therefore the visible gizmo) tracking the group's centroid as it
* moves - without this, the gizmo would stay frozen at the pre-drag centroid for the
* whole gesture and only jump to the correct spot once the post-commit rebuild reattaches
* it.
*/
computeCentroid_(meshes: THREE.Mesh[]): THREE.Vector3;
/**
* Clears the current atom selection, removes its highlight(s), and detaches the gizmo.
* @param {boolean} forgetLastSelection - Also forget the remembered indices used to
Expand Down
Loading
Loading