Snap to origin and axis behavior for profile starts and segments (#4344)

* Visualize draft point when near axes (only works on XY rn due to quaternion rotation issue)

* Slightly better quaternion rotation

* Actually snap new profiles to the X and Y axis

* Add snapping behavior while dragging

* Fix flickering on non-XY planes

* Add some fixture additions to support click-and-drag tests

* Add new test to verify snapping behavior

* Make the editor test fixture auto-open and close as needed

* All feedback except absolute lines

* Use `lineTo` for lines that have snapped

* Get other existing tests passing after switching to `lineTo` when snapping

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* Re-run CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Frank Noirot
2024-10-31 07:04:38 -07:00
committed by GitHub
parent a8b816a3e2
commit 26e995dc3f
14 changed files with 453 additions and 71 deletions

View File

@ -30,6 +30,7 @@ import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine'
import { getAngle, throttle } from 'lib/utils'
import { Themes } from 'lib/theme'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { orthoScale, perspScale } from './helpers'
type SendType = ReturnType<typeof useModelingContext>['send']
@ -49,6 +50,10 @@ export const RAYCASTABLE_PLANE = 'raycastable-plane'
export const X_AXIS = 'xAxis'
export const Y_AXIS = 'yAxis'
/** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */
export const DRAFT_POINT_GROUP = 'draft-point-group'
/** the THREEjs representation of a "snapped" point that is not yet placed */
export const DRAFT_POINT = 'draft-point'
export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead'
@ -60,6 +65,11 @@ export interface OnMouseEnterLeaveArgs {
selected: Object3D<Object3DEventMap>
dragSelected?: Object3D<Object3DEventMap>
mouseEvent: MouseEvent
/** The intersection of the mouse with the THREEjs raycast plane */
intersectionPoint?: {
twoD?: Vector2
threeD?: Vector3
}
}
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
@ -348,29 +358,42 @@ export class SceneInfra {
window.removeEventListener('resize', this.onWindowResize)
// Dispose of any other resources like geometries, materials, textures
}
getClientSceneScaleFactor(meshOrGroup: Mesh | Group) {
const orthoFactor = orthoScale(this.camControls.camera)
const factor =
(this.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(this.camControls.camera, meshOrGroup)) /
this._baseUnitMultiplier
return factor
}
getPlaneIntersectPoint = (): {
twoD?: Vector2
threeD?: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
} | null => {
// Get the orientations from the camera and mouse position
this.planeRaycaster.setFromCamera(
this.currentMouseVector,
this.camControls.camera
)
// Get the intersection of the ray with the default planes
const planeIntersects = this.planeRaycaster.intersectObjects(
this.scene.children,
true
)
const recastablePlaneIntersect = planeIntersects.find(
if (!planeIntersects.length) return null
// Find the intersection with the raycastable (or sketch) plane
const raycastablePlaneIntersection = planeIntersects.find(
(intersect) => intersect.object.name === RAYCASTABLE_PLANE
)
if (!planeIntersects.length) return null
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
const planePosition = planeIntersects[0].object.position
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
.clone()
.invert()
const intersectPoint = planeIntersects[0].point
if (!raycastablePlaneIntersection)
return { intersection: planeIntersects[0] }
const planePosition = raycastablePlaneIntersection.object.position
const inversePlaneQuaternion =
raycastablePlaneIntersection.object.quaternion.clone().invert()
const intersectPoint = raycastablePlaneIntersection.point
let transformedPoint = intersectPoint.clone()
if (transformedPoint) {
transformedPoint.applyQuaternion(inversePlaneQuaternion)
@ -447,18 +470,26 @@ export class SceneInfra {
if (intersects[0]) {
const firstIntersectObject = intersects[0].object
const planeIntersectPoint = this.getPlaneIntersectPoint()
const intersectionPoint = {
twoD: planeIntersectPoint?.twoD,
threeD: planeIntersectPoint?.threeD,
}
if (this.hoveredObject !== firstIntersectObject) {
const hoveredObj = this.hoveredObject
this.hoveredObject = null
await this.onMouseLeave({
selected: hoveredObj,
mouseEvent: mouseEvent,
intersectionPoint,
})
this.hoveredObject = firstIntersectObject
await this.onMouseEnter({
selected: this.hoveredObject,
dragSelected: this.selected?.object,
mouseEvent: mouseEvent,
intersectionPoint,
})
if (!this.selected)
this.updateMouseState({