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:
@ -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({
|
||||
|
Reference in New Issue
Block a user