2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
AmbientLight,
|
|
|
|
Color,
|
|
|
|
GridHelper,
|
|
|
|
LineBasicMaterial,
|
|
|
|
OrthographicCamera,
|
|
|
|
PerspectiveCamera,
|
|
|
|
Scene,
|
|
|
|
Vector3,
|
|
|
|
WebGLRenderer,
|
|
|
|
Raycaster,
|
|
|
|
Vector2,
|
|
|
|
Group,
|
|
|
|
MeshBasicMaterial,
|
|
|
|
Mesh,
|
|
|
|
Intersection,
|
|
|
|
Object3D,
|
|
|
|
Object3DEventMap,
|
2024-04-03 13:22:56 +11:00
|
|
|
TextureLoader,
|
|
|
|
Texture,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'three'
|
2024-05-24 20:54:42 +10:00
|
|
|
import { Coords2d, compareVec2Epsilon2 } from 'lang/std/sketch'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { useModelingContext } from 'hooks/useModelingContext'
|
|
|
|
import * as TWEEN from '@tweenjs/tween.js'
|
|
|
|
import { Axis } from 'lib/selections'
|
2024-03-14 15:56:45 -04:00
|
|
|
import { type BaseUnit } from 'lib/settings/settingsTypes'
|
2024-02-26 19:53:44 +11:00
|
|
|
import { CameraControls } from './CameraControls'
|
2024-03-22 16:55:30 +11:00
|
|
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
2024-05-24 20:54:42 +10:00
|
|
|
import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine'
|
|
|
|
import { getAngle, throttle } from 'lib/utils'
|
2024-05-09 08:38:42 -04:00
|
|
|
import { Themes } from 'lib/theme'
|
2024-07-08 16:41:00 -04:00
|
|
|
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
type SendType = ReturnType<typeof useModelingContext>['send']
|
|
|
|
|
|
|
|
// 63.5 is definitely a bit of a magic number, play with it until it looked right
|
|
|
|
// if it were 64, that would feel like it's something in the engine where a random
|
|
|
|
// power of 2 is used, but it's the 0.5 seems to make things look much more correct
|
2024-02-26 19:53:44 +11:00
|
|
|
export const ZOOM_MAGIC_NUMBER = 63.5
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
export const INTERSECTION_PLANE_LAYER = 1
|
|
|
|
export const SKETCH_LAYER = 2
|
2024-03-22 10:23:04 +11:00
|
|
|
|
|
|
|
// redundant types so that it can be changed temporarily but CI will catch the wrong type
|
|
|
|
export const DEBUG_SHOW_INTERSECTION_PLANE: false = false
|
|
|
|
export const DEBUG_SHOW_BOTH_SCENES: false = false
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
|
|
|
|
|
|
|
export const X_AXIS = 'xAxis'
|
|
|
|
export const Y_AXIS = 'yAxis'
|
|
|
|
export const AXIS_GROUP = 'axisGroup'
|
|
|
|
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
|
|
|
export const ARROWHEAD = 'arrowhead'
|
2024-07-08 16:41:00 -04:00
|
|
|
export const SEGMENT_LENGTH_LABEL = 'segment-length-label'
|
|
|
|
export const SEGMENT_LENGTH_LABEL_TEXT = 'segment-length-label-text'
|
|
|
|
export const SEGMENT_LENGTH_LABEL_OFFSET_PX = 30
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-03-04 08:14:37 +11:00
|
|
|
export interface OnMouseEnterLeaveArgs {
|
2024-03-03 16:23:16 +11:00
|
|
|
selected: Object3D<Object3DEventMap>
|
2024-04-03 13:22:56 +11:00
|
|
|
dragSelected?: Object3D<Object3DEventMap>
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent: MouseEvent
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-03-03 16:23:16 +11:00
|
|
|
|
|
|
|
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
|
|
|
|
intersectionPoint: {
|
|
|
|
twoD: Vector2
|
|
|
|
threeD: Vector3
|
|
|
|
}
|
|
|
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-07-30 14:16:53 -04:00
|
|
|
export interface OnClickCallbackArgs {
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent: MouseEvent
|
|
|
|
intersectionPoint?: {
|
|
|
|
twoD: Vector2
|
|
|
|
threeD: Vector3
|
|
|
|
}
|
|
|
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
|
|
|
selected?: Object3D<Object3DEventMap>
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
2024-03-03 16:23:16 +11:00
|
|
|
interface OnMoveCallbackArgs {
|
|
|
|
mouseEvent: MouseEvent
|
|
|
|
intersectionPoint: {
|
|
|
|
twoD: Vector2
|
|
|
|
threeD: Vector3
|
|
|
|
}
|
|
|
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
|
|
|
selected?: Object3D<Object3DEventMap>
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
2024-02-14 08:03:20 +11:00
|
|
|
// This singleton class is responsible for all of the under the hood setup for the client side scene.
|
|
|
|
// That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls.
|
|
|
|
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
type Voidish = void | Promise<void>
|
2024-03-22 16:55:30 +11:00
|
|
|
export class SceneInfra {
|
2024-02-14 08:03:20 +11:00
|
|
|
static instance: SceneInfra
|
2024-02-11 12:59:00 +11:00
|
|
|
scene: Scene
|
|
|
|
renderer: WebGLRenderer
|
2024-07-08 16:41:00 -04:00
|
|
|
labelRenderer: CSS2DRenderer
|
2024-02-26 19:53:44 +11:00
|
|
|
camControls: CameraControls
|
2024-02-11 12:59:00 +11:00
|
|
|
isPerspective = true
|
|
|
|
fov = 45
|
|
|
|
fovBeforeAnimate = 45
|
|
|
|
isFovAnimationInProgress = false
|
2024-03-01 06:55:49 +11:00
|
|
|
_baseUnit: BaseUnit = 'mm'
|
|
|
|
_baseUnitMultiplier = 1
|
2024-05-09 08:38:42 -04:00
|
|
|
_theme: Themes = Themes.System
|
2024-04-03 13:22:56 +11:00
|
|
|
extraSegmentTexture: Texture
|
2024-04-04 11:07:51 +11:00
|
|
|
lastMouseState: MouseState = { type: 'idle' }
|
2024-09-23 22:42:51 +10:00
|
|
|
onDragStartCallback: (arg: OnDragCallbackArgs) => Voidish = () => {}
|
|
|
|
onDragEndCallback: (arg: OnDragCallbackArgs) => Voidish = () => {}
|
|
|
|
onDragCallback: (arg: OnDragCallbackArgs) => Voidish = () => {}
|
|
|
|
onMoveCallback: (arg: OnMoveCallbackArgs) => Voidish = () => {}
|
|
|
|
onClickCallback: (arg: OnClickCallbackArgs) => Voidish = () => {}
|
|
|
|
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => Voidish = () => {}
|
|
|
|
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => Voidish = () => {}
|
2024-02-11 12:59:00 +11:00
|
|
|
setCallbacks = (callbacks: {
|
2024-09-23 22:42:51 +10:00
|
|
|
onDragStart?: (arg: OnDragCallbackArgs) => Voidish
|
|
|
|
onDragEnd?: (arg: OnDragCallbackArgs) => Voidish
|
|
|
|
onDrag?: (arg: OnDragCallbackArgs) => Voidish
|
|
|
|
onMove?: (arg: OnMoveCallbackArgs) => Voidish
|
|
|
|
onClick?: (arg: OnClickCallbackArgs) => Voidish
|
|
|
|
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => Voidish
|
|
|
|
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => Voidish
|
2024-02-11 12:59:00 +11:00
|
|
|
}) => {
|
2024-04-03 13:22:56 +11:00
|
|
|
this.onDragStartCallback = callbacks.onDragStart || this.onDragStartCallback
|
|
|
|
this.onDragEndCallback = callbacks.onDragEnd || this.onDragEndCallback
|
2024-02-11 12:59:00 +11:00
|
|
|
this.onDragCallback = callbacks.onDrag || this.onDragCallback
|
|
|
|
this.onMoveCallback = callbacks.onMove || this.onMoveCallback
|
|
|
|
this.onClickCallback = callbacks.onClick || this.onClickCallback
|
|
|
|
this.onMouseEnter = callbacks.onMouseEnter || this.onMouseEnter
|
|
|
|
this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave
|
|
|
|
this.selected = null // following selections between callbacks being set is too tricky
|
|
|
|
}
|
2024-03-01 06:55:49 +11:00
|
|
|
set baseUnit(unit: BaseUnit) {
|
|
|
|
this._baseUnit = unit
|
|
|
|
this._baseUnitMultiplier = baseUnitTomm(unit)
|
|
|
|
this.scene.scale.set(
|
|
|
|
this._baseUnitMultiplier,
|
|
|
|
this._baseUnitMultiplier,
|
|
|
|
this._baseUnitMultiplier
|
|
|
|
)
|
|
|
|
}
|
2024-05-09 08:38:42 -04:00
|
|
|
set theme(theme: Themes) {
|
|
|
|
this._theme = theme
|
|
|
|
}
|
2024-02-20 11:04:42 +11:00
|
|
|
resetMouseListeners = () => {
|
2024-03-22 16:55:30 +11:00
|
|
|
this.setCallbacks({
|
2024-04-03 13:22:56 +11:00
|
|
|
onDragStart: () => {},
|
|
|
|
onDragEnd: () => {},
|
2024-02-20 11:04:42 +11:00
|
|
|
onDrag: () => {},
|
|
|
|
onMove: () => {},
|
|
|
|
onClick: () => {},
|
|
|
|
onMouseEnter: () => {},
|
|
|
|
onMouseLeave: () => {},
|
|
|
|
})
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
modelingSend: SendType = (() => {}) as any
|
2024-05-24 20:54:42 +10:00
|
|
|
throttledModelingSend: any = (() => {}) as any
|
2024-02-11 12:59:00 +11:00
|
|
|
setSend(send: SendType) {
|
|
|
|
this.modelingSend = send
|
2024-05-24 20:54:42 +10:00
|
|
|
this.throttledModelingSend = throttle(send, 100)
|
|
|
|
}
|
|
|
|
overlayTimeout = 0
|
|
|
|
callbacks: (() => SegmentOverlayPayload | null)[] = []
|
|
|
|
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
|
|
|
const segmentOverlayPayload: SegmentOverlayPayload = {
|
|
|
|
type: 'set-many',
|
|
|
|
overlays: {},
|
|
|
|
}
|
|
|
|
callbacks.forEach((cb) => {
|
|
|
|
const overlay = cb()
|
|
|
|
if (overlay?.type === 'set-one') {
|
|
|
|
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.modelingSend({
|
|
|
|
type: 'Set Segment Overlays',
|
|
|
|
data: segmentOverlayPayload,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
overlayCallbacks(
|
|
|
|
callbacks: (() => SegmentOverlayPayload | null)[],
|
|
|
|
instant = false
|
|
|
|
) {
|
|
|
|
if (instant) {
|
|
|
|
this._overlayCallbacks(callbacks)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.callbacks = callbacks
|
|
|
|
if (this.overlayTimeout) clearTimeout(this.overlayTimeout)
|
|
|
|
this.overlayTimeout = setTimeout(() => {
|
|
|
|
this._overlayCallbacks(this.callbacks)
|
|
|
|
}, 100) as unknown as number
|
|
|
|
}
|
|
|
|
|
|
|
|
overlayThrottleMap: { [pathToNodeString: string]: number } = {}
|
|
|
|
updateOverlayDetails({
|
|
|
|
arrowGroup,
|
|
|
|
group,
|
|
|
|
isHandlesVisible,
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
angle,
|
|
|
|
}: {
|
|
|
|
arrowGroup: Group
|
|
|
|
group: Group
|
|
|
|
isHandlesVisible: boolean
|
|
|
|
from: Coords2d
|
|
|
|
to: Coords2d
|
|
|
|
angle?: number
|
|
|
|
}): SegmentOverlayPayload | null {
|
2024-10-25 15:34:53 -04:00
|
|
|
if (!group.userData.draft && group.userData.pathToNode && arrowGroup) {
|
2024-05-24 20:54:42 +10:00
|
|
|
const vector = new Vector3(0, 0, 0)
|
|
|
|
|
|
|
|
// Get the position of the object3D in world space
|
|
|
|
// console.log('arrowGroup', arrowGroup)
|
|
|
|
arrowGroup.getWorldPosition(vector)
|
|
|
|
|
|
|
|
// Project that position to screen space
|
|
|
|
vector.project(this.camControls.camera)
|
|
|
|
|
|
|
|
const _angle = typeof angle === 'number' ? angle : getAngle(from, to)
|
|
|
|
|
|
|
|
const x = (vector.x * 0.5 + 0.5) * window.innerWidth
|
|
|
|
const y = (-vector.y * 0.5 + 0.5) * window.innerHeight
|
|
|
|
const pathToNodeString = JSON.stringify(group.userData.pathToNode)
|
|
|
|
return {
|
|
|
|
type: 'set-one',
|
|
|
|
pathToNodeString,
|
|
|
|
seg: {
|
|
|
|
windowCoords: [x, y],
|
|
|
|
angle: _angle,
|
|
|
|
group,
|
|
|
|
pathToNode: group.userData.pathToNode,
|
|
|
|
visible: isHandlesVisible,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
hoveredObject: null | any = null
|
|
|
|
raycaster = new Raycaster()
|
|
|
|
planeRaycaster = new Raycaster()
|
|
|
|
currentMouseVector = new Vector2()
|
|
|
|
selected: {
|
|
|
|
mouseDownVector: Vector2
|
2024-03-03 16:23:16 +11:00
|
|
|
object: Object3D<Object3DEventMap>
|
2024-02-11 12:59:00 +11:00
|
|
|
hasBeenDragged: boolean
|
|
|
|
} | null = null
|
|
|
|
mouseDownVector: null | Vector2 = null
|
|
|
|
|
2024-03-22 16:55:30 +11:00
|
|
|
constructor(engineCommandManager: EngineCommandManager) {
|
2024-02-11 12:59:00 +11:00
|
|
|
// SCENE
|
|
|
|
this.scene = new Scene()
|
|
|
|
this.scene.background = new Color(0x000000)
|
|
|
|
this.scene.background = null
|
|
|
|
|
2024-02-26 19:53:44 +11:00
|
|
|
// RENDERER
|
|
|
|
this.renderer = new WebGLRenderer({ antialias: true, alpha: true }) // Enable transparency
|
|
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
|
|
|
this.renderer.setClearColor(0x000000, 0) // Set clear color to black with 0 alpha (fully transparent)
|
2024-07-08 16:41:00 -04:00
|
|
|
|
|
|
|
// LABEL RENDERER
|
|
|
|
this.labelRenderer = new CSS2DRenderer()
|
|
|
|
this.labelRenderer.setSize(window.innerWidth, window.innerHeight)
|
|
|
|
this.labelRenderer.domElement.style.position = 'absolute'
|
|
|
|
this.labelRenderer.domElement.style.top = '0px'
|
|
|
|
this.labelRenderer.domElement.style.pointerEvents = 'none'
|
2024-02-26 19:53:44 +11:00
|
|
|
window.addEventListener('resize', this.onWindowResize)
|
|
|
|
|
2024-03-22 16:55:30 +11:00
|
|
|
this.camControls = new CameraControls(
|
|
|
|
false,
|
|
|
|
this.renderer.domElement,
|
|
|
|
engineCommandManager
|
|
|
|
)
|
2024-02-26 19:53:44 +11:00
|
|
|
this.camControls.subscribeToCamChange(() => this.onCameraChange())
|
|
|
|
this.camControls.camera.layers.enable(SKETCH_LAYER)
|
|
|
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
|
|
|
this.camControls.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
// RAYCASTERS
|
|
|
|
this.raycaster.layers.enable(SKETCH_LAYER)
|
|
|
|
this.raycaster.layers.disable(0)
|
|
|
|
this.planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
|
|
|
|
|
|
|
|
// GRID
|
|
|
|
const size = 100
|
|
|
|
const divisions = 10
|
|
|
|
const gridHelperMaterial = new LineBasicMaterial({
|
|
|
|
color: 0x0000ff,
|
|
|
|
transparent: true,
|
|
|
|
opacity: 0.5,
|
|
|
|
})
|
|
|
|
|
|
|
|
const gridHelper = new GridHelper(size, divisions, 0x0000ff, 0xffffff)
|
|
|
|
gridHelper.material = gridHelperMaterial
|
|
|
|
gridHelper.rotation.x = Math.PI / 2
|
|
|
|
// this.scene.add(gridHelper) // more of a debug thing, but maybe useful
|
|
|
|
|
|
|
|
const light = new AmbientLight(0x505050) // soft white light
|
|
|
|
this.scene.add(light)
|
|
|
|
|
2024-04-03 13:22:56 +11:00
|
|
|
const textureLoader = new TextureLoader()
|
|
|
|
this.extraSegmentTexture = textureLoader.load(
|
2024-08-16 07:15:42 -04:00
|
|
|
'./clientSideSceneAssets/extra-segment-texture.png'
|
2024-04-03 13:22:56 +11:00
|
|
|
)
|
|
|
|
this.extraSegmentTexture.anisotropy =
|
|
|
|
this.renderer?.capabilities?.getMaxAnisotropy?.()
|
|
|
|
|
2024-02-14 08:03:20 +11:00
|
|
|
SceneInfra.instance = this
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
onCameraChange = () => {
|
2024-02-26 19:53:44 +11:00
|
|
|
const scale = getSceneScale(
|
|
|
|
this.camControls.camera,
|
|
|
|
this.camControls.target
|
|
|
|
)
|
2024-02-17 07:04:24 +11:00
|
|
|
const axisGroup = this.scene
|
|
|
|
.getObjectByName(AXIS_GROUP)
|
|
|
|
?.getObjectByName('gridHelper')
|
|
|
|
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
onWindowResize = () => {
|
|
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
2024-07-08 16:41:00 -04:00
|
|
|
this.labelRenderer.setSize(window.innerWidth, window.innerHeight)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
animate = () => {
|
|
|
|
requestAnimationFrame(this.animate)
|
|
|
|
TWEEN.update() // This will update all tweens during the animation loop
|
2024-02-26 19:53:44 +11:00
|
|
|
if (!this.isFovAnimationInProgress) {
|
|
|
|
// console.log('animation frame', this.cameraControls.camera)
|
|
|
|
this.camControls.update()
|
|
|
|
this.renderer.render(this.scene, this.camControls.camera)
|
2024-07-08 16:41:00 -04:00
|
|
|
this.labelRenderer.render(this.scene, this.camControls.camera)
|
2024-02-15 20:32:59 +11:00
|
|
|
}
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
dispose = () => {
|
|
|
|
// Dispose of scene resources, renderer, and controls
|
|
|
|
this.renderer.dispose()
|
|
|
|
window.removeEventListener('resize', this.onWindowResize)
|
|
|
|
// Dispose of any other resources like geometries, materials, textures
|
|
|
|
}
|
|
|
|
getPlaneIntersectPoint = (): {
|
2024-03-03 16:23:16 +11:00
|
|
|
twoD?: Vector2
|
|
|
|
threeD?: Vector3
|
2024-02-11 12:59:00 +11:00
|
|
|
intersection: Intersection<Object3D<Object3DEventMap>>
|
|
|
|
} | null => {
|
|
|
|
this.planeRaycaster.setFromCamera(
|
|
|
|
this.currentMouseVector,
|
2024-03-22 16:55:30 +11:00
|
|
|
this.camControls.camera
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
|
|
|
const planeIntersects = this.planeRaycaster.intersectObjects(
|
|
|
|
this.scene.children,
|
|
|
|
true
|
|
|
|
)
|
2024-03-03 16:23:16 +11:00
|
|
|
const recastablePlaneIntersect = planeIntersects.find(
|
|
|
|
(intersect) => intersect.object.name === RAYCASTABLE_PLANE
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
2024-03-03 16:23:16 +11:00
|
|
|
if (!planeIntersects.length) return null
|
|
|
|
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
|
2024-02-11 12:59:00 +11:00
|
|
|
const planePosition = planeIntersects[0].object.position
|
|
|
|
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
|
|
|
|
.clone()
|
|
|
|
.invert()
|
|
|
|
const intersectPoint = planeIntersects[0].point
|
|
|
|
let transformedPoint = intersectPoint.clone()
|
|
|
|
if (transformedPoint) {
|
|
|
|
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
const twoD = new Vector2(
|
|
|
|
// I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change
|
|
|
|
transformedPoint.x / this._baseUnitMultiplier,
|
|
|
|
transformedPoint.y / this._baseUnitMultiplier
|
|
|
|
) // z should be 0
|
|
|
|
const planePositionCorrected = new Vector3(
|
|
|
|
...planePosition
|
|
|
|
).applyQuaternion(inversePlaneQuaternion)
|
|
|
|
twoD.sub(new Vector2(...planePositionCorrected))
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
return {
|
2024-03-22 10:23:04 +11:00
|
|
|
twoD,
|
2024-03-03 16:23:16 +11:00
|
|
|
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
|
2024-02-11 12:59:00 +11:00
|
|
|
intersection: planeIntersects[0],
|
|
|
|
}
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
onMouseMove = async (mouseEvent: MouseEvent) => {
|
2024-03-03 16:23:16 +11:00
|
|
|
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
|
|
|
|
this.currentMouseVector.y =
|
|
|
|
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
2024-03-03 16:23:16 +11:00
|
|
|
const intersects = this.raycastRing()
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
if (this.selected) {
|
|
|
|
const hasBeenDragged = !compareVec2Epsilon2(
|
|
|
|
[this.currentMouseVector.x, this.currentMouseVector.y],
|
|
|
|
[this.selected.mouseDownVector.x, this.selected.mouseDownVector.y],
|
|
|
|
0.02
|
|
|
|
)
|
|
|
|
if (!this.selected.hasBeenDragged && hasBeenDragged) {
|
|
|
|
this.selected.hasBeenDragged = true
|
|
|
|
// this is where we could fire a onDragStart event
|
|
|
|
// console.log('onDragStart', this.selected)
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
hasBeenDragged &&
|
|
|
|
planeIntersectPoint &&
|
2024-03-03 16:23:16 +11:00
|
|
|
planeIntersectPoint.twoD &&
|
|
|
|
planeIntersectPoint.threeD
|
2024-02-11 12:59:00 +11:00
|
|
|
) {
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onDragCallback({
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent,
|
|
|
|
intersectionPoint: {
|
|
|
|
twoD: planeIntersectPoint.twoD,
|
|
|
|
threeD: planeIntersectPoint.threeD,
|
|
|
|
},
|
|
|
|
intersects,
|
|
|
|
selected: this.selected.object,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-04-04 11:07:51 +11:00
|
|
|
this.updateMouseState({
|
|
|
|
type: 'isDragging',
|
|
|
|
on: this.selected.object,
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-03-03 16:23:16 +11:00
|
|
|
} else if (
|
|
|
|
planeIntersectPoint &&
|
|
|
|
planeIntersectPoint.twoD &&
|
|
|
|
planeIntersectPoint.threeD
|
|
|
|
) {
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onMoveCallback({
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent,
|
|
|
|
intersectionPoint: {
|
|
|
|
twoD: planeIntersectPoint.twoD,
|
|
|
|
threeD: planeIntersectPoint.threeD,
|
|
|
|
},
|
|
|
|
intersects,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-03-03 16:23:16 +11:00
|
|
|
if (intersects[0]) {
|
|
|
|
const firstIntersectObject = intersects[0].object
|
2024-02-11 12:59:00 +11:00
|
|
|
if (this.hoveredObject !== firstIntersectObject) {
|
2024-04-04 11:07:51 +11:00
|
|
|
const hoveredObj = this.hoveredObject
|
|
|
|
this.hoveredObject = null
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onMouseLeave({
|
2024-04-04 11:07:51 +11:00
|
|
|
selected: hoveredObj,
|
|
|
|
mouseEvent: mouseEvent,
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
this.hoveredObject = firstIntersectObject
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onMouseEnter({
|
2024-03-03 16:23:16 +11:00
|
|
|
selected: this.hoveredObject,
|
2024-04-03 13:22:56 +11:00
|
|
|
dragSelected: this.selected?.object,
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent: mouseEvent,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-04-04 11:07:51 +11:00
|
|
|
if (!this.selected)
|
|
|
|
this.updateMouseState({
|
|
|
|
type: 'isHovering',
|
|
|
|
on: this.hoveredObject,
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (this.hoveredObject) {
|
2024-04-04 11:07:51 +11:00
|
|
|
const hoveredObj = this.hoveredObject
|
|
|
|
this.hoveredObject = null
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onMouseLeave({
|
2024-04-04 11:07:51 +11:00
|
|
|
selected: hoveredObj,
|
2024-04-03 13:22:56 +11:00
|
|
|
dragSelected: this.selected?.object,
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent: mouseEvent,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-04-04 11:07:51 +11:00
|
|
|
if (!this.selected) this.updateMouseState({ type: 'idle' })
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
raycastRing = (
|
|
|
|
pixelRadius = 8,
|
|
|
|
rayRingCount = 32
|
2024-03-03 16:23:16 +11:00
|
|
|
): Intersection<Object3D<Object3DEventMap>>[] => {
|
2024-02-11 12:59:00 +11:00
|
|
|
const mouseDownVector = this.currentMouseVector.clone()
|
2024-03-03 16:23:16 +11:00
|
|
|
const intersectionsMap = new Map<
|
|
|
|
Object3D,
|
|
|
|
Intersection<Object3D<Object3DEventMap>>
|
|
|
|
>()
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-03-03 16:23:16 +11:00
|
|
|
const updateIntersectionsMap = (
|
2024-02-11 12:59:00 +11:00
|
|
|
intersections: Intersection<Object3D<Object3DEventMap>>[]
|
|
|
|
) => {
|
2024-03-03 16:23:16 +11:00
|
|
|
intersections.forEach((intersection) => {
|
|
|
|
if (intersection.object.type !== 'GridHelper') {
|
|
|
|
const existingIntersection = intersectionsMap.get(intersection.object)
|
|
|
|
if (
|
|
|
|
!existingIntersection ||
|
|
|
|
existingIntersection.distance > intersection.distance
|
|
|
|
) {
|
|
|
|
intersectionsMap.set(intersection.object, intersection)
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-03-03 16:23:16 +11:00
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check the center point
|
2024-02-26 19:53:44 +11:00
|
|
|
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
|
2024-03-03 16:23:16 +11:00
|
|
|
updateIntersectionsMap(
|
2024-02-11 12:59:00 +11:00
|
|
|
this.raycaster.intersectObjects(this.scene.children, true)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Check the ring points
|
|
|
|
for (let i = 0; i < rayRingCount; i++) {
|
|
|
|
const angle = (i / rayRingCount) * Math.PI * 2
|
|
|
|
const offsetX = ((pixelRadius * Math.cos(angle)) / window.innerWidth) * 2
|
|
|
|
const offsetY = ((pixelRadius * Math.sin(angle)) / window.innerHeight) * 2
|
|
|
|
const ringVector = new Vector2(
|
|
|
|
mouseDownVector.x + offsetX,
|
|
|
|
mouseDownVector.y - offsetY
|
|
|
|
)
|
2024-02-26 19:53:44 +11:00
|
|
|
this.raycaster.setFromCamera(ringVector, this.camControls.camera)
|
2024-03-03 16:23:16 +11:00
|
|
|
updateIntersectionsMap(
|
2024-02-11 12:59:00 +11:00
|
|
|
this.raycaster.intersectObjects(this.scene.children, true)
|
|
|
|
)
|
|
|
|
}
|
2024-03-03 16:23:16 +11:00
|
|
|
|
|
|
|
// Convert the map values to an array and sort by distance
|
|
|
|
return Array.from(intersectionsMap.values()).sort(
|
|
|
|
(a, b) => a.distance - b.distance
|
|
|
|
)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-04-04 11:07:51 +11:00
|
|
|
updateMouseState(mouseState: MouseState) {
|
|
|
|
if (this.lastMouseState.type === mouseState.type) return
|
|
|
|
this.lastMouseState = mouseState
|
|
|
|
this.modelingSend({ type: 'Set mouse state', data: mouseState })
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
onMouseDown = (event: MouseEvent) => {
|
|
|
|
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1
|
|
|
|
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
|
|
|
|
|
|
|
|
const mouseDownVector = this.currentMouseVector.clone()
|
2024-03-03 16:23:16 +11:00
|
|
|
const intersect = this.raycastRing()[0]
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
if (intersect) {
|
|
|
|
const intersectParent = intersect?.object?.parent as Group
|
|
|
|
this.selected = intersectParent.isGroup
|
|
|
|
? {
|
|
|
|
mouseDownVector,
|
2024-03-03 16:23:16 +11:00
|
|
|
object: intersect.object,
|
2024-02-11 12:59:00 +11:00
|
|
|
hasBeenDragged: false,
|
|
|
|
}
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-23 22:42:51 +10:00
|
|
|
onMouseUp = async (mouseEvent: MouseEvent) => {
|
2024-03-03 16:23:16 +11:00
|
|
|
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
|
|
|
|
this.currentMouseVector.y =
|
|
|
|
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
|
2024-02-11 12:59:00 +11:00
|
|
|
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
2024-03-03 16:23:16 +11:00
|
|
|
const intersects = this.raycastRing()
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
if (this.selected) {
|
|
|
|
if (this.selected.hasBeenDragged) {
|
2024-04-03 13:22:56 +11:00
|
|
|
// TODO do the types properly here
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onDragEndCallback({
|
2024-04-03 13:22:56 +11:00
|
|
|
intersectionPoint: {
|
|
|
|
twoD: planeIntersectPoint?.twoD as any,
|
|
|
|
threeD: planeIntersectPoint?.threeD as any,
|
|
|
|
},
|
|
|
|
intersects,
|
|
|
|
mouseEvent,
|
|
|
|
selected: this.selected as any,
|
|
|
|
})
|
2024-04-04 11:07:51 +11:00
|
|
|
if (intersects.length) {
|
|
|
|
this.updateMouseState({
|
|
|
|
type: 'isHovering',
|
|
|
|
on: intersects[0].object,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.updateMouseState({
|
|
|
|
type: 'idle',
|
|
|
|
})
|
|
|
|
}
|
2024-03-03 16:23:16 +11:00
|
|
|
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
2024-02-11 12:59:00 +11:00
|
|
|
// fire onClick event as there was no drags
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onClickCallback({
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent,
|
|
|
|
intersectionPoint: {
|
|
|
|
twoD: planeIntersectPoint.twoD,
|
|
|
|
threeD: planeIntersectPoint.threeD,
|
|
|
|
},
|
|
|
|
intersects,
|
|
|
|
selected: this.selected.object,
|
|
|
|
})
|
|
|
|
} else if (planeIntersectPoint) {
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onClickCallback({
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent,
|
|
|
|
intersects,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
|
|
|
} else {
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onClickCallback({ mouseEvent, intersects })
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
// Clear the selected state whether it was dragged or not
|
|
|
|
this.selected = null
|
2024-03-03 16:23:16 +11:00
|
|
|
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onClickCallback({
|
2024-03-03 16:23:16 +11:00
|
|
|
mouseEvent,
|
|
|
|
intersectionPoint: {
|
|
|
|
twoD: planeIntersectPoint.twoD,
|
|
|
|
threeD: planeIntersectPoint.threeD,
|
|
|
|
},
|
|
|
|
intersects,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
|
|
|
} else {
|
2024-09-23 22:42:51 +10:00
|
|
|
await this.onClickCallback({ mouseEvent, intersects })
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
updateOtherSelectionColors = (otherSelections: Axis[]) => {
|
2024-03-22 16:55:30 +11:00
|
|
|
const axisGroup = this.scene.children.find(
|
2024-02-11 12:59:00 +11:00
|
|
|
({ userData }) => userData?.type === AXIS_GROUP
|
|
|
|
)
|
|
|
|
const axisMap: { [key: string]: Axis } = {
|
|
|
|
[X_AXIS]: 'x-axis',
|
|
|
|
[Y_AXIS]: 'y-axis',
|
|
|
|
}
|
|
|
|
axisGroup?.children.forEach((_mesh) => {
|
|
|
|
const mesh = _mesh as Mesh
|
|
|
|
const mat = mesh.material as MeshBasicMaterial
|
|
|
|
if (otherSelections.includes(axisMap[mesh.userData?.type])) {
|
|
|
|
mat.color.set(mesh?.userData?.baseColor)
|
|
|
|
mat.color.offsetHSL(0, 0, 0.2)
|
|
|
|
mesh.userData.isSelected = true
|
|
|
|
} else {
|
|
|
|
mat.color.set(mesh?.userData?.baseColor)
|
|
|
|
mesh.userData.isSelected = false
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-15 20:32:59 +11:00
|
|
|
export function getSceneScale(
|
|
|
|
camera: PerspectiveCamera | OrthographicCamera,
|
|
|
|
target: Vector3
|
|
|
|
): number {
|
|
|
|
const distance =
|
|
|
|
camera instanceof PerspectiveCamera
|
|
|
|
? camera.position.distanceTo(target)
|
|
|
|
: 63.7942123 / camera.zoom
|
|
|
|
|
|
|
|
if (distance <= 20) return 0.1
|
|
|
|
else if (distance > 20 && distance <= 200) return 1
|
|
|
|
else if (distance > 200 && distance <= 2000) return 10
|
|
|
|
else if (distance > 2000 && distance <= 20000) return 100
|
|
|
|
else if (distance > 20000) return 1000
|
|
|
|
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2024-02-20 17:55:06 -08:00
|
|
|
function baseUnitTomm(baseUnit: BaseUnit) {
|
|
|
|
switch (baseUnit) {
|
|
|
|
case 'mm':
|
|
|
|
return 1
|
|
|
|
case 'cm':
|
|
|
|
return 10
|
|
|
|
case 'm':
|
|
|
|
return 1000
|
|
|
|
case 'in':
|
|
|
|
return 25.4
|
|
|
|
case 'ft':
|
|
|
|
return 304.8
|
|
|
|
case 'yd':
|
|
|
|
return 914.4
|
|
|
|
}
|
|
|
|
}
|