Implement dual camera sync direction (#1597)
* implement dual camer sync direction The existance of the client side scene requires two cameras to stay in sync, really these need to be a master-slave relationship, intitial this was implemented with the client side scene taking the lead and sending updates to the server using the endpoint (as it didn't require an new endpoints), but even though we added a sequence property to this endpoint and sent it over udp, it was still an abuse of this endpoint as the engine didn't have this endpoint setup with a fload of messages and low-latency in mind. Now we have migrated back to sending mouse events to the engine instead, but with the engine replying with camera details on drag_end etc so that we can keep the client camera in sync. The client side camera still does take the master role in sketch mode as it makes sense to keep the low latency benfits of the local camera for the locallay rendered assets in sketch mode, moving the camera in this mode already did hide the engine camera while the camera is moving so as to avoid ghoasting so this works well. The camera controls now work by syncing in either direction depending on what's appropiate * fmt * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * update default plane extrude numbers * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * trigger-ci --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@ -16,9 +16,9 @@ document.addEventListener('mousemove', (e) =>
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const commonPoints = {
|
const commonPoints = {
|
||||||
startAt: '[0.93, -1.26]',
|
startAt: '[9.06, -12.22]',
|
||||||
num1: 0.95,
|
num1: 9.14,
|
||||||
num2: 1.88,
|
num2: 18.2,
|
||||||
}
|
}
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
@ -102,13 +102,13 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1 - 0.01}], %)`)
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1 - 0.01}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> line([-${commonPoints.num2}, 0], %)`)
|
|> line([-${commonPoints.num2}, 0], %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
@ -133,7 +133,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line({ to: [${commonPoints.num1}, 0], tag: 'seg01' }, %)
|
|> line({ to: [${commonPoints.num1}, 0], tag: 'seg01' }, %)
|
||||||
|> line([0, ${commonPoints.num1 - 0.01}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> angledLine([180, segLen('seg01', %)], %)`)
|
|> angledLine([180, segLen('seg01', %)], %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -492,13 +492,13 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1 - 0.01}], %)`)
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1 - 0.01}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> line([-${commonPoints.num2}, 0], %)`)
|
|> line([-${commonPoints.num2}, 0], %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
@ -771,12 +771,12 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1 - 0.01}], %)`)
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1 - 0.01}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> line([-${commonPoints.num2}, 0], %)`
|
|> line([-${commonPoints.num2}, 0], %)`
|
||||||
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
|
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
|
||||||
|
|
||||||
|
@ -384,13 +384,13 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|
||||||
|> startProfileAt([0.70, 0.44], %)
|
|> startProfileAt([7.00, 4.40], %)
|
||||||
|> line([0.66, -0.02], %)
|
|> line([6.60, -0.20], %)
|
||||||
|> line([0.28, 0.50], %)
|
|> line([2.80, 5.00], %)
|
||||||
|> line([-0.56, 0.44], %)
|
|> line([-5.60, 4.40], %)
|
||||||
|> line([-0.54, -0.38], %)
|
|> line([-5.40, -3.80], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(1.00, %)
|
|> extrude(10.00, %)
|
||||||
`
|
`
|
||||||
await context.addInitScript(async (code) => {
|
await context.addInitScript(async (code) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
@ -484,7 +484,7 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)`)
|
|> startProfileAt([9.06, -12.22], %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -498,8 +498,8 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)
|
|> startProfileAt([9.06, -12.22], %)
|
||||||
|> line([0.95, 0], %)`)
|
|> line([9.14, 0], %)`)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
|
|
||||||
@ -562,7 +562,7 @@ test('Client side scene scale should match engine scale inch', async ({
|
|||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)`)
|
|> startProfileAt([9.06, -12.22], %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -572,8 +572,8 @@ test('Client side scene scale should match engine scale inch', async ({
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)
|
|> startProfileAt([9.06, -12.22], %)
|
||||||
|> line([0.95, 0], %)`)
|
|> line([9.14, 0], %)`)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
@ -582,9 +582,13 @@ test('Client side scene scale should match engine scale inch', async ({
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)
|
|> startProfileAt([9.06, -12.22], %)
|
||||||
|> line([0.95, 0], %)
|
|> line([9.14, 0], %)
|
||||||
|> tangentialArcTo([2.82, -0.32], %)`)
|
|> tangentialArcTo([27.34, -3.08], %)`)
|
||||||
|
|
||||||
|
// click tangential arc tool again to unequip it
|
||||||
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// screen shot should show the sketch
|
// screen shot should show the sketch
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -658,7 +662,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
|||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)`)
|
|> startProfileAt([230.03, -310.33], %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -668,8 +672,8 @@ test('Client side scene scale should match engine scale mm', async ({
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)
|
|> startProfileAt([230.03, -310.33], %)
|
||||||
|> line([0.95, 0], %)`)
|
|> line([232.2, 0], %)`)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
@ -678,9 +682,12 @@ test('Client side scene scale should match engine scale mm', async ({
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0.93, -1.26], %)
|
|> startProfileAt([230.03, -310.33], %)
|
||||||
|> line([0.95, 0], %)
|
|> line([232.2, 0], %)
|
||||||
|> tangentialArcTo([2.82, -0.32], %)`)
|
|> tangentialArcTo([694.43, -78.12], %)`)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// screen shot should show the sketch
|
// screen shot should show the sketch
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
@ -16,7 +16,11 @@ import {
|
|||||||
SKETCH_LAYER,
|
SKETCH_LAYER,
|
||||||
ZOOM_MAGIC_NUMBER,
|
ZOOM_MAGIC_NUMBER,
|
||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection'
|
import {
|
||||||
|
EngineCommand,
|
||||||
|
Subscription,
|
||||||
|
engineCommandManager,
|
||||||
|
} from 'lang/std/engineConnection'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { deg2Rad } from 'lib/utils2d'
|
import { deg2Rad } from 'lib/utils2d'
|
||||||
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
||||||
@ -28,6 +32,12 @@ const FRAMES_TO_ANIMATE_IN = 30
|
|||||||
|
|
||||||
const tempQuaternion = new Quaternion() // just used for maths
|
const tempQuaternion = new Quaternion() // just used for maths
|
||||||
|
|
||||||
|
type interactionType = 'pan' | 'rotate' | 'zoom'
|
||||||
|
|
||||||
|
const throttledEngCmd = throttle((cmd: EngineCommand) => {
|
||||||
|
engineCommandManager.sendSceneCommand(cmd)
|
||||||
|
}, 1000 / 15)
|
||||||
|
|
||||||
interface ThreeCamValues {
|
interface ThreeCamValues {
|
||||||
position: Vector3
|
position: Vector3
|
||||||
quaternion: Quaternion
|
quaternion: Quaternion
|
||||||
@ -110,10 +120,11 @@ const throttledUpdateEngineFov = throttle(
|
|||||||
lastCmdDelay
|
lastCmdDelay
|
||||||
) as any as number
|
) as any as number
|
||||||
},
|
},
|
||||||
1000 / 15
|
1000 / 30
|
||||||
)
|
)
|
||||||
|
|
||||||
export class CameraControls {
|
export class CameraControls {
|
||||||
|
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
|
||||||
camera: PerspectiveCamera | OrthographicCamera
|
camera: PerspectiveCamera | OrthographicCamera
|
||||||
target: Vector3
|
target: Vector3
|
||||||
domElement: HTMLCanvasElement
|
domElement: HTMLCanvasElement
|
||||||
@ -221,6 +232,45 @@ export class CameraControls {
|
|||||||
|
|
||||||
this.update()
|
this.update()
|
||||||
this._usePerspectiveCamera()
|
this._usePerspectiveCamera()
|
||||||
|
|
||||||
|
const cb: Subscription<
|
||||||
|
'default_camera_zoom' | 'camera_drag_end' | 'default_camera_get_settings'
|
||||||
|
>['callback'] = ({ data, type }) => {
|
||||||
|
const camSettings = data.settings
|
||||||
|
this.camera.position.set(
|
||||||
|
camSettings.pos.x,
|
||||||
|
camSettings.pos.y,
|
||||||
|
camSettings.pos.z
|
||||||
|
)
|
||||||
|
this.target.set(
|
||||||
|
camSettings.center.x,
|
||||||
|
camSettings.center.y,
|
||||||
|
camSettings.center.z
|
||||||
|
)
|
||||||
|
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
|
||||||
|
this.camera.fov = camSettings.fov_y
|
||||||
|
} else if (
|
||||||
|
this.camera instanceof OrthographicCamera &&
|
||||||
|
camSettings.ortho_scale
|
||||||
|
) {
|
||||||
|
this.camera.zoom = camSettings.ortho_scale
|
||||||
|
}
|
||||||
|
this.onCameraChange()
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
engineCommandManager.subscribeTo({
|
||||||
|
event: 'camera_drag_end',
|
||||||
|
callback: cb,
|
||||||
|
})
|
||||||
|
engineCommandManager.subscribeTo({
|
||||||
|
event: 'default_camera_zoom',
|
||||||
|
callback: cb,
|
||||||
|
})
|
||||||
|
engineCommandManager.subscribeTo({
|
||||||
|
event: 'default_camera_get_settings',
|
||||||
|
callback: cb,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
||||||
@ -254,7 +304,21 @@ export class CameraControls {
|
|||||||
onMouseDown = (event: MouseEvent) => {
|
onMouseDown = (event: MouseEvent) => {
|
||||||
this.isDragging = true
|
this.isDragging = true
|
||||||
this.mouseDownPosition.set(event.clientX, event.clientY)
|
this.mouseDownPosition.set(event.clientX, event.clientY)
|
||||||
|
let interaction = this.getInteractionType(event)
|
||||||
|
if (interaction === 'none') return
|
||||||
this.handleStart()
|
this.handleStart()
|
||||||
|
|
||||||
|
if (this.syncDirection === 'engineToClient') {
|
||||||
|
void engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'camera_drag_start',
|
||||||
|
interaction,
|
||||||
|
window: { x: event.clientX, y: event.clientY },
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove = (event: MouseEvent) => {
|
onMouseMove = (event: MouseEvent) => {
|
||||||
@ -265,36 +329,34 @@ export class CameraControls {
|
|||||||
.sub(this.mouseDownPosition)
|
.sub(this.mouseDownPosition)
|
||||||
this.mouseDownPosition.copy(this.mouseNewPosition)
|
this.mouseDownPosition.copy(this.mouseNewPosition)
|
||||||
|
|
||||||
let state: 'pan' | 'rotate' | 'zoom' = 'pan'
|
const interaction = this.getInteractionType(event)
|
||||||
|
if (interaction === 'none') return
|
||||||
|
|
||||||
if (this.interactionGuards.pan.callback(event as any)) {
|
if (this.syncDirection === 'engineToClient') {
|
||||||
if (this.enablePan === false) return
|
throttledEngCmd({
|
||||||
// handleMouseDownPan(event)
|
type: 'modeling_cmd_req',
|
||||||
state = 'pan'
|
cmd: {
|
||||||
} else if (this.interactionGuards.rotate.callback(event as any)) {
|
type: 'camera_drag_move',
|
||||||
if (this.enableRotate === false) return
|
interaction,
|
||||||
// handleMouseDownRotate(event)
|
window: { x: event.clientX, y: event.clientY },
|
||||||
state = 'rotate'
|
},
|
||||||
} else if (this.interactionGuards.zoom.dragCallback(event as any)) {
|
cmd_id: uuidv4(),
|
||||||
if (this.enableZoom === false) return
|
})
|
||||||
// handleMouseDownDolly(event)
|
|
||||||
state = 'zoom'
|
|
||||||
} else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement camera movement logic here based on deltaMove
|
// Implement camera movement logic here based on deltaMove
|
||||||
// For example, for rotating the camera around the target:
|
// For example, for rotating the camera around the target:
|
||||||
if (state === 'rotate') {
|
if (interaction === 'rotate') {
|
||||||
this.pendingRotation = this.pendingRotation
|
this.pendingRotation = this.pendingRotation
|
||||||
? this.pendingRotation
|
? this.pendingRotation
|
||||||
: new Vector2()
|
: new Vector2()
|
||||||
this.pendingRotation.x += deltaMove.x
|
this.pendingRotation.x += deltaMove.x
|
||||||
this.pendingRotation.y += deltaMove.y
|
this.pendingRotation.y += deltaMove.y
|
||||||
} else if (state === 'zoom') {
|
} else if (interaction === 'zoom') {
|
||||||
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||||
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
||||||
} else if (state === 'pan') {
|
} else if (interaction === 'pan') {
|
||||||
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
||||||
let distance = this.camera.position.distanceTo(this.target)
|
let distance = this.camera.position.distanceTo(this.target)
|
||||||
if (this.camera instanceof OrthographicCamera) {
|
if (this.camera instanceof OrthographicCamera) {
|
||||||
@ -311,11 +373,45 @@ export class CameraControls {
|
|||||||
onMouseUp = (event: MouseEvent) => {
|
onMouseUp = (event: MouseEvent) => {
|
||||||
this.isDragging = false
|
this.isDragging = false
|
||||||
this.handleEnd()
|
this.handleEnd()
|
||||||
|
if (this.syncDirection === 'engineToClient') {
|
||||||
|
const interaction = this.getInteractionType(event)
|
||||||
|
if (interaction === 'none') return
|
||||||
|
void engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'camera_drag_end',
|
||||||
|
interaction,
|
||||||
|
window: { x: event.clientX, y: event.clientY },
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseWheel = (event: WheelEvent) => {
|
onMouseWheel = (event: WheelEvent) => {
|
||||||
// Assume trackpad if the deltas are small and integers
|
// Assume trackpad if the deltas are small and integers
|
||||||
this.handleStart()
|
this.handleStart()
|
||||||
|
|
||||||
|
if (this.syncDirection === 'engineToClient') {
|
||||||
|
const interactions = this.interactionGuards.zoom.scrollCallback(
|
||||||
|
event as any
|
||||||
|
)
|
||||||
|
if (!interactions) {
|
||||||
|
this.handleEnd()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throttledEngCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_zoom',
|
||||||
|
magnitude: -event.deltaY * 0.4,
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
this.handleEnd()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
|
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
|
||||||
|
|
||||||
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
|
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
|
||||||
@ -637,6 +733,10 @@ export class CameraControls {
|
|||||||
duration = 500,
|
duration = 500,
|
||||||
toOrthographic = true
|
toOrthographic = true
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (this.syncDirection === 'engineToClient')
|
||||||
|
console.warn(
|
||||||
|
'tweenCameraToQuaternion not design to work with engineToClient syncDirection.'
|
||||||
|
)
|
||||||
const isVertical = isQuaternionVertical(targetQuaternion)
|
const isVertical = isQuaternionVertical(targetQuaternion)
|
||||||
let remainingDuration = duration
|
let remainingDuration = duration
|
||||||
if (isVertical) {
|
if (isVertical) {
|
||||||
@ -719,6 +819,10 @@ export class CameraControls {
|
|||||||
|
|
||||||
animateToOrthographic = () =>
|
animateToOrthographic = () =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
|
if (this.syncDirection === 'engineToClient')
|
||||||
|
console.warn(
|
||||||
|
'animate To Orthographic not design to work with engineToClient syncDirection.'
|
||||||
|
)
|
||||||
this.isFovAnimationInProgress = true
|
this.isFovAnimationInProgress = true
|
||||||
let currentFov = this.lastPerspectiveFov
|
let currentFov = this.lastPerspectiveFov
|
||||||
this.fovBeforeOrtho = currentFov
|
this.fovBeforeOrtho = currentFov
|
||||||
@ -752,6 +856,10 @@ export class CameraControls {
|
|||||||
})
|
})
|
||||||
animateToPerspective = () =>
|
animateToPerspective = () =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
|
if (this.syncDirection === 'engineToClient')
|
||||||
|
console.warn(
|
||||||
|
'animate To Perspective not design to work with engineToClient syncDirection.'
|
||||||
|
)
|
||||||
this.isFovAnimationInProgress = true
|
this.isFovAnimationInProgress = true
|
||||||
// Immediately set the camera to perspective with a very low FOV
|
// Immediately set the camera to perspective with a very low FOV
|
||||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||||
@ -798,13 +906,14 @@ export class CameraControls {
|
|||||||
this.camera.updateProjectionMatrix()
|
this.camera.updateProjectionMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
throttledUpdateEngineCamera({
|
if (this.syncDirection === 'clientToEngine')
|
||||||
quaternion: this.camera.quaternion,
|
throttledUpdateEngineCamera({
|
||||||
position: this.camera.position,
|
quaternion: this.camera.quaternion,
|
||||||
zoom: this.camera.zoom,
|
position: this.camera.position,
|
||||||
isPerspective: this.isPerspective,
|
zoom: this.camera.zoom,
|
||||||
target: this.target,
|
isPerspective: this.isPerspective,
|
||||||
})
|
target: this.target,
|
||||||
|
})
|
||||||
this.deferReactUpdate({
|
this.deferReactUpdate({
|
||||||
type: this.isPerspective ? 'perspective' : 'orthographic',
|
type: this.isPerspective ? 'perspective' : 'orthographic',
|
||||||
[this.isPerspective ? 'fov' : 'zoom']:
|
[this.isPerspective ? 'fov' : 'zoom']:
|
||||||
@ -825,9 +934,18 @@ export class CameraControls {
|
|||||||
})
|
})
|
||||||
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||||
}
|
}
|
||||||
|
getInteractionType = (event: any) =>
|
||||||
|
_getInteractionType(
|
||||||
|
this.interactionGuards,
|
||||||
|
event,
|
||||||
|
this.enablePan,
|
||||||
|
this.enableRotate,
|
||||||
|
this.enableZoom
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently duplicated, delete one
|
// Pure function helpers
|
||||||
|
|
||||||
function calculateNearFarFromFOV(fov: number) {
|
function calculateNearFarFromFOV(fov: number) {
|
||||||
const nearFarRatio = (fov - 3) / (45 - 3)
|
const nearFarRatio = (fov - 3) / (45 - 3)
|
||||||
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
||||||
@ -835,7 +953,6 @@ function calculateNearFarFromFOV(fov: number) {
|
|||||||
return { z_near: 0.1, z_far }
|
return { z_near: 0.1, z_far }
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently duplicated, delete one
|
|
||||||
function convertThreeCamValuesToEngineCam({
|
function convertThreeCamValuesToEngineCam({
|
||||||
target,
|
target,
|
||||||
position,
|
position,
|
||||||
@ -876,8 +993,6 @@ function convertThreeCamValuesToEngineCam({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pure function helpers
|
|
||||||
|
|
||||||
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
||||||
// Direction from position to target, normalized.
|
// Direction from position to target, normalized.
|
||||||
let direction = new Vector3().subVectors(target, position).normalize()
|
let direction = new Vector3().subVectors(target, position).normalize()
|
||||||
@ -896,3 +1011,17 @@ function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
|||||||
|
|
||||||
return quaternion
|
return quaternion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _getInteractionType(
|
||||||
|
interactionGuards: MouseGuard,
|
||||||
|
event: any,
|
||||||
|
enablePan: boolean,
|
||||||
|
enableRotate: boolean,
|
||||||
|
enableZoom: boolean
|
||||||
|
): interactionType | 'none' {
|
||||||
|
let state: interactionType | 'none' = 'none'
|
||||||
|
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
|
||||||
|
if (enableRotate && interactionGuards.rotate.callback(event)) return 'rotate'
|
||||||
|
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
import {
|
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||||
MouseEventHandler,
|
|
||||||
WheelEventHandler,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { getNormalisedCoordinates, throttle } from '../lib/utils'
|
import { getNormalisedCoordinates } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
@ -36,7 +29,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
}))
|
}))
|
||||||
const { settings } = useGlobalStateContext()
|
const { settings } = useGlobalStateContext()
|
||||||
const cameraControls = settings?.context?.cameraControls
|
|
||||||
const { state } = useModelingContext()
|
const { state } = useModelingContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
const { overallState } = useNetworkStatus()
|
const { overallState } = useNetworkStatus()
|
||||||
@ -68,19 +60,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
setClickCoords({ x, y })
|
setClickCoords({ x, y })
|
||||||
}
|
}
|
||||||
|
|
||||||
const fps = 60
|
|
||||||
const handleScroll: WheelEventHandler<HTMLVideoElement> = throttle((e) => {
|
|
||||||
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_zoom',
|
|
||||||
magnitude: e.deltaY * 0.4,
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
}, Math.round(1000 / fps))
|
|
||||||
|
|
||||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
@ -159,7 +138,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
muted
|
muted
|
||||||
autoPlay
|
autoPlay
|
||||||
controls={false}
|
controls={false}
|
||||||
onWheel={handleScroll}
|
|
||||||
onPlay={() => setIsLoading(false)}
|
onPlay={() => setIsLoading(false)}
|
||||||
onMouseMoveCapture={handleMouseMove}
|
onMouseMoveCapture={handleMouseMove}
|
||||||
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
||||||
|
@ -796,7 +796,7 @@ interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
|||||||
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
|
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Subscription<T extends ModelTypes> {
|
export interface Subscription<T extends ModelTypes> {
|
||||||
event: T
|
event: T
|
||||||
callback: (
|
callback: (
|
||||||
data: Extract<Models['OkModelingCmdResponse_type'], { type: T }>
|
data: Extract<Models['OkModelingCmdResponse_type'], { type: T }>
|
||||||
@ -926,6 +926,15 @@ export class EngineCommandManager {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
sceneInfra.camControls.onCameraChange()
|
sceneInfra.camControls.onCameraChange()
|
||||||
|
this.sendSceneCommand({
|
||||||
|
// CameraControls subscribes to default_camera_get_settings response events
|
||||||
|
// firing this at connection ensure the camera's are synced initially
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
this.initPlanes().then(() => {
|
this.initPlanes().then(() => {
|
||||||
this.resolveReady()
|
this.resolveReady()
|
||||||
|
@ -40,9 +40,9 @@ export interface MouseGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const butName = (e: React.MouseEvent) => ({
|
const butName = (e: React.MouseEvent) => ({
|
||||||
middle: !!(e.buttons & 4),
|
middle: !!(e.buttons & 4) || e.button === 1,
|
||||||
right: !!(e.buttons & 2),
|
right: !!(e.buttons & 2) || e.button === 2,
|
||||||
left: !!(e.buttons & 1),
|
left: !!(e.buttons & 1) || e.button === 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||||
|
@ -119,7 +119,7 @@ export type MoveDesc = { line: number; snippet: string }
|
|||||||
|
|
||||||
export const modelingMachine = createMachine(
|
export const modelingMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout 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-McBNlhGQjynZHZSmibF5HsGoKFmKgKi6KcCAA */
|
/** @xstate-layout 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-McBNlhGQjynZHZSmibF5HsGoKFmKgKi6KcCAA */
|
||||||
id: 'Modeling',
|
id: 'Modeling',
|
||||||
|
|
||||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||||
@ -481,6 +481,7 @@ export const modelingMachine = createMachine(
|
|||||||
'animate after sketch',
|
'animate after sketch',
|
||||||
'tear down client sketch',
|
'tear down client sketch',
|
||||||
'remove sketch grid',
|
'remove sketch grid',
|
||||||
|
'engineToClient cam sync direction',
|
||||||
],
|
],
|
||||||
|
|
||||||
entry: ['add axis n grid', 'conditionally equip line tool'],
|
entry: ['add axis n grid', 'conditionally equip line tool'],
|
||||||
@ -514,6 +515,8 @@ export const modelingMachine = createMachine(
|
|||||||
internal: true,
|
internal: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
entry: 'clientToEngine cam sync direction',
|
||||||
},
|
},
|
||||||
|
|
||||||
'animating to existing sketch': {
|
'animating to existing sketch': {
|
||||||
@ -524,6 +527,8 @@ export const modelingMachine = createMachine(
|
|||||||
onDone: 'Sketch',
|
onDone: 'Sketch',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
entry: 'clientToEngine cam sync direction',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -845,6 +850,12 @@ export const modelingMachine = createMachine(
|
|||||||
// (note the orbit controls are always active though)
|
// (note the orbit controls are always active though)
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
},
|
},
|
||||||
|
'clientToEngine cam sync direction': () => {
|
||||||
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
|
},
|
||||||
|
'engineToClient cam sync direction': () => {
|
||||||
|
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// end actions
|
// end actions
|
||||||
}
|
}
|
||||||
|