Refactor clientSide scene (#3859)

* refactor clientSide scene

* start consolidate threejs segment funcitons

* rename stuff

* first pass of integrating threejs segment create and update into one

* reduce create segment complexity

* add color back in

* use input

* fix comment

* feedback changes
This commit is contained in:
Kurt Hutten
2024-09-13 21:14:14 +10:00
committed by GitHub
parent 728e87a627
commit 8610d606f4
23 changed files with 1823 additions and 1634 deletions

View File

@ -1,10 +1,8 @@
import {
BoxGeometry,
DoubleSide,
ExtrudeGeometry,
Group,
Intersection,
LineCurve3,
Mesh,
MeshBasicMaterial,
Object3D,
@ -15,7 +13,6 @@ import {
Points,
Quaternion,
Scene,
Shape,
Vector2,
Vector3,
} from 'three'
@ -27,8 +24,6 @@ import {
OnClickCallbackArgs,
OnMouseEnterLeaveArgs,
RAYCASTABLE_PLANE,
SEGMENT_LENGTH_LABEL,
SEGMENT_LENGTH_LABEL_TEXT,
SKETCH_GROUP_SEGMENTS,
SKETCH_LAYER,
X_AXIS,
@ -37,7 +32,6 @@ import {
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
import {
CallExpression,
getTangentialArcToInfo,
parse,
Path,
PathToNode,
@ -61,11 +55,9 @@ import {
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst } from 'lang/langHelpers'
import {
createArcGeometry,
dashedStraight,
profileStart,
straightSegment,
tangentialArcToSegment,
createProfileStartHandle,
SegmentUtils,
segmentUtils,
} from './segments'
import {
addCallExpressionsToPipe,
@ -74,13 +66,7 @@ import {
changeSketchArguments,
updateStartProfileAtArgs,
} from 'lang/std/sketch'
import {
isArray,
isOverlap,
normaliseAngle,
roundOff,
throttle,
} from 'lib/utils'
import { isArray, isOverlap, roundOff } from 'lib/utils'
import {
addStartProfileAt,
createArrayExpression,
@ -91,7 +77,6 @@ import {
findUniqueName,
} from 'lang/modifyAst'
import { Selections, getEventForSegmentSelection } from 'lib/selections'
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { createGridHelper, orthoScale, perspScale } from './helpers'
import { Models } from '@kittycad/lib'
import { uuidv4 } from 'lib/utils'
@ -121,6 +106,11 @@ export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const SEGMENT_WIDTH_PX = 1.6
export const HIDE_SEGMENT_LENGTH = 75 // in pixels
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
export const SEGMENT_BODIES = [STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT]
export const SEGMENT_BODIES_PLUS_PROFILE_START = [
...SEGMENT_BODIES,
PROFILE_START,
]
type Vec3Array = [number, number, number]
@ -154,37 +144,35 @@ export class SceneEntities {
? orthoFactor
: perspScale(sceneInfra.camControls.camera, segment)) /
sceneInfra._baseUnitMultiplier
const input = {
type: 'straight-segment',
from: segment.userData.from,
to: segment.userData.to,
} as const
let update: SegmentUtils['update'] | null = null
if (
segment.userData.from &&
segment.userData.to &&
segment.userData.type === STRAIGHT_SEGMENT
) {
callbacks.push(
this.updateStraightSegment({
from: segment.userData.from,
to: segment.userData.to,
group: segment,
scale: factor,
})
)
update = segmentUtils.straight.update
}
if (
segment.userData.from &&
segment.userData.to &&
segment.userData.prevSegment &&
segment.userData.type === TANGENTIAL_ARC_TO_SEGMENT
) {
callbacks.push(
this.updateTangentialArcToSegment({
prevSegment: segment.userData.prevSegment,
from: segment.userData.from,
to: segment.userData.to,
group: segment,
scale: factor,
})
)
update = segmentUtils.tangentialArcTo.update
}
const callBack = update?.({
prevSegment: segment.userData.prevSegment,
input,
group: segment,
scale: factor,
sceneInfra,
})
callBack && !err(callBack) && callbacks.push(callBack)
if (segment.name === PROFILE_START) {
segment.scale.set(factor, factor, factor)
}
@ -421,7 +409,7 @@ export class SceneEntities {
maybeModdedAst,
sketchGroup.start.__geoMeta.sourceRange
)
const _profileStart = profileStart({
const _profileStart = createProfileStartHandle({
from: sketchGroup.start.from,
id: sketchGroup.start.__geoMeta.id,
pathToNode: segPathToNode,
@ -476,50 +464,31 @@ export class SceneEntities {
if (err(_node1)) return
const callExpName = _node1.node?.callee?.name
if (segment.type === 'TangentialArcTo') {
seg = tangentialArcToSegment({
prevSegment: sketchGroup.value[index - 1],
const initSegment =
segment.type === 'TangentialArcTo'
? segmentUtils.tangentialArcTo.init
: segmentUtils.straight.init
const result = initSegment({
prevSegment: sketchGroup.value[index - 1],
callExpName,
input: {
type: 'straight-segment',
from: segment.from,
to: segment.to,
id: segment.__geoMeta.id,
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
isSelected,
})
callbacks.push(
this.updateTangentialArcToSegment({
prevSegment: sketchGroup.value[index - 1],
from: segment.from,
to: segment.to,
group: seg,
scale: factor,
})
)
} else {
seg = straightSegment({
from: segment.from,
to: segment.to,
id: segment.__geoMeta.id,
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
callExpName,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
isSelected,
})
callbacks.push(
this.updateStraightSegment({
from: segment.from,
to: segment.to,
group: seg,
scale: factor,
})
)
}
},
id: segment.__geoMeta.id,
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
isSelected,
sceneInfra,
})
if (err(result)) return
const { group: _group, updateOverlaysCallback } = result
seg = _group
callbacks.push(updateOverlaysCallback)
seg.layers.set(SKETCH_LAYER)
seg.traverse((child) => {
child.layers.set(SKETCH_LAYER)
@ -602,16 +571,19 @@ export class SceneEntities {
kclManager.programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sg)) return sg
const lastSeg = sg.value?.slice(-1)[0] || sg.start
if (err(sg)) return Promise.reject(sg)
const lastSeg = sg?.value?.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({
node: _ast,
programMemory: kclManager.programMemory,
to: [lastSeg.to[0], lastSeg.to[1]],
from: [lastSeg.to[0], lastSeg.to[1]],
input: {
type: 'straight-segment',
to: lastSeg.to,
from: lastSeg.to,
},
fnName: segmentName,
pathToNode: sketchPathToNode,
})
@ -682,8 +654,11 @@ export class SceneEntities {
const tmp = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [intersection2d.x, intersection2d.y],
from: [lastSegment.to[0], lastSegment.to[1]],
input: {
type: 'straight-segment',
from: [lastSegment.to[0], lastSegment.to[1]],
to: [intersection2d.x, intersection2d.y],
},
fnName:
lastSegment.type === 'TangentialArcTo'
? 'tangentialArcTo'
@ -834,14 +809,14 @@ export class SceneEntities {
sketchPathToNode || [],
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') {
updateRectangleSketch(sketchInit, x, y, tags[0])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
if (trap(_recastAst)) return
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
@ -861,7 +836,7 @@ export class SceneEntities {
programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sketchGroup)) return sketchGroup
if (err(sketchGroup)) return
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -950,8 +925,11 @@ export class SceneEntities {
const mod = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
from: [prevSegment.from[0], prevSegment.from[1]],
input: {
type: 'straight-segment',
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
from: prevSegment.from,
},
// TODO assuming it's always a straight segments being added
// as this is easiest, and we'll need to add "tabbing" behavior
// to support other segment types
@ -1072,7 +1050,7 @@ export class SceneEntities {
group.userData.from[0],
group.userData.from[1],
]
const to: [number, number] = [intersection2d.x, intersection2d.y]
const dragTo: [number, number] = [intersection2d.x, intersection2d.y]
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
const _node = getNodeFromPath<CallExpression>(
@ -1095,8 +1073,11 @@ export class SceneEntities {
modded = updateStartProfileAtArgs({
node: modifiedAst,
pathToNode,
to,
from,
input: {
type: 'straight-segment',
to: dragTo,
from,
},
previousProgramMemory: kclManager.programMemory,
})
} else {
@ -1104,8 +1085,11 @@ export class SceneEntities {
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
to,
from
{
type: 'straight-segment',
from,
to: dragTo,
}
)
}
if (trap(modded)) return
@ -1208,264 +1192,36 @@ export class SceneEntities {
? orthoFactor
: perspScale(sceneInfra.camControls.camera, group)) /
sceneInfra._baseUnitMultiplier
const input = {
type: 'straight-segment',
from: segment.from,
to: segment.to,
} as const
let update: SegmentUtils['update'] | null = null
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
return this.updateTangentialArcToSegment({
prevSegment: sgPaths[index - 1],
from: segment.from,
to: segment.to,
group: group,
scale: factor,
})
update = segmentUtils.tangentialArcTo.update
} else if (type === STRAIGHT_SEGMENT) {
return this.updateStraightSegment({
from: segment.from,
to: segment.to,
update = segmentUtils.straight.update
}
const callBack =
update &&
!err(update) &&
update({
input,
group,
scale: factor,
prevSegment: sgPaths[index - 1],
sceneInfra,
})
} else if (type === PROFILE_START) {
if (callBack && !err(callBack)) return callBack
if (type === PROFILE_START) {
group.position.set(segment.from[0], segment.from[1], 0)
group.scale.set(factor, factor, factor)
}
return () => null
}
updateTangentialArcToSegment({
prevSegment,
from,
to,
group,
scale = 1,
}: {
prevSegment: SketchGroup['value'][number]
from: [number, number]
to: [number, number]
group: Group
scale?: number
}): () => SegmentOverlayPayload | null {
group.userData.from = from
group.userData.to = to
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
? getTangentPointFromPreviousArc(
prevSegment.center,
prevSegment.ccw,
prevSegment.to
)
: prevSegment.from
const arcInfo = getTangentialArcToInfo({
arcStartPoint: from,
arcEndPoint: to,
tanPreviousPoint: previousPoint,
obtuse: true,
})
const pxLength = arcInfo.arcLength / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [TANGENTIAL_ARC_TO_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
const arrowheadAngle =
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
if (extraSegmentGroup) {
const circumferenceInPx = (2 * Math.PI * arcInfo.radius) / scale
const extraSegmentAngleDelta =
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
const extraSegmentAngle =
arcInfo.startAngle + (arcInfo.ccw ? 1 : -1) * extraSegmentAngleDelta
const extraSegmentOffset = new Vector2(
Math.cos(extraSegmentAngle) * arcInfo.radius,
Math.sin(extraSegmentAngle) * arcInfo.radius
)
extraSegmentGroup.position.set(
arcInfo.center[0] + extraSegmentOffset.x,
arcInfo.center[1] + extraSegmentOffset.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = isHandlesVisible
}
const tangentialArcToSegmentBody = group.children.find(
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
) as Mesh
if (tangentialArcToSegmentBody) {
const newGeo = createArcGeometry({ ...arcInfo, scale })
tangentialArcToSegmentBody.geometry = newGeo
}
const tangentialArcToSegmentBodyDashed = group.children.find(
(child) => child.userData.type === TANGENTIAL_ARC_TO__SEGMENT_DASH
) as Mesh
if (tangentialArcToSegmentBodyDashed) {
// consider throttling the whole updateTangentialArcToSegment
// if there are more perf considerations going forward
this.throttledUpdateDashedArcGeo({
...arcInfo,
mesh: tangentialArcToSegmentBodyDashed,
isDashed: true,
scale,
})
}
const angle = normaliseAngle(
(arcInfo.endAngle * 180) / Math.PI + (arcInfo.ccw ? 90 : -90)
)
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from,
to,
angle,
})
}
throttledUpdateDashedArcGeo = throttle(
(
args: Parameters<typeof createArcGeometry>[0] & {
mesh: Mesh
scale: number
}
) => (args.mesh.geometry = createArcGeometry(args)),
1000 / 30
)
updateStraightSegment({
from,
to,
group,
scale = 1,
}: {
from: [number, number]
to: [number, number]
group: Group
scale?: number
}): () => SegmentOverlayPayload | null {
group.userData.from = from
group.userData.to = to
const shape = new Shape()
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale) // The width of the line in px (2.4px in this case)
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
const length = Math.sqrt(
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
)
const pxLength = length / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [STRAIGHT_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(
new Vector3(to[0], to[1], 0),
new Vector3(from[0], from[1], 0)
)
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
.normalize()
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
extraSegmentGroup.position.set(
from[0] + offsetFromBase.x,
from[1] + offsetFromBase.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = isHandlesVisible
}
if (labelGroup) {
const labelWrapper = labelGroup.getObjectByName(
SEGMENT_LENGTH_LABEL_TEXT
) as CSS2DObject
const labelWrapperElem = labelWrapper.element as HTMLDivElement
const label = labelWrapperElem.children[0] as HTMLParagraphElement
label.innerText = `${roundOff(length)}`
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
const slope = (to[1] - from[1]) / (to[0] - from[0])
let slopeAngle = ((Math.atan(slope) * 180) / Math.PI) * -1
label.style.setProperty('--degree', `${slopeAngle}deg`)
label.style.setProperty('--x', `0px`)
label.style.setProperty('--y', `0px`)
labelWrapper.position.set((from[0] + to[0]) / 2, (from[1] + to[1]) / 2, 0)
labelGroup.visible = isHandlesVisible
}
const straightSegmentBody = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
) as Mesh
if (straightSegmentBody) {
const line = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)
)
straightSegmentBody.geometry = new ExtrudeGeometry(shape, {
steps: 2,
bevelEnabled: false,
extrudePath: line,
})
}
const straightSegmentBodyDashed = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_DASH
) as Mesh
if (straightSegmentBodyDashed) {
straightSegmentBodyDashed.geometry = dashedStraight(
from,
to,
shape,
scale
)
}
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from,
to,
})
}
/**
* Update the base color of each of the THREEjs meshes
* that represent each of the sketch segments, to get the
@ -1578,27 +1334,30 @@ export class SceneEntities {
}
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const input = {
type: 'straight-segment',
from: parent.userData.from,
to: parent.userData.to,
} as const
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier
let update: SegmentUtils['update'] | null = null
if (parent.name === STRAIGHT_SEGMENT) {
this.updateStraightSegment({
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
update = segmentUtils.straight.update
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({
update = segmentUtils.tangentialArcTo.update
}
update &&
update({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
input,
group: parent,
scale: factor,
sceneInfra,
})
}
return
}
editorManager.setHighlightRange([[0, 0]])
@ -1613,27 +1372,30 @@ export class SceneEntities {
if (parent) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const input = {
type: 'straight-segment',
from: parent.userData.from,
to: parent.userData.to,
} as const
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier
let update: SegmentUtils['update'] | null = null
if (parent.name === STRAIGHT_SEGMENT) {
this.updateStraightSegment({
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
update = segmentUtils.straight.update
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({
update = segmentUtils.tangentialArcTo.update
}
update &&
update({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
input,
group: parent,
scale: factor,
sceneInfra,
})
}
}
const isSelected = parent?.userData?.isSelected
colorSegment(