solve a couple of scene scale bugs (#1496)
* solve a couple of scene scale bugs * Some cam fixes (#1520) * rotate and zoom basics working * intergrate mouse guards, and add pan * implement orthographic camera again * implement switch to perspective camera again * migrate dollyzoom * make pan robust for differnt FOV and orthographic cam * tween to quaternion and default plane selection working with quirks * fix pan It the up and right was derived from the camera's up, which is a static [0,0,1] not the camera's current cameras real up, which aligns itself as best to [0,0,1] but is not that especially when looking straight up or down, and the pan felt very awkward in these vertical look sintuations * fix raycastRing to use new camera * fix tween to quaternion for camera lock situations And get all playwright tests passing * fix up CamToggle, even thought this component is not setup properly to use react properties from our scene class * add animation to cameras back in * first big clean up of sceneInfra * move more cam stuff out of sceneInfra * clean up mouse guard logic * clean up camera change callbacks * fix some sitations where animation to xy doesn't work great * needs to take the target into consideration * last bits of clean up * more clean up * make vitest happ * fix up remaining interaction guards * make scrolling less sensative for trackpads * remove debug cube * fix snapshot tests
This commit is contained in:
@ -14,6 +14,12 @@ document.addEventListener('mousemove', (e) =>
|
|||||||
)
|
)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const commonPoints = {
|
||||||
|
startAt: '[26.38, -35.59]',
|
||||||
|
num1: 26.63,
|
||||||
|
num2: 53.01,
|
||||||
|
}
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
// wait for Vite preview server to be up
|
// wait for Vite preview server to be up
|
||||||
await waitOn({
|
await waitOn({
|
||||||
@ -72,35 +78,34 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
const startAt = '[23.74, -32.03]'
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const num = 23.97
|
const num = 26.63
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)`)
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 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(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${num}], %)`)
|
|> 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(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${num}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> line([-47.71, 0], %)`)
|
|> line([-${commonPoints.num2}, 0], %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await page.getByRole('button', { name: 'Line' }).click()
|
await page.getByRole('button', { name: 'Line' }).click()
|
||||||
@ -122,9 +127,9 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line({ to: [${num}, 0], tag: 'seg01' }, %)
|
|> line({ to: [${commonPoints.num1}, 0], tag: 'seg01' }, %)
|
||||||
|> line([0, ${num}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> angledLine([180, segLen('seg01', %)], %)`)
|
|> angledLine([180, segLen('seg01', %)], %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -305,11 +310,9 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const codeTemplate = (
|
const codeTemplate = (
|
||||||
plane = 'XY',
|
plane = 'XY'
|
||||||
rounded = false,
|
|
||||||
otherThing = '1'
|
|
||||||
) => `const part001 = startSketchOn('${plane}')
|
) => `const part001 = startSketchOn('${plane}')
|
||||||
|> startProfileAt([28.9${otherThing}, -39${rounded ? '' : '.01'}], %)`
|
|> startProfileAt([32.13, -43.34], %)`
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camPos,
|
viewCmd: camPos,
|
||||||
expectedCode: codeTemplate('XY'),
|
expectedCode: codeTemplate('XY'),
|
||||||
@ -318,7 +321,7 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camPos,
|
viewCmd: camPos,
|
||||||
expectedCode: codeTemplate('YZ', true),
|
expectedCode: codeTemplate('YZ'),
|
||||||
clickCoords: { x: 700, y: 300 }, // green plane
|
clickCoords: { x: 700, y: 300 }, // green plane
|
||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
@ -329,7 +332,7 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
|
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmdBackSide,
|
viewCmd: camCmdBackSide,
|
||||||
expectedCode: codeTemplate('-XY', false, '3'),
|
expectedCode: codeTemplate('-XY'),
|
||||||
clickCoords: { x: 601, y: 118 }, // back of red plane
|
clickCoords: { x: 601, y: 118 }, // back of red plane
|
||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
@ -339,7 +342,7 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmdBackSide,
|
viewCmd: camCmdBackSide,
|
||||||
expectedCode: codeTemplate('-XZ', true),
|
expectedCode: codeTemplate('-XZ'),
|
||||||
clickCoords: { x: 680, y: 427 }, // back of blue plane
|
clickCoords: { x: 680, y: 427 }, // back of blue plane
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -461,35 +464,32 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
const startAt = '[23.74, -32.03]'
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
|
||||||
const num = 23.97
|
|
||||||
const num2 = '47.71'
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)`)
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 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(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${num}], %)`)
|
|> 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(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${num}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> line([-${num2}, 0], %)`)
|
|> line([-${commonPoints.num2}, 0], %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await page.getByRole('button', { name: 'Line' }).click()
|
await page.getByRole('button', { name: 'Line' }).click()
|
||||||
@ -539,7 +539,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
|
|
||||||
// check the same selection again by putting cursor in code first then selecting axis
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
await page.getByText(` |> line([-${num2}, 0], %)`).click()
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
@ -550,7 +550,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
|
|
||||||
// select segment in editor than another segment in scene and check there are two cursors
|
// select segment in editor than another segment in scene and check there are two cursors
|
||||||
await page.getByText(` |> line([-${num2}, 0], %)`).click()
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||||
@ -575,7 +575,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
|
|
||||||
// select a line
|
// select a line
|
||||||
// await topHorzSegmentClick()
|
// await topHorzSegmentClick()
|
||||||
await page.getByText(startAt).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
await page.getByText(commonPoints.startAt).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
@ -737,34 +737,32 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
const startAt = '[23.74, -32.03]'
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const num = 23.97
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)`)
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 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(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${num}], %)`)
|
|> 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(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${num}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|> line([-47.71, 0], %)`
|
|> line([-${commonPoints.num2}, 0], %)`
|
||||||
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
|
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
|
||||||
|
|
||||||
// exit the sketch
|
// exit the sketch
|
||||||
@ -786,7 +784,7 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
const startAt2 = '[23.61, -31.85]'
|
const startAt2 = '[26.23, -35.39]'
|
||||||
await expect(
|
await expect(
|
||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
@ -800,7 +798,7 @@ const part002 = startSketchOn('XY')
|
|||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const num2 = 23.83
|
const num2 = 26.48
|
||||||
await expect(
|
await expect(
|
||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
@ -829,7 +827,7 @@ const part002 = startSketchOn('XY')
|
|||||||
|> startProfileAt(${startAt2}, %)
|
|> startProfileAt(${startAt2}, %)
|
||||||
|> line([${num2}, 0], %)
|
|> line([${num2}, 0], %)
|
||||||
|> line([0, ${num2}], %)
|
|> line([0, ${num2}], %)
|
||||||
|> line([-47.44, 0], %)`.replace(/\s/g, '')
|
|> line([-52.71, 0], %)`.replace(/\s/g, '')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -31,6 +31,12 @@ test.beforeEach(async ({ context, page }) => {
|
|||||||
|
|
||||||
test.setTimeout(60000)
|
test.setTimeout(60000)
|
||||||
|
|
||||||
|
const commonPoints = {
|
||||||
|
startAt: '[26.38, -35.59]',
|
||||||
|
num1: 26.63,
|
||||||
|
num2: 53.01,
|
||||||
|
}
|
||||||
|
|
||||||
test('change camera, show planes', async ({ page, context }) => {
|
test('change camera, show planes', async ({ page, context }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -448,10 +454,9 @@ test('Draft segments should look right', async ({ page }) => {
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
const startAt = '[23.74, -32.03]'
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -463,11 +468,10 @@ test('Draft segments should look right', async ({ page }) => {
|
|||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const num = 23.97
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${num}, 0], %)`)
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||||
"lint": "eslint --fix src",
|
"lint": "eslint --fix src",
|
||||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
||||||
"postinstall": "patch-package && yarn xstate:typegen",
|
"postinstall": "yarn xstate:typegen",
|
||||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
|
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
@ -134,7 +134,6 @@
|
|||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"happy-dom": "^10.8.0",
|
"happy-dom": "^10.8.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"patch-package": "^8.0.0",
|
|
||||||
"pixelmatch": "^5.3.0",
|
"pixelmatch": "^5.3.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
diff --git a/node_modules/three/examples/jsm/controls/OrbitControls.js b/node_modules/three/examples/jsm/controls/OrbitControls.js
|
|
||||||
index f29e7fe..0ef636b 100644
|
|
||||||
--- a/node_modules/three/examples/jsm/controls/OrbitControls.js
|
|
||||||
+++ b/node_modules/three/examples/jsm/controls/OrbitControls.js
|
|
||||||
@@ -113,6 +113,25 @@ class OrbitControls extends EventDispatcher {
|
|
||||||
// public methods
|
|
||||||
//
|
|
||||||
|
|
||||||
+ this.interactionGuards = {
|
|
||||||
+ pan: {
|
|
||||||
+ description: 'Right click + Shift + drag or middle click + drag',
|
|
||||||
+ callback: (e) => e.button === 2 && !e.ctrlKey,
|
|
||||||
+ },
|
|
||||||
+ zoom: {
|
|
||||||
+ description: 'Scroll wheel or Right click + Ctrl + drag',
|
|
||||||
+ dragCallback: (e) => e.button === 2 && e.ctrlKey,
|
|
||||||
+ scrollCallback: () => true,
|
|
||||||
+ },
|
|
||||||
+ rotate: {
|
|
||||||
+ description: 'Right click + drag',
|
|
||||||
+ callback: (e) => e.button === 0,
|
|
||||||
+ },
|
|
||||||
+ }
|
|
||||||
+ this.setMouseGuards = (interactionGuards) => {
|
|
||||||
+ this.interactionGuards = interactionGuards
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
this.getPolarAngle = function () {
|
|
||||||
|
|
||||||
return spherical.phi;
|
|
||||||
@@ -1057,92 +1076,21 @@ class OrbitControls extends EventDispatcher {
|
|
||||||
|
|
||||||
function onMouseDown( event ) {
|
|
||||||
|
|
||||||
- let mouseAction;
|
|
||||||
-
|
|
||||||
- switch ( event.button ) {
|
|
||||||
-
|
|
||||||
- case 0:
|
|
||||||
-
|
|
||||||
- mouseAction = scope.mouseButtons.LEFT;
|
|
||||||
- break;
|
|
||||||
-
|
|
||||||
- case 1:
|
|
||||||
-
|
|
||||||
- mouseAction = scope.mouseButtons.MIDDLE;
|
|
||||||
- break;
|
|
||||||
-
|
|
||||||
- case 2:
|
|
||||||
-
|
|
||||||
- mouseAction = scope.mouseButtons.RIGHT;
|
|
||||||
- break;
|
|
||||||
-
|
|
||||||
- default:
|
|
||||||
-
|
|
||||||
- mouseAction = - 1;
|
|
||||||
-
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- switch ( mouseAction ) {
|
|
||||||
-
|
|
||||||
- case MOUSE.DOLLY:
|
|
||||||
-
|
|
||||||
- if ( scope.enableZoom === false ) return;
|
|
||||||
-
|
|
||||||
- handleMouseDownDolly( event );
|
|
||||||
-
|
|
||||||
- state = STATE.DOLLY;
|
|
||||||
-
|
|
||||||
- break;
|
|
||||||
-
|
|
||||||
- case MOUSE.ROTATE:
|
|
||||||
-
|
|
||||||
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
||||||
-
|
|
||||||
- if ( scope.enablePan === false ) return;
|
|
||||||
-
|
|
||||||
- handleMouseDownPan( event );
|
|
||||||
-
|
|
||||||
- state = STATE.PAN;
|
|
||||||
-
|
|
||||||
- } else {
|
|
||||||
-
|
|
||||||
- if ( scope.enableRotate === false ) return;
|
|
||||||
-
|
|
||||||
- handleMouseDownRotate( event );
|
|
||||||
-
|
|
||||||
- state = STATE.ROTATE;
|
|
||||||
-
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- break;
|
|
||||||
-
|
|
||||||
- case MOUSE.PAN:
|
|
||||||
-
|
|
||||||
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
||||||
-
|
|
||||||
- if ( scope.enableRotate === false ) return;
|
|
||||||
-
|
|
||||||
- handleMouseDownRotate( event );
|
|
||||||
-
|
|
||||||
- state = STATE.ROTATE;
|
|
||||||
-
|
|
||||||
- } else {
|
|
||||||
-
|
|
||||||
- if ( scope.enablePan === false ) return;
|
|
||||||
-
|
|
||||||
- handleMouseDownPan( event );
|
|
||||||
-
|
|
||||||
- state = STATE.PAN;
|
|
||||||
-
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- break;
|
|
||||||
-
|
|
||||||
- default:
|
|
||||||
-
|
|
||||||
- state = STATE.NONE;
|
|
||||||
-
|
|
||||||
- }
|
|
||||||
+ if (scope.interactionGuards.pan.callback(event)) {
|
|
||||||
+ if (scope.enablePan === false) return
|
|
||||||
+ handleMouseDownPan(event)
|
|
||||||
+ state = STATE.PAN
|
|
||||||
+ } else if (scope.interactionGuards.rotate.callback(event)) {
|
|
||||||
+ if (scope.enableRotate === false) return
|
|
||||||
+ handleMouseDownRotate(event)
|
|
||||||
+ state = STATE.ROTATE
|
|
||||||
+ } else if (scope.interactionGuards.zoom.dragCallback(event)) {
|
|
||||||
+ if (scope.enableZoom === false) return
|
|
||||||
+ handleMouseDownDolly(event)
|
|
||||||
+ state = STATE.DOLLY
|
|
||||||
+ } else {
|
|
||||||
+ return
|
|
||||||
+ }
|
|
||||||
|
|
||||||
if ( state !== STATE.NONE ) {
|
|
||||||
|
|
900
src/clientSideScene/CameraControls.ts
Normal file
900
src/clientSideScene/CameraControls.ts
Normal file
@ -0,0 +1,900 @@
|
|||||||
|
import { MouseGuard } from 'lib/cameraControls'
|
||||||
|
import {
|
||||||
|
Euler,
|
||||||
|
MathUtils,
|
||||||
|
Matrix4,
|
||||||
|
OrthographicCamera,
|
||||||
|
PerspectiveCamera,
|
||||||
|
Quaternion,
|
||||||
|
Spherical,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
} from 'three'
|
||||||
|
import {
|
||||||
|
DEBUG_SHOW_INTERSECTION_PLANE,
|
||||||
|
INTERSECTION_PLANE_LAYER,
|
||||||
|
SKETCH_LAYER,
|
||||||
|
ZOOM_MAGIC_NUMBER,
|
||||||
|
} from './sceneInfra'
|
||||||
|
import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { deg2Rad } from 'lib/utils2d'
|
||||||
|
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
||||||
|
import * as TWEEN from '@tweenjs/tween.js'
|
||||||
|
import { isQuaternionVertical } from './helpers'
|
||||||
|
|
||||||
|
const ORTHOGRAPHIC_CAMERA_SIZE = 20
|
||||||
|
const FRAMES_TO_ANIMATE_IN = 30
|
||||||
|
|
||||||
|
const tempQuaternion = new Quaternion() // just used for maths
|
||||||
|
|
||||||
|
interface ThreeCamValues {
|
||||||
|
position: Vector3
|
||||||
|
quaternion: Quaternion
|
||||||
|
zoom: number
|
||||||
|
isPerspective: boolean
|
||||||
|
target: Vector3
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReactCameraProperties =
|
||||||
|
| {
|
||||||
|
type: 'perspective'
|
||||||
|
fov?: number
|
||||||
|
position: [number, number, number]
|
||||||
|
quaternion: [number, number, number, number]
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'orthographic'
|
||||||
|
zoom?: number
|
||||||
|
position: [number, number, number]
|
||||||
|
quaternion: [number, number, number, number]
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastCmdDelay = 50
|
||||||
|
|
||||||
|
let lastCmd: EngineCommand | null = null
|
||||||
|
let lastCmdTime: number = Date.now()
|
||||||
|
let lastCmdTimeoutId: number | null = null
|
||||||
|
|
||||||
|
const sendLastReliableChannel = () => {
|
||||||
|
if (lastCmd && Date.now() - lastCmdTime >= lastCmdDelay) {
|
||||||
|
engineCommandManager.sendSceneCommand(lastCmd, true)
|
||||||
|
lastCmdTime = Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
|
||||||
|
const cmd: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
...convertThreeCamValuesToEngineCam(threeValues),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
engineCommandManager.sendSceneCommand(cmd)
|
||||||
|
lastCmd = cmd
|
||||||
|
lastCmdTime = Date.now()
|
||||||
|
|
||||||
|
if (lastCmdTimeoutId !== null) {
|
||||||
|
clearTimeout(lastCmdTimeoutId)
|
||||||
|
}
|
||||||
|
lastCmdTimeoutId = setTimeout(
|
||||||
|
sendLastReliableChannel,
|
||||||
|
lastCmdDelay
|
||||||
|
) as any as number
|
||||||
|
}, 1000 / 30)
|
||||||
|
|
||||||
|
let lastPerspectiveCmd: EngineCommand | null = null
|
||||||
|
let lastPerspectiveCmdTime: number = Date.now()
|
||||||
|
let lastPerspectiveCmdTimeoutId: number | null = null
|
||||||
|
|
||||||
|
const sendLastPerspectiveReliableChannel = () => {
|
||||||
|
if (
|
||||||
|
lastPerspectiveCmd &&
|
||||||
|
Date.now() - lastPerspectiveCmdTime >= lastCmdDelay
|
||||||
|
) {
|
||||||
|
engineCommandManager.sendSceneCommand(lastPerspectiveCmd, true)
|
||||||
|
lastPerspectiveCmdTime = Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const throttledUpdateEngineFov = throttle(
|
||||||
|
(vals: {
|
||||||
|
position: Vector3
|
||||||
|
quaternion: Quaternion
|
||||||
|
zoom: number
|
||||||
|
fov: number
|
||||||
|
target: Vector3
|
||||||
|
}) => {
|
||||||
|
const cmd: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_perspective_settings',
|
||||||
|
...convertThreeCamValuesToEngineCam({
|
||||||
|
...vals,
|
||||||
|
isPerspective: true,
|
||||||
|
}),
|
||||||
|
fov_y: vals.fov,
|
||||||
|
...calculateNearFarFromFOV(vals.fov),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
engineCommandManager.sendSceneCommand(cmd)
|
||||||
|
lastPerspectiveCmd = cmd
|
||||||
|
lastPerspectiveCmdTime = Date.now()
|
||||||
|
if (lastPerspectiveCmdTimeoutId !== null) {
|
||||||
|
clearTimeout(lastPerspectiveCmdTimeoutId)
|
||||||
|
}
|
||||||
|
lastPerspectiveCmdTimeoutId = setTimeout(
|
||||||
|
sendLastPerspectiveReliableChannel,
|
||||||
|
lastCmdDelay
|
||||||
|
) as any as number
|
||||||
|
},
|
||||||
|
1000 / 15
|
||||||
|
)
|
||||||
|
|
||||||
|
export class CameraControls {
|
||||||
|
camera: PerspectiveCamera | OrthographicCamera
|
||||||
|
target: Vector3
|
||||||
|
domElement: HTMLCanvasElement
|
||||||
|
isDragging: boolean
|
||||||
|
mouseDownPosition: Vector2
|
||||||
|
mouseNewPosition: Vector2
|
||||||
|
rotationSpeed = 0.3
|
||||||
|
enableRotate = true
|
||||||
|
enablePan = true
|
||||||
|
enableZoom = true
|
||||||
|
lastPerspectiveFov: number = 45
|
||||||
|
pendingZoom: number | null = null
|
||||||
|
pendingRotation: Vector2 | null = null
|
||||||
|
pendingPan: Vector2 | null = null
|
||||||
|
interactionGuards: MouseGuard = {
|
||||||
|
pan: {
|
||||||
|
description: 'Right click + Shift + drag or middle click + drag',
|
||||||
|
callback: (e) => !!(e.buttons & 4) && !e.ctrlKey,
|
||||||
|
},
|
||||||
|
zoom: {
|
||||||
|
description: 'Scroll wheel or Right click + Ctrl + drag',
|
||||||
|
dragCallback: (e) => e.button === 2 && e.ctrlKey,
|
||||||
|
scrollCallback: () => true,
|
||||||
|
},
|
||||||
|
rotate: {
|
||||||
|
description: 'Right click + drag',
|
||||||
|
callback: (e) => {
|
||||||
|
console.log('event', e)
|
||||||
|
return !!(e.buttons & 2)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
isFovAnimationInProgress = false
|
||||||
|
fovBeforeOrtho = 45
|
||||||
|
get isPerspective() {
|
||||||
|
return this.camera instanceof PerspectiveCamera
|
||||||
|
}
|
||||||
|
|
||||||
|
// reacts hooks into some of this singleton's properties
|
||||||
|
reactCameraProperties: ReactCameraProperties = {
|
||||||
|
type: 'perspective',
|
||||||
|
fov: 12,
|
||||||
|
position: [0, 0, 0],
|
||||||
|
quaternion: [0, 0, 0, 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
setCam = (camProps: ReactCameraProperties) => {
|
||||||
|
if (
|
||||||
|
camProps.type === 'perspective' &&
|
||||||
|
this.camera instanceof OrthographicCamera
|
||||||
|
) {
|
||||||
|
this.usePerspectiveCamera()
|
||||||
|
} else if (
|
||||||
|
camProps.type === 'orthographic' &&
|
||||||
|
this.camera instanceof PerspectiveCamera
|
||||||
|
) {
|
||||||
|
this.useOrthographicCamera()
|
||||||
|
}
|
||||||
|
this.camera.position.set(...camProps.position)
|
||||||
|
this.camera.quaternion.set(...camProps.quaternion)
|
||||||
|
if (
|
||||||
|
camProps.type === 'perspective' &&
|
||||||
|
this.camera instanceof PerspectiveCamera
|
||||||
|
) {
|
||||||
|
// not sure what to do here, calling dollyZoom here is buggy because it updates the position
|
||||||
|
// at the same time
|
||||||
|
} else if (
|
||||||
|
camProps.type === 'orthographic' &&
|
||||||
|
this.camera instanceof OrthographicCamera
|
||||||
|
) {
|
||||||
|
this.camera.zoom = camProps.zoom || 1
|
||||||
|
}
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
this.update(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(isOrtho = false, domElement: HTMLCanvasElement) {
|
||||||
|
this.camera = isOrtho ? new OrthographicCamera() : new PerspectiveCamera()
|
||||||
|
this.camera.up.set(0, 0, 1)
|
||||||
|
this.camera.far = 20000
|
||||||
|
this.target = new Vector3()
|
||||||
|
this.domElement = domElement
|
||||||
|
this.isDragging = false
|
||||||
|
this.mouseDownPosition = new Vector2()
|
||||||
|
this.mouseNewPosition = new Vector2()
|
||||||
|
|
||||||
|
this.domElement.addEventListener('pointerdown', this.onMouseDown)
|
||||||
|
this.domElement.addEventListener('pointermove', this.onMouseMove)
|
||||||
|
this.domElement.addEventListener('pointerup', this.onMouseUp)
|
||||||
|
this.domElement.addEventListener('wheel', this.onMouseWheel)
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.onWindowResize)
|
||||||
|
this.onWindowResize()
|
||||||
|
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
||||||
|
() => {}
|
||||||
|
setIsCamMovingCallback(cb: (isMoving: boolean, isTween: boolean) => void) {
|
||||||
|
this._isCamMovingCallback = cb
|
||||||
|
}
|
||||||
|
private _camChangeCallbacks: { [key: string]: () => void } = {}
|
||||||
|
subscribeToCamChange(cb: () => void) {
|
||||||
|
const cbId = uuidv4()
|
||||||
|
this._camChangeCallbacks[cbId] = cb
|
||||||
|
const unsubscribe = () => {
|
||||||
|
delete this._camChangeCallbacks[cbId]
|
||||||
|
}
|
||||||
|
return unsubscribe
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowResize = () => {
|
||||||
|
if (this.camera instanceof PerspectiveCamera) {
|
||||||
|
this.camera.aspect = window.innerWidth / window.innerHeight
|
||||||
|
} else if (this.camera instanceof OrthographicCamera) {
|
||||||
|
const aspect = window.innerWidth / window.innerHeight
|
||||||
|
this.camera.left = -ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
||||||
|
this.camera.right = ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
||||||
|
this.camera.top = ORTHOGRAPHIC_CAMERA_SIZE
|
||||||
|
this.camera.bottom = -ORTHOGRAPHIC_CAMERA_SIZE
|
||||||
|
}
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown = (event: MouseEvent) => {
|
||||||
|
this.isDragging = true
|
||||||
|
this.mouseDownPosition.set(event.clientX, event.clientY)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseMove = (event: MouseEvent) => {
|
||||||
|
if (this.isDragging) {
|
||||||
|
this.mouseNewPosition.set(event.clientX, event.clientY)
|
||||||
|
const deltaMove = this.mouseNewPosition
|
||||||
|
.clone()
|
||||||
|
.sub(this.mouseDownPosition)
|
||||||
|
this.mouseDownPosition.copy(this.mouseNewPosition)
|
||||||
|
|
||||||
|
let state: 'pan' | 'rotate' | 'zoom' = 'pan'
|
||||||
|
|
||||||
|
if (this.interactionGuards.pan.callback(event as any)) {
|
||||||
|
if (this.enablePan === false) return
|
||||||
|
// handleMouseDownPan(event)
|
||||||
|
state = 'pan'
|
||||||
|
} else if (this.interactionGuards.rotate.callback(event as any)) {
|
||||||
|
if (this.enableRotate === false) return
|
||||||
|
// handleMouseDownRotate(event)
|
||||||
|
state = 'rotate'
|
||||||
|
} else if (this.interactionGuards.zoom.dragCallback(event as any)) {
|
||||||
|
if (this.enableZoom === false) return
|
||||||
|
// handleMouseDownDolly(event)
|
||||||
|
state = 'zoom'
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement camera movement logic here based on deltaMove
|
||||||
|
// For example, for rotating the camera around the target:
|
||||||
|
if (state === 'rotate') {
|
||||||
|
this.pendingRotation = this.pendingRotation
|
||||||
|
? this.pendingRotation
|
||||||
|
: new Vector2()
|
||||||
|
this.pendingRotation.x += deltaMove.x
|
||||||
|
this.pendingRotation.y += deltaMove.y
|
||||||
|
} else if (state === 'zoom') {
|
||||||
|
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||||
|
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
||||||
|
} else if (state === 'pan') {
|
||||||
|
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
||||||
|
let distance = this.camera.position.distanceTo(this.target)
|
||||||
|
if (this.camera instanceof OrthographicCamera) {
|
||||||
|
const zoomFudgeFactor = 2280
|
||||||
|
distance = zoomFudgeFactor / (this.camera.zoom * 45)
|
||||||
|
}
|
||||||
|
const panSpeed = (distance / 1000 / 45) * this.fovBeforeOrtho
|
||||||
|
this.pendingPan.x += -deltaMove.x * panSpeed
|
||||||
|
this.pendingPan.y += deltaMove.y * panSpeed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseUp = (event: MouseEvent) => {
|
||||||
|
this.isDragging = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseWheel = (event: WheelEvent) => {
|
||||||
|
// Assume trackpad if the deltas are small and integers
|
||||||
|
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
|
||||||
|
|
||||||
|
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
|
||||||
|
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||||
|
this.pendingZoom *= 1 + (event.deltaY > 0 ? zoomSpeed : -zoomSpeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
useOrthographicCamera = () => {
|
||||||
|
if (this.camera instanceof OrthographicCamera) return
|
||||||
|
const { x: px, y: py, z: pz } = this.camera.position
|
||||||
|
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||||
|
const aspect = window.innerWidth / window.innerHeight
|
||||||
|
this.lastPerspectiveFov = this.camera.fov
|
||||||
|
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||||
|
this.camera = new OrthographicCamera(
|
||||||
|
-ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
||||||
|
ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
||||||
|
ORTHOGRAPHIC_CAMERA_SIZE,
|
||||||
|
-ORTHOGRAPHIC_CAMERA_SIZE,
|
||||||
|
z_near,
|
||||||
|
z_far
|
||||||
|
)
|
||||||
|
this.camera.up.set(0, 0, 1)
|
||||||
|
this.camera.layers.enable(SKETCH_LAYER)
|
||||||
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||||
|
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
|
this.camera.position.set(px, py, pz)
|
||||||
|
const distance = this.camera.position.distanceTo(this.target.clone())
|
||||||
|
const fovFactor = 45 / this.lastPerspectiveFov
|
||||||
|
this.camera.zoom = (ZOOM_MAGIC_NUMBER * fovFactor * 0.8) / distance
|
||||||
|
|
||||||
|
this.camera.quaternion.set(qx, qy, qz, qw)
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_set_orthographic',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.onCameraChange()
|
||||||
|
}
|
||||||
|
private createPerspectiveCamera = () => {
|
||||||
|
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||||
|
this.camera = new PerspectiveCamera(
|
||||||
|
this.lastPerspectiveFov,
|
||||||
|
window.innerWidth / window.innerHeight,
|
||||||
|
z_near,
|
||||||
|
z_far
|
||||||
|
)
|
||||||
|
this.camera.up.set(0, 0, 1)
|
||||||
|
this.camera.layers.enable(SKETCH_LAYER)
|
||||||
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||||
|
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
|
|
||||||
|
return this.camera
|
||||||
|
}
|
||||||
|
usePerspectiveCamera = () => {
|
||||||
|
const { x: px, y: py, z: pz } = this.camera.position
|
||||||
|
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||||
|
const zoom = this.camera.zoom
|
||||||
|
this.camera = this.createPerspectiveCamera()
|
||||||
|
|
||||||
|
this.camera.position.set(px, py, pz)
|
||||||
|
this.camera.quaternion.set(qx, qy, qz, qw)
|
||||||
|
const zoomFudgeFactor = 2280
|
||||||
|
const distance = zoomFudgeFactor / (zoom * this.lastPerspectiveFov)
|
||||||
|
const direction = new Vector3().subVectors(
|
||||||
|
this.camera.position,
|
||||||
|
this.target
|
||||||
|
)
|
||||||
|
direction.normalize()
|
||||||
|
this.camera.position.copy(this.target).addScaledVector(direction, distance)
|
||||||
|
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_set_perspective',
|
||||||
|
parameters: {
|
||||||
|
fov_y: this.camera.fov,
|
||||||
|
...calculateNearFarFromFOV(this.lastPerspectiveFov),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.onCameraChange()
|
||||||
|
this.update()
|
||||||
|
return this.camera
|
||||||
|
}
|
||||||
|
|
||||||
|
dollyZoom = (newFov: number) => {
|
||||||
|
if (!(this.camera instanceof PerspectiveCamera)) {
|
||||||
|
console.warn('Dolly zoom is only applicable to perspective cameras.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.lastPerspectiveFov = newFov
|
||||||
|
|
||||||
|
// Calculate the direction vector from the camera towards the controls target
|
||||||
|
const direction = new Vector3()
|
||||||
|
.subVectors(this.target, this.camera.position)
|
||||||
|
.normalize()
|
||||||
|
|
||||||
|
// Calculate the distance to the controls target before changing the FOV
|
||||||
|
const distanceBefore = this.camera.position.distanceTo(this.target)
|
||||||
|
|
||||||
|
// Calculate the scale factor for the new FOV compared to the old one
|
||||||
|
// This needs to be calculated before updating the camera's FOV
|
||||||
|
const oldFov = this.camera.fov
|
||||||
|
|
||||||
|
const viewHeightFactor = (fov: number) => {
|
||||||
|
/* *
|
||||||
|
/|
|
||||||
|
/ |
|
||||||
|
/ |
|
||||||
|
/ |
|
||||||
|
/ | viewHeight/2
|
||||||
|
/ |
|
||||||
|
/ |
|
||||||
|
/↙️fov/2 |
|
||||||
|
/________|
|
||||||
|
\ |
|
||||||
|
\._._._.|
|
||||||
|
*/
|
||||||
|
return Math.tan(deg2Rad(fov / 2))
|
||||||
|
}
|
||||||
|
const scaleFactor = viewHeightFactor(oldFov) / viewHeightFactor(newFov)
|
||||||
|
|
||||||
|
this.camera.fov = newFov
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
|
||||||
|
const distanceAfter = distanceBefore * scaleFactor
|
||||||
|
|
||||||
|
const newPosition = this.target
|
||||||
|
.clone()
|
||||||
|
.add(direction.multiplyScalar(-distanceAfter))
|
||||||
|
this.camera.position.copy(newPosition)
|
||||||
|
|
||||||
|
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||||
|
this.camera.near = z_near
|
||||||
|
this.camera.far = z_far
|
||||||
|
|
||||||
|
throttledUpdateEngineFov({
|
||||||
|
fov: newFov,
|
||||||
|
position: newPosition,
|
||||||
|
quaternion: this.camera.quaternion,
|
||||||
|
zoom: this.camera.zoom,
|
||||||
|
target: this.target,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update = (forceUpdate = false) => {
|
||||||
|
// If there are any changes that need to be applied to the camera, apply them here.
|
||||||
|
|
||||||
|
let didChange = forceUpdate
|
||||||
|
if (this.pendingRotation) {
|
||||||
|
this.rotateCamera(this.pendingRotation.x, this.pendingRotation.y)
|
||||||
|
this.pendingRotation = null // Clear the pending rotation after applying it
|
||||||
|
didChange = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pendingZoom) {
|
||||||
|
if (this.camera instanceof PerspectiveCamera) {
|
||||||
|
// move camera towards or away from the target
|
||||||
|
const distance = this.camera.position.distanceTo(this.target)
|
||||||
|
const newDistance = distance * this.pendingZoom
|
||||||
|
const direction = this.camera.position
|
||||||
|
.clone()
|
||||||
|
.sub(this.target)
|
||||||
|
.normalize()
|
||||||
|
const newPosition = this.target
|
||||||
|
.clone()
|
||||||
|
.add(direction.multiplyScalar(newDistance))
|
||||||
|
this.camera.position.copy(newPosition)
|
||||||
|
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
this.pendingZoom = null // Clear the pending zoom after applying it
|
||||||
|
} else {
|
||||||
|
// TODO change ortho zoom
|
||||||
|
this.camera.zoom = this.camera.zoom / this.pendingZoom
|
||||||
|
this.pendingZoom = null
|
||||||
|
}
|
||||||
|
didChange = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pendingPan) {
|
||||||
|
// move camera left/right and up/down
|
||||||
|
const offset = this.camera.position.clone().sub(this.target)
|
||||||
|
const direction = offset.clone().normalize()
|
||||||
|
const cameraQuaternion = this.camera.quaternion
|
||||||
|
const up = new Vector3(0, 1, 0).applyQuaternion(cameraQuaternion)
|
||||||
|
const right = new Vector3().crossVectors(up, direction)
|
||||||
|
right.multiplyScalar(this.pendingPan.x)
|
||||||
|
up.multiplyScalar(this.pendingPan.y)
|
||||||
|
const newPosition = this.camera.position.clone().add(right).add(up)
|
||||||
|
this.target.add(right)
|
||||||
|
this.target.add(up)
|
||||||
|
this.camera.position.copy(newPosition)
|
||||||
|
this.pendingPan = null
|
||||||
|
didChange = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.safeLookAtTarget()
|
||||||
|
|
||||||
|
// Update the camera's matrices
|
||||||
|
this.camera.updateMatrixWorld()
|
||||||
|
if (didChange) {
|
||||||
|
this.onCameraChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
// damping would be implemented here in update if we choose to add it.
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateCamera = (deltaX: number, deltaY: number) => {
|
||||||
|
const quat = new Quaternion().setFromUnitVectors(
|
||||||
|
new Vector3(0, 0, 1),
|
||||||
|
new Vector3(0, 1, 0)
|
||||||
|
)
|
||||||
|
const quatInverse = quat.clone().invert()
|
||||||
|
|
||||||
|
const angleX = deltaX * this.rotationSpeed // rotationSpeed is a constant that defines how fast the camera rotates
|
||||||
|
const angleY = deltaY * this.rotationSpeed
|
||||||
|
|
||||||
|
// Convert angles to radians
|
||||||
|
const radianX = MathUtils.degToRad(angleX)
|
||||||
|
const radianY = MathUtils.degToRad(angleY)
|
||||||
|
|
||||||
|
// Get the offset from the camera to the target
|
||||||
|
const offset = new Vector3().subVectors(this.camera.position, this.target)
|
||||||
|
|
||||||
|
// spherical is a y-up paradigm, need to conform to that for now
|
||||||
|
offset.applyQuaternion(quat)
|
||||||
|
|
||||||
|
// Convert offset to spherical coordinates
|
||||||
|
const spherical = new Spherical().setFromVector3(offset)
|
||||||
|
|
||||||
|
// Apply the rotations
|
||||||
|
spherical.theta -= radianX
|
||||||
|
spherical.phi -= radianY
|
||||||
|
|
||||||
|
// Restrict the phi angle to avoid the camera flipping at the poles
|
||||||
|
spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi))
|
||||||
|
|
||||||
|
// Convert back to Cartesian coordinates
|
||||||
|
offset.setFromSpherical(spherical)
|
||||||
|
|
||||||
|
// put the offset back into the z-up paradigm
|
||||||
|
offset.applyQuaternion(quatInverse)
|
||||||
|
|
||||||
|
// Update the camera's position
|
||||||
|
this.camera.position.copy(this.target).add(offset)
|
||||||
|
|
||||||
|
// Look at the target
|
||||||
|
this.camera.updateMatrixWorld()
|
||||||
|
}
|
||||||
|
|
||||||
|
safeLookAtTarget(up = new Vector3(0, 0, 1)) {
|
||||||
|
const quaternion = _lookAt(this.camera.position, this.target, up)
|
||||||
|
this.camera.quaternion.copy(quaternion)
|
||||||
|
this.camera.updateMatrixWorld()
|
||||||
|
}
|
||||||
|
|
||||||
|
tweenCamToNegYAxis(
|
||||||
|
// -90 degrees from the x axis puts the camera on the negative y axis
|
||||||
|
targetAngle = -Math.PI / 2,
|
||||||
|
duration = 500
|
||||||
|
): Promise<void> {
|
||||||
|
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
||||||
|
// zPosition should stay the same
|
||||||
|
const xyRadius = Math.sqrt(
|
||||||
|
(this.target.x - this.camera.position.x) ** 2 +
|
||||||
|
(this.target.y - this.camera.position.y) ** 2
|
||||||
|
)
|
||||||
|
const xyAngle = Math.atan2(
|
||||||
|
this.camera.position.y - this.target.y,
|
||||||
|
this.camera.position.x - this.target.x
|
||||||
|
)
|
||||||
|
this._isCamMovingCallback(true, true)
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
new TWEEN.Tween({ angle: xyAngle })
|
||||||
|
.to({ angle: targetAngle }, duration)
|
||||||
|
.onUpdate((obj) => {
|
||||||
|
const x = xyRadius * Math.cos(obj.angle)
|
||||||
|
const y = xyRadius * Math.sin(obj.angle)
|
||||||
|
this.camera.position.set(
|
||||||
|
this.target.x + x,
|
||||||
|
this.target.y + y,
|
||||||
|
this.camera.position.z
|
||||||
|
)
|
||||||
|
this.update()
|
||||||
|
this.onCameraChange()
|
||||||
|
})
|
||||||
|
.onComplete((obj) => {
|
||||||
|
const x = xyRadius * Math.cos(obj.angle)
|
||||||
|
const y = xyRadius * Math.sin(obj.angle)
|
||||||
|
this.camera.position.set(
|
||||||
|
this.target.x + x,
|
||||||
|
this.target.y + y,
|
||||||
|
this.camera.position.z
|
||||||
|
)
|
||||||
|
this.update()
|
||||||
|
this.onCameraChange()
|
||||||
|
this._isCamMovingCallback(false, true)
|
||||||
|
|
||||||
|
// resolve after a couple of frames
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => resolve())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.start()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async tweenCameraToQuaternion(
|
||||||
|
targetQuaternion: Quaternion,
|
||||||
|
duration = 500,
|
||||||
|
toOrthographic = true
|
||||||
|
): Promise<void> {
|
||||||
|
const isVertical = isQuaternionVertical(targetQuaternion)
|
||||||
|
let remainingDuration = duration
|
||||||
|
if (isVertical) {
|
||||||
|
remainingDuration = duration * 0.5
|
||||||
|
const orbitRotationDuration = duration * 0.65
|
||||||
|
let targetAngle = -Math.PI / 2
|
||||||
|
const v = new Vector3(0, 0, 1).applyQuaternion(targetQuaternion)
|
||||||
|
if (v.z < 0) targetAngle = Math.PI / 2
|
||||||
|
await this.tweenCamToNegYAxis(targetAngle, orbitRotationDuration)
|
||||||
|
}
|
||||||
|
await this._tweenCameraToQuaternion(
|
||||||
|
targetQuaternion,
|
||||||
|
remainingDuration,
|
||||||
|
toOrthographic
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_tweenCameraToQuaternion(
|
||||||
|
targetQuaternion: Quaternion,
|
||||||
|
duration = 500,
|
||||||
|
toOrthographic = false
|
||||||
|
): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const camera = this.camera
|
||||||
|
this._isCamMovingCallback(true, true)
|
||||||
|
const initialQuaternion = camera.quaternion.clone()
|
||||||
|
const isVertical = isQuaternionVertical(targetQuaternion)
|
||||||
|
let tweenEnd = isVertical ? 0.99 : 1
|
||||||
|
const controlsTarget = this.target.clone()
|
||||||
|
const initialDistance = controlsTarget.distanceTo(camera.position.clone())
|
||||||
|
|
||||||
|
const cameraAtTime = (animationProgress: number /* 0 - 1 */) => {
|
||||||
|
const currentQ = tempQuaternion.slerpQuaternions(
|
||||||
|
initialQuaternion,
|
||||||
|
targetQuaternion,
|
||||||
|
animationProgress
|
||||||
|
)
|
||||||
|
if (this.camera instanceof PerspectiveCamera)
|
||||||
|
// changing the camera position back when it's orthographic doesn't do anything
|
||||||
|
// and it messes up animating back to perspective later
|
||||||
|
this.camera.position
|
||||||
|
.set(0, 0, 1)
|
||||||
|
.applyQuaternion(currentQ)
|
||||||
|
.multiplyScalar(initialDistance)
|
||||||
|
.add(controlsTarget)
|
||||||
|
|
||||||
|
this.camera.up.set(0, 1, 0).applyQuaternion(currentQ).normalize()
|
||||||
|
this.camera.quaternion.copy(currentQ)
|
||||||
|
this.target.copy(controlsTarget)
|
||||||
|
// this.controls.update()
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
this.update()
|
||||||
|
this.onCameraChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onComplete = async () => {
|
||||||
|
if (isReducedMotion() && toOrthographic) {
|
||||||
|
cameraAtTime(0.99)
|
||||||
|
this.useOrthographicCamera()
|
||||||
|
} else if (toOrthographic) {
|
||||||
|
await this.animateToOrthographic()
|
||||||
|
}
|
||||||
|
this.enableRotate = false
|
||||||
|
this._isCamMovingCallback(false, true)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReducedMotion()) {
|
||||||
|
onComplete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
new TWEEN.Tween({ t: 0 })
|
||||||
|
.to({ t: tweenEnd }, duration)
|
||||||
|
.easing(TWEEN.Easing.Quadratic.InOut)
|
||||||
|
.onUpdate(({ t }) => cameraAtTime(t))
|
||||||
|
.onComplete(onComplete)
|
||||||
|
.start()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
animateToOrthographic = () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
this.isFovAnimationInProgress = true
|
||||||
|
let currentFov = this.lastPerspectiveFov
|
||||||
|
this.fovBeforeOrtho = currentFov
|
||||||
|
|
||||||
|
const targetFov = 4
|
||||||
|
const fovAnimationStep = (currentFov - targetFov) / FRAMES_TO_ANIMATE_IN
|
||||||
|
let frameWaitOnFinish = 10
|
||||||
|
|
||||||
|
const animateFovChange = () => {
|
||||||
|
if (this.camera instanceof PerspectiveCamera) {
|
||||||
|
if (this.camera.fov > targetFov) {
|
||||||
|
// Decrease the FOV
|
||||||
|
currentFov = Math.max(currentFov - fovAnimationStep, targetFov)
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
this.dollyZoom(currentFov)
|
||||||
|
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||||
|
} else if (frameWaitOnFinish > 0) {
|
||||||
|
frameWaitOnFinish--
|
||||||
|
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||||
|
} else {
|
||||||
|
// Once the target FOV is reached, switch to the orthographic camera
|
||||||
|
// Needs to wait a couple frames after the FOV animation is complete
|
||||||
|
this.useOrthographicCamera()
|
||||||
|
this.isFovAnimationInProgress = false
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animateFovChange() // Start the animation
|
||||||
|
})
|
||||||
|
animateToPerspective = () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
this.isFovAnimationInProgress = true
|
||||||
|
// Immediately set the camera to perspective with a very low FOV
|
||||||
|
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||||
|
this.lastPerspectiveFov = 4
|
||||||
|
let currentFov = 4
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
|
||||||
|
this.usePerspectiveCamera()
|
||||||
|
|
||||||
|
const animateFovChange = () => {
|
||||||
|
if (this.camera instanceof OrthographicCamera) return
|
||||||
|
if (this.camera.fov < targetFov) {
|
||||||
|
// Increase the FOV
|
||||||
|
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
|
||||||
|
// this.camera.fov = currentFov
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
this.dollyZoom(currentFov)
|
||||||
|
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||||
|
} else {
|
||||||
|
// Set the flag to false as the FOV animation is complete
|
||||||
|
this.isFovAnimationInProgress = false
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animateFovChange() // Start the animation
|
||||||
|
})
|
||||||
|
|
||||||
|
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
||||||
|
setReactCameraPropertiesCallback = (
|
||||||
|
cb: (a: ReactCameraProperties) => void
|
||||||
|
) => {
|
||||||
|
this.reactCameraPropertiesCallback = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
deferReactUpdate = throttle((a: ReactCameraProperties) => {
|
||||||
|
this.reactCameraPropertiesCallback(a)
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
onCameraChange = () => {
|
||||||
|
const distance = this.target.distanceTo(this.camera.position)
|
||||||
|
if (this.camera.far / 2.1 < distance || this.camera.far / 1.9 > distance) {
|
||||||
|
this.camera.far = distance * 2
|
||||||
|
this.camera.near = distance / 10
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
throttledUpdateEngineCamera({
|
||||||
|
quaternion: this.camera.quaternion,
|
||||||
|
position: this.camera.position,
|
||||||
|
zoom: this.camera.zoom,
|
||||||
|
isPerspective: this.isPerspective,
|
||||||
|
target: this.target,
|
||||||
|
})
|
||||||
|
this.deferReactUpdate({
|
||||||
|
type: this.isPerspective ? 'perspective' : 'orthographic',
|
||||||
|
[this.isPerspective ? 'fov' : 'zoom']:
|
||||||
|
this.camera instanceof PerspectiveCamera
|
||||||
|
? this.camera.fov
|
||||||
|
: this.camera.zoom,
|
||||||
|
position: [
|
||||||
|
roundOff(this.camera.position.x, 2),
|
||||||
|
roundOff(this.camera.position.y, 2),
|
||||||
|
roundOff(this.camera.position.z, 2),
|
||||||
|
],
|
||||||
|
quaternion: [
|
||||||
|
roundOff(this.camera.quaternion.x, 2),
|
||||||
|
roundOff(this.camera.quaternion.y, 2),
|
||||||
|
roundOff(this.camera.quaternion.z, 2),
|
||||||
|
roundOff(this.camera.quaternion.w, 2),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently duplicated, delete one
|
||||||
|
function calculateNearFarFromFOV(fov: number) {
|
||||||
|
const nearFarRatio = (fov - 3) / (45 - 3)
|
||||||
|
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
||||||
|
const z_far = 1000 + nearFarRatio * (100000 - 1000)
|
||||||
|
return { z_near: 0.1, z_far }
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently duplicated, delete one
|
||||||
|
function convertThreeCamValuesToEngineCam({
|
||||||
|
target,
|
||||||
|
position,
|
||||||
|
quaternion,
|
||||||
|
zoom,
|
||||||
|
isPerspective,
|
||||||
|
}: ThreeCamValues): {
|
||||||
|
center: Vector3
|
||||||
|
up: Vector3
|
||||||
|
vantage: Vector3
|
||||||
|
} {
|
||||||
|
// Something to consider is that the orbit controls have a target,
|
||||||
|
// we're kind of deriving the target/lookAtVector here when it might not be needed
|
||||||
|
// leaving for now since it's working but maybe revisit later
|
||||||
|
const euler = new Euler().setFromQuaternion(quaternion, 'XYZ')
|
||||||
|
|
||||||
|
const lookAtVector = new Vector3(0, 0, -1)
|
||||||
|
.applyEuler(euler)
|
||||||
|
.normalize()
|
||||||
|
.add(position)
|
||||||
|
|
||||||
|
const upVector = new Vector3(0, 1, 0).applyEuler(euler).normalize()
|
||||||
|
if (isPerspective) {
|
||||||
|
return {
|
||||||
|
center: target,
|
||||||
|
up: upVector,
|
||||||
|
vantage: position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fudgeFactor2 = zoom * 0.9979224466814468 - 0.03473692325839295
|
||||||
|
const zoomFactor = (-ZOOM_MAGIC_NUMBER + fudgeFactor2) / zoom
|
||||||
|
const direction = lookAtVector.clone().sub(position).normalize()
|
||||||
|
const newVantage = position.clone().add(direction.multiplyScalar(zoomFactor))
|
||||||
|
return {
|
||||||
|
center: lookAtVector,
|
||||||
|
up: upVector,
|
||||||
|
vantage: newVantage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pure function helpers
|
||||||
|
|
||||||
|
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
||||||
|
// Direction from position to target, normalized.
|
||||||
|
let direction = new Vector3().subVectors(target, position).normalize()
|
||||||
|
|
||||||
|
// Calculate a new "effective" up vector that is orthogonal to the direction.
|
||||||
|
// This step ensures that the up vector does not affect the direction the camera is looking.
|
||||||
|
let right = new Vector3().crossVectors(direction, up).normalize()
|
||||||
|
let orthogonalUp = new Vector3().crossVectors(right, direction).normalize()
|
||||||
|
|
||||||
|
// Create a lookAt matrix using the position, and the recalculated orthogonal up vector.
|
||||||
|
let lookAtMatrix = new Matrix4()
|
||||||
|
lookAtMatrix.lookAt(position, target, orthogonalUp)
|
||||||
|
|
||||||
|
// Create a quaternion from the lookAt matrix.
|
||||||
|
let quaternion = new Quaternion().setFromRotationMatrix(lookAtMatrix)
|
||||||
|
|
||||||
|
return quaternion
|
||||||
|
}
|
@ -4,11 +4,8 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import {
|
import { DEBUG_SHOW_BOTH_SCENES, sceneInfra } from './sceneInfra'
|
||||||
DEBUG_SHOW_BOTH_SCENES,
|
import { ReactCameraProperties } from './CameraControls'
|
||||||
ReactCameraProperties,
|
|
||||||
sceneInfra,
|
|
||||||
} from './sceneInfra'
|
|
||||||
import { throttle } from 'lib/utils'
|
import { throttle } from 'lib/utils'
|
||||||
|
|
||||||
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||||||
@ -18,7 +15,7 @@ function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
|||||||
const { state } = useModelingContext()
|
const { state } = useModelingContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sceneInfra.setIsCamMovingCallback((isMoving, isTween) => {
|
sceneInfra.camControls.setIsCamMovingCallback((isMoving, isTween) => {
|
||||||
setIsCamMoving(isMoving)
|
setIsCamMoving(isMoving)
|
||||||
setIsTween(isTween)
|
setIsTween(isTween)
|
||||||
})
|
})
|
||||||
@ -52,7 +49,8 @@ export const ClientSideScene = ({
|
|||||||
// Listen for changes to the camera controls setting
|
// Listen for changes to the camera controls setting
|
||||||
// and update the client-side scene's controls accordingly.
|
// and update the client-side scene's controls accordingly.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sceneInfra.setInteractionGuards(cameraMouseDragGuards[cameraControls])
|
sceneInfra.camControls.interactionGuards =
|
||||||
|
cameraMouseDragGuards[cameraControls]
|
||||||
}, [cameraControls])
|
}, [cameraControls])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sceneInfra.updateOtherSelectionColors(
|
sceneInfra.updateOtherSelectionColors(
|
||||||
@ -93,7 +91,7 @@ export const ClientSideScene = ({
|
|||||||
|
|
||||||
const throttled = throttle((a: ReactCameraProperties) => {
|
const throttled = throttle((a: ReactCameraProperties) => {
|
||||||
if (a.type === 'perspective' && a.fov) {
|
if (a.type === 'perspective' && a.fov) {
|
||||||
sceneInfra.dollyZoom(a.fov)
|
sceneInfra.camControls.dollyZoom(a.fov)
|
||||||
}
|
}
|
||||||
}, 1000 / 15)
|
}, 1000 / 15)
|
||||||
|
|
||||||
@ -107,7 +105,7 @@ export const CamDebugSettings = () => {
|
|||||||
const [fov, setFov] = useState(12)
|
const [fov, setFov] = useState(12)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sceneInfra.setReactCameraPropertiesCallback(setCamSettings)
|
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
|
||||||
}, [sceneInfra])
|
}, [sceneInfra])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (camSettings.type === 'perspective' && camSettings.fov) {
|
if (camSettings.type === 'perspective' && camSettings.fov) {
|
||||||
@ -124,9 +122,9 @@ export const CamDebugSettings = () => {
|
|||||||
checked={camSettings.type === 'perspective'}
|
checked={camSettings.type === 'perspective'}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (camSettings.type === 'perspective') {
|
if (camSettings.type === 'perspective') {
|
||||||
sceneInfra.useOrthographicCamera()
|
sceneInfra.camControls.useOrthographicCamera()
|
||||||
} else {
|
} else {
|
||||||
sceneInfra.usePerspectiveCamera()
|
sceneInfra.camControls.usePerspectiveCamera()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -156,7 +154,7 @@ export const CamDebugSettings = () => {
|
|||||||
value={camSettings.fov}
|
value={camSettings.fov}
|
||||||
className="text-black w-16"
|
className="text-black w-16"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
sceneInfra.setCam({
|
sceneInfra.camControls.setCam({
|
||||||
...camSettings,
|
...camSettings,
|
||||||
fov: parseFloat(e.target.value),
|
fov: parseFloat(e.target.value),
|
||||||
})
|
})
|
||||||
@ -173,7 +171,7 @@ export const CamDebugSettings = () => {
|
|||||||
value={camSettings.zoom}
|
value={camSettings.zoom}
|
||||||
className="text-black w-16"
|
className="text-black w-16"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
sceneInfra.setCam({
|
sceneInfra.camControls.setCam({
|
||||||
...camSettings,
|
...camSettings,
|
||||||
zoom: parseFloat(e.target.value),
|
zoom: parseFloat(e.target.value),
|
||||||
})
|
})
|
||||||
@ -194,7 +192,7 @@ export const CamDebugSettings = () => {
|
|||||||
value={camSettings.position[0]}
|
value={camSettings.position[0]}
|
||||||
className="text-black w-16"
|
className="text-black w-16"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
sceneInfra.setCam({
|
sceneInfra.camControls.setCam({
|
||||||
...camSettings,
|
...camSettings,
|
||||||
position: [
|
position: [
|
||||||
parseFloat(e.target.value),
|
parseFloat(e.target.value),
|
||||||
@ -214,7 +212,7 @@ export const CamDebugSettings = () => {
|
|||||||
value={camSettings.position[1]}
|
value={camSettings.position[1]}
|
||||||
className="text-black w-16"
|
className="text-black w-16"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
sceneInfra.setCam({
|
sceneInfra.camControls.setCam({
|
||||||
...camSettings,
|
...camSettings,
|
||||||
position: [
|
position: [
|
||||||
camSettings.position[0],
|
camSettings.position[0],
|
||||||
@ -234,7 +232,7 @@ export const CamDebugSettings = () => {
|
|||||||
value={camSettings.position[2]}
|
value={camSettings.position[2]}
|
||||||
className="text-black w-16"
|
className="text-black w-16"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
sceneInfra.setCam({
|
sceneInfra.camControls.setCam({
|
||||||
...camSettings,
|
...camSettings,
|
||||||
position: [
|
position: [
|
||||||
camSettings.position[0],
|
camSettings.position[0],
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Quaternion } from 'three'
|
import { Quaternion } from 'three'
|
||||||
import { isQuaternionVertical } from './sceneInfra'
|
import { isQuaternionVertical } from './helpers'
|
||||||
|
|
||||||
describe('isQuaternionVertical', () => {
|
describe('isQuaternionVertical', () => {
|
||||||
it('should identify vertical quaternions', () => {
|
it('should identify vertical quaternions', () => {
|
@ -1,3 +1,4 @@
|
|||||||
|
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||||
import {
|
import {
|
||||||
GridHelper,
|
GridHelper,
|
||||||
LineBasicMaterial,
|
LineBasicMaterial,
|
||||||
@ -5,6 +6,8 @@ import {
|
|||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
Group,
|
Group,
|
||||||
Mesh,
|
Mesh,
|
||||||
|
Quaternion,
|
||||||
|
Vector3,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
|
|
||||||
export function createGridHelper({
|
export function createGridHelper({
|
||||||
@ -31,3 +34,9 @@ export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
|
|||||||
|
|
||||||
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
|
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
|
||||||
(group.position.distanceTo(cam.position) * cam.fov) / 4000
|
(group.position.distanceTo(cam.position) * cam.fov) / 4000
|
||||||
|
|
||||||
|
export function isQuaternionVertical(q: Quaternion) {
|
||||||
|
const v = new Vector3(0, 0, 1).applyQuaternion(q)
|
||||||
|
// no x or y components means it's vertical
|
||||||
|
return compareVec2Epsilon2([v.x, v.y], [0, 0])
|
||||||
|
}
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
defaultPlaneColor,
|
defaultPlaneColor,
|
||||||
getSceneScale,
|
getSceneScale,
|
||||||
INTERSECTION_PLANE_LAYER,
|
INTERSECTION_PLANE_LAYER,
|
||||||
isQuaternionVertical,
|
|
||||||
RAYCASTABLE_PLANE,
|
RAYCASTABLE_PLANE,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
SKETCH_GROUP_SEGMENTS,
|
SKETCH_GROUP_SEGMENTS,
|
||||||
@ -34,6 +33,7 @@ import {
|
|||||||
Y_AXIS,
|
Y_AXIS,
|
||||||
YZ_PLANE,
|
YZ_PLANE,
|
||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
|
import { isQuaternionVertical } from './helpers'
|
||||||
import {
|
import {
|
||||||
CallExpression,
|
CallExpression,
|
||||||
getTangentialArcToInfo,
|
getTangentialArcToInfo,
|
||||||
@ -98,17 +98,17 @@ class SceneEntities {
|
|||||||
currentSketchQuaternion: Quaternion | null = null
|
currentSketchQuaternion: Quaternion | null = null
|
||||||
constructor() {
|
constructor() {
|
||||||
this.scene = sceneInfra?.scene
|
this.scene = sceneInfra?.scene
|
||||||
sceneInfra?.setOnCamChange(this.onCamChange)
|
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCamChange = () => {
|
onCamChange = () => {
|
||||||
const orthoFactor = orthoScale(sceneInfra.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
Object.values(this.activeSegments).forEach((segment) => {
|
Object.values(this.activeSegments).forEach((segment) => {
|
||||||
const factor =
|
const factor =
|
||||||
sceneInfra.camera instanceof OrthographicCamera
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camera, segment)
|
: perspScale(sceneInfra.camControls.camera, segment)
|
||||||
if (
|
if (
|
||||||
segment.userData.from &&
|
segment.userData.from &&
|
||||||
segment.userData.to &&
|
segment.userData.to &&
|
||||||
@ -139,9 +139,9 @@ class SceneEntities {
|
|||||||
})
|
})
|
||||||
if (this.axisGroup) {
|
if (this.axisGroup) {
|
||||||
const factor =
|
const factor =
|
||||||
sceneInfra.camera instanceof OrthographicCamera
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camera, this.axisGroup)
|
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
|
||||||
const x = this.axisGroup.getObjectByName(X_AXIS)
|
const x = this.axisGroup.getObjectByName(X_AXIS)
|
||||||
x?.scale.set(1, factor, 1)
|
x?.scale.set(1, factor, 1)
|
||||||
const y = this.axisGroup.getObjectByName(Y_AXIS)
|
const y = this.axisGroup.getObjectByName(Y_AXIS)
|
||||||
@ -150,7 +150,12 @@ class SceneEntities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createIntersectionPlane() {
|
createIntersectionPlane() {
|
||||||
const planeGeometry = new PlaneGeometry(100000, 100000)
|
if (sceneInfra.scene.getObjectByName(RAYCASTABLE_PLANE)) {
|
||||||
|
console.warn('createIntersectionPlane called when it already exists')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const hundredM = 1000000
|
||||||
|
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
|
||||||
const planeMaterial = new MeshBasicMaterial({
|
const planeMaterial = new MeshBasicMaterial({
|
||||||
color: 0xff0000,
|
color: 0xff0000,
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
@ -195,11 +200,12 @@ class SceneEntities {
|
|||||||
|
|
||||||
this.axisGroup = new Group()
|
this.axisGroup = new Group()
|
||||||
const gridHelper = createGridHelper({ size: 100, divisions: 10 })
|
const gridHelper = createGridHelper({ size: 100, divisions: 10 })
|
||||||
|
gridHelper.position.z = -0.01
|
||||||
gridHelper.renderOrder = -3 // is this working?
|
gridHelper.renderOrder = -3 // is this working?
|
||||||
gridHelper.name = 'gridHelper'
|
gridHelper.name = 'gridHelper'
|
||||||
const sceneScale = getSceneScale(
|
const sceneScale = getSceneScale(
|
||||||
sceneInfra.camera,
|
sceneInfra.camControls.camera,
|
||||||
sceneInfra.controls.target
|
sceneInfra.camControls.target
|
||||||
)
|
)
|
||||||
gridHelper.scale.set(sceneScale, sceneScale, sceneScale)
|
gridHelper.scale.set(sceneScale, sceneScale, sceneScale)
|
||||||
this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper)
|
this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper)
|
||||||
@ -240,15 +246,6 @@ class SceneEntities {
|
|||||||
}) {
|
}) {
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
this.createIntersectionPlane()
|
this.createIntersectionPlane()
|
||||||
const distance = sceneInfra.controls.target.distanceTo(
|
|
||||||
sceneInfra.camera.position
|
|
||||||
)
|
|
||||||
// TODO this should probably be distance to the sketch group, more important after sketch on face
|
|
||||||
// since sketches won't always so close to the origin
|
|
||||||
// is this the best place to adjust camera far?
|
|
||||||
if (sceneInfra.camera.far < distance * 1.5) {
|
|
||||||
sceneInfra.camera.far = distance * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||||
this.prepareTruncatedMemoryAndAst(
|
this.prepareTruncatedMemoryAndAst(
|
||||||
@ -280,11 +277,11 @@ class SceneEntities {
|
|||||||
sketchGroup.position[1],
|
sketchGroup.position[1],
|
||||||
sketchGroup.position[2]
|
sketchGroup.position[2]
|
||||||
)
|
)
|
||||||
const orthoFactor = orthoScale(sceneInfra.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
const factor =
|
const factor =
|
||||||
sceneInfra.camera instanceof OrthographicCamera
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camera, dummy)
|
: perspScale(sceneInfra.camControls.camera, dummy)
|
||||||
sketchGroup.value.forEach((segment, index) => {
|
sketchGroup.value.forEach((segment, index) => {
|
||||||
let segPathToNode = getNodePathFromSourceRange(
|
let segPathToNode = getNodePathFromSourceRange(
|
||||||
draftSegment ? truncatedAst : kclManager.ast,
|
draftSegment ? truncatedAst : kclManager.ast,
|
||||||
@ -451,7 +448,7 @@ class SceneEntities {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sceneInfra.controls.enableRotate = false
|
sceneInfra.camControls.enableRotate = false
|
||||||
}
|
}
|
||||||
updateAstAndRejigSketch = async (
|
updateAstAndRejigSketch = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
@ -554,7 +551,7 @@ class SceneEntities {
|
|||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
const sketchGroup = programMemory.root[variableDeclarationName]
|
const sketchGroup = programMemory.root[variableDeclarationName]
|
||||||
.value as Path[]
|
.value as Path[]
|
||||||
const orthoFactor = orthoScale(sceneInfra.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
sketchGroup.forEach((segment, index) => {
|
sketchGroup.forEach((segment, index) => {
|
||||||
const segPathToNode = getNodePathFromSourceRange(
|
const segPathToNode = getNodePathFromSourceRange(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
@ -570,9 +567,9 @@ class SceneEntities {
|
|||||||
// const prevSegment = sketchGroup.slice(index - 1)[0]
|
// const prevSegment = sketchGroup.slice(index - 1)[0]
|
||||||
const type = group?.userData?.type
|
const type = group?.userData?.type
|
||||||
const factor =
|
const factor =
|
||||||
sceneInfra.camera instanceof OrthographicCamera
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camera, group)
|
: perspScale(sceneInfra.camControls.camera, group)
|
||||||
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
||||||
this.updateTangentialArcToSegment({
|
this.updateTangentialArcToSegment({
|
||||||
prevSegment: sketchGroup[index - 1],
|
prevSegment: sketchGroup[index - 1],
|
||||||
@ -729,10 +726,10 @@ class SceneEntities {
|
|||||||
}
|
}
|
||||||
async animateAfterSketch() {
|
async animateAfterSketch() {
|
||||||
if (isReducedMotion()) {
|
if (isReducedMotion()) {
|
||||||
sceneInfra.usePerspectiveCamera()
|
sceneInfra.camControls.usePerspectiveCamera()
|
||||||
} else {
|
return
|
||||||
await sceneInfra.animateToPerspective()
|
|
||||||
}
|
}
|
||||||
|
await sceneInfra.camControls.animateToPerspective()
|
||||||
}
|
}
|
||||||
removeSketchGrid() {
|
removeSketchGrid() {
|
||||||
if (this.axisGroup) this.scene.remove(this.axisGroup)
|
if (this.axisGroup) this.scene.remove(this.axisGroup)
|
||||||
@ -764,7 +761,7 @@ class SceneEntities {
|
|||||||
reject()
|
reject()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sceneInfra.controls.enableRotate = true
|
sceneInfra.camControls.enableRotate = true
|
||||||
this.activeSegments = {}
|
this.activeSegments = {}
|
||||||
// maybe should reset onMove etc handlers
|
// maybe should reset onMove etc handlers
|
||||||
if (shouldResolve) resolve(true)
|
if (shouldResolve) resolve(true)
|
||||||
@ -797,9 +794,8 @@ class SceneEntities {
|
|||||||
onClick: (args) => {
|
onClick: (args) => {
|
||||||
if (!args || !args.object) return
|
if (!args || !args.object) return
|
||||||
if (args.event.which !== 1) return
|
if (args.event.which !== 1) return
|
||||||
const { object, intersection } = args
|
const { intersection } = args
|
||||||
const type = object?.userData?.type || ''
|
const type = intersection.object.name || ''
|
||||||
console.log('intersection.normal?.z', intersection)
|
|
||||||
const posNorm = Number(intersection.normal?.z) > 0
|
const posNorm = Number(intersection.normal?.z) > 0
|
||||||
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
||||||
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
AmbientLight,
|
AmbientLight,
|
||||||
Color,
|
Color,
|
||||||
Euler,
|
|
||||||
GridHelper,
|
GridHelper,
|
||||||
LineBasicMaterial,
|
LineBasicMaterial,
|
||||||
OrthographicCamera,
|
OrthographicCamera,
|
||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
Quaternion,
|
|
||||||
Scene,
|
Scene,
|
||||||
Vector3,
|
Vector3,
|
||||||
WebGLRenderer,
|
WebGLRenderer,
|
||||||
@ -20,32 +18,26 @@ import {
|
|||||||
Intersection,
|
Intersection,
|
||||||
Object3D,
|
Object3D,
|
||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
|
BoxGeometry,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
|
||||||
import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
|
||||||
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { deg2Rad } from 'lib/utils2d'
|
|
||||||
import * as TWEEN from '@tweenjs/tween.js'
|
import * as TWEEN from '@tweenjs/tween.js'
|
||||||
import { MouseGuard, cameraMouseDragGuards } from 'lib/cameraControls'
|
|
||||||
import { SourceRange } from 'lang/wasm'
|
import { SourceRange } from 'lang/wasm'
|
||||||
import { Axis } from 'lib/selections'
|
import { Axis } from 'lib/selections'
|
||||||
import { BaseUnit, SETTINGS_PERSIST_KEY } from 'machines/settingsMachine'
|
import { BaseUnit, SETTINGS_PERSIST_KEY } from 'machines/settingsMachine'
|
||||||
|
import { CameraControls } from './CameraControls'
|
||||||
|
|
||||||
type SendType = ReturnType<typeof useModelingContext>['send']
|
type SendType = ReturnType<typeof useModelingContext>['send']
|
||||||
|
|
||||||
// 63.5 is definitely a bit of a magic number, play with it until it looked right
|
// 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
|
// 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
|
// power of 2 is used, but it's the 0.5 seems to make things look much more correct
|
||||||
const ZOOM_MAGIC_NUMBER = 63.5
|
export const ZOOM_MAGIC_NUMBER = 63.5
|
||||||
const FRAMES_TO_ANIMATE_IN = 30
|
|
||||||
const ORTHOGRAPHIC_CAMERA_SIZE = 20
|
|
||||||
|
|
||||||
export const INTERSECTION_PLANE_LAYER = 1
|
export const INTERSECTION_PLANE_LAYER = 1
|
||||||
export const SKETCH_LAYER = 2
|
export const SKETCH_LAYER = 2
|
||||||
const DEBUG_SHOW_INTERSECTION_PLANE = false
|
export const DEBUG_SHOW_INTERSECTION_PLANE = false
|
||||||
export const DEBUG_SHOW_BOTH_SCENES = false
|
export const DEBUG_SHOW_BOTH_SCENES = false
|
||||||
|
|
||||||
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
||||||
@ -57,100 +49,6 @@ export const AXIS_GROUP = 'axisGroup'
|
|||||||
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
||||||
export const ARROWHEAD = 'arrowhead'
|
export const ARROWHEAD = 'arrowhead'
|
||||||
|
|
||||||
const tempQuaternion = new Quaternion() // just used for maths
|
|
||||||
|
|
||||||
interface ThreeCamValues {
|
|
||||||
position: Vector3
|
|
||||||
quaternion: Quaternion
|
|
||||||
zoom: number
|
|
||||||
isPerspective: boolean
|
|
||||||
target: Vector3
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastCmdDelay = 50
|
|
||||||
|
|
||||||
let lastCmd: EngineCommand | null = null
|
|
||||||
let lastCmdTime: number = Date.now()
|
|
||||||
let lastCmdTimeoutId: number | null = null
|
|
||||||
|
|
||||||
const sendLastReliableChannel = () => {
|
|
||||||
if (lastCmd && Date.now() - lastCmdTime >= lastCmdDelay) {
|
|
||||||
engineCommandManager.sendSceneCommand(lastCmd, true)
|
|
||||||
lastCmdTime = Date.now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
|
|
||||||
const cmd: EngineCommand = {
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
...convertThreeCamValuesToEngineCam(threeValues),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
engineCommandManager.sendSceneCommand(cmd)
|
|
||||||
lastCmd = cmd
|
|
||||||
lastCmdTime = Date.now()
|
|
||||||
|
|
||||||
if (lastCmdTimeoutId !== null) {
|
|
||||||
clearTimeout(lastCmdTimeoutId)
|
|
||||||
}
|
|
||||||
lastCmdTimeoutId = setTimeout(
|
|
||||||
sendLastReliableChannel,
|
|
||||||
lastCmdDelay
|
|
||||||
) as any as number
|
|
||||||
}, 1000 / 30)
|
|
||||||
|
|
||||||
let lastPerspectiveCmd: EngineCommand | null = null
|
|
||||||
let lastPerspectiveCmdTime: number = Date.now()
|
|
||||||
let lastPerspectiveCmdTimeoutId: number | null = null
|
|
||||||
|
|
||||||
const sendLastPerspectiveReliableChannel = () => {
|
|
||||||
if (
|
|
||||||
lastPerspectiveCmd &&
|
|
||||||
Date.now() - lastPerspectiveCmdTime >= lastCmdDelay
|
|
||||||
) {
|
|
||||||
engineCommandManager.sendSceneCommand(lastPerspectiveCmd, true)
|
|
||||||
lastPerspectiveCmdTime = Date.now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const throttledUpdateEngineFov = throttle(
|
|
||||||
(vals: {
|
|
||||||
position: Vector3
|
|
||||||
quaternion: Quaternion
|
|
||||||
zoom: number
|
|
||||||
fov: number
|
|
||||||
target: Vector3
|
|
||||||
}) => {
|
|
||||||
const cmd: EngineCommand = {
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_perspective_settings',
|
|
||||||
...convertThreeCamValuesToEngineCam({
|
|
||||||
...vals,
|
|
||||||
isPerspective: true,
|
|
||||||
}),
|
|
||||||
fov_y: vals.fov,
|
|
||||||
...calculateNearFarFromFOV(vals.fov),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
engineCommandManager.sendSceneCommand(cmd)
|
|
||||||
lastPerspectiveCmd = cmd
|
|
||||||
lastPerspectiveCmdTime = Date.now()
|
|
||||||
if (lastPerspectiveCmdTimeoutId !== null) {
|
|
||||||
clearTimeout(lastPerspectiveCmdTimeoutId)
|
|
||||||
}
|
|
||||||
lastPerspectiveCmdTimeoutId = setTimeout(
|
|
||||||
sendLastPerspectiveReliableChannel,
|
|
||||||
lastCmdDelay
|
|
||||||
) as any as number
|
|
||||||
},
|
|
||||||
1000 / 15
|
|
||||||
)
|
|
||||||
|
|
||||||
interface BaseCallbackArgs2 {
|
interface BaseCallbackArgs2 {
|
||||||
object: any
|
object: any
|
||||||
event: any
|
event: any
|
||||||
@ -178,34 +76,18 @@ interface onMoveCallbackArgs {
|
|||||||
intersection: Intersection<Object3D<Object3DEventMap>>
|
intersection: Intersection<Object3D<Object3DEventMap>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReactCameraProperties =
|
|
||||||
| {
|
|
||||||
type: 'perspective'
|
|
||||||
fov?: number
|
|
||||||
position: [number, number, number]
|
|
||||||
quaternion: [number, number, number, number]
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'orthographic'
|
|
||||||
zoom?: number
|
|
||||||
position: [number, number, number]
|
|
||||||
quaternion: [number, number, number, number]
|
|
||||||
}
|
|
||||||
|
|
||||||
// This singleton class is responsible for all of the under the hood setup for the client side scene.
|
// 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.
|
// 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
|
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
|
||||||
class SceneInfra {
|
class SceneInfra {
|
||||||
static instance: SceneInfra
|
static instance: SceneInfra
|
||||||
scene: Scene
|
scene: Scene
|
||||||
camera: PerspectiveCamera | OrthographicCamera
|
|
||||||
renderer: WebGLRenderer
|
renderer: WebGLRenderer
|
||||||
controls: OrbitControls
|
camControls: CameraControls
|
||||||
isPerspective = true
|
isPerspective = true
|
||||||
fov = 45
|
fov = 45
|
||||||
fovBeforeAnimate = 45
|
fovBeforeAnimate = 45
|
||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
|
||||||
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
onMoveCallback: (arg: onMoveCallbackArgs) => void = () => {}
|
onMoveCallback: (arg: onMoveCallbackArgs) => void = () => {}
|
||||||
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
||||||
@ -256,55 +138,18 @@ class SceneInfra {
|
|||||||
selectedObject: null | any = null
|
selectedObject: null | any = null
|
||||||
mouseDownVector: null | Vector2 = null
|
mouseDownVector: null | Vector2 = null
|
||||||
|
|
||||||
// reacts hooks into some of this singleton's properties
|
|
||||||
reactCameraProperties: ReactCameraProperties = {
|
|
||||||
type: 'perspective',
|
|
||||||
fov: 12,
|
|
||||||
position: [0, 0, 0],
|
|
||||||
quaternion: [0, 0, 0, 1],
|
|
||||||
}
|
|
||||||
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
|
||||||
setReactCameraPropertiesCallback = (
|
|
||||||
cb: (a: ReactCameraProperties) => void
|
|
||||||
) => {
|
|
||||||
this.reactCameraPropertiesCallback = cb
|
|
||||||
}
|
|
||||||
setCam = (camProps: ReactCameraProperties) => {
|
|
||||||
if (
|
|
||||||
camProps.type === 'perspective' &&
|
|
||||||
this.camera instanceof OrthographicCamera
|
|
||||||
) {
|
|
||||||
this.usePerspectiveCamera()
|
|
||||||
} else if (
|
|
||||||
camProps.type === 'orthographic' &&
|
|
||||||
this.camera instanceof PerspectiveCamera
|
|
||||||
) {
|
|
||||||
this.useOrthographicCamera()
|
|
||||||
}
|
|
||||||
this.camera.position.set(...camProps.position)
|
|
||||||
this.camera.quaternion.set(...camProps.quaternion)
|
|
||||||
if (
|
|
||||||
camProps.type === 'perspective' &&
|
|
||||||
this.camera instanceof PerspectiveCamera
|
|
||||||
) {
|
|
||||||
// not sure what to do here, calling dollyZoom here is buggy because it updates the position
|
|
||||||
// at the same time
|
|
||||||
} else if (
|
|
||||||
camProps.type === 'orthographic' &&
|
|
||||||
this.camera instanceof OrthographicCamera
|
|
||||||
) {
|
|
||||||
this.camera.zoom = camProps.zoom || 1
|
|
||||||
}
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
this.controls.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// SCENE
|
// SCENE
|
||||||
this.scene = new Scene()
|
this.scene = new Scene()
|
||||||
this.scene.background = new Color(0x000000)
|
this.scene.background = new Color(0x000000)
|
||||||
this.scene.background = null
|
this.scene.background = null
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
window.addEventListener('resize', this.onWindowResize)
|
||||||
|
|
||||||
// CAMERA
|
// CAMERA
|
||||||
const camHeightDistanceRatio = 0.5
|
const camHeightDistanceRatio = 0.5
|
||||||
const baseUnit: BaseUnit =
|
const baseUnit: BaseUnit =
|
||||||
@ -315,25 +160,19 @@ class SceneInfra {
|
|||||||
const ang = Math.atan(camHeightDistanceRatio)
|
const ang = Math.atan(camHeightDistanceRatio)
|
||||||
const x = Math.cos(ang) * length
|
const x = Math.cos(ang) * length
|
||||||
const y = Math.sin(ang) * length
|
const y = Math.sin(ang) * length
|
||||||
this.camera = this.createPerspectiveCamera()
|
|
||||||
this.camera.position.set(0, -x, y)
|
|
||||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
|
||||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
|
||||||
|
|
||||||
// RENDERER
|
this.camControls = new CameraControls(false, this.renderer.domElement)
|
||||||
this.renderer = new WebGLRenderer({ antialias: true, alpha: true }) // Enable transparency
|
this.camControls.subscribeToCamChange(() => this.onCameraChange())
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
this.camControls.camera.layers.enable(SKETCH_LAYER)
|
||||||
this.renderer.setClearColor(0x000000, 0) // Set clear color to black with 0 alpha (fully transparent)
|
this.camControls.camera.position.set(0, -x, y)
|
||||||
window.addEventListener('resize', this.onWindowResize)
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||||
|
this.camControls.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
|
|
||||||
// RAYCASTERS
|
// RAYCASTERS
|
||||||
this.raycaster.layers.enable(SKETCH_LAYER)
|
this.raycaster.layers.enable(SKETCH_LAYER)
|
||||||
this.raycaster.layers.disable(0)
|
this.raycaster.layers.disable(0)
|
||||||
this.planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
|
this.planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
|
|
||||||
// CONTROLS
|
|
||||||
this.controls = this.setupOrbitControls()
|
|
||||||
|
|
||||||
// GRID
|
// GRID
|
||||||
const size = 100
|
const size = 100
|
||||||
const divisions = 10
|
const divisions = 10
|
||||||
@ -353,415 +192,40 @@ class SceneInfra {
|
|||||||
|
|
||||||
SceneInfra.instance = this
|
SceneInfra.instance = this
|
||||||
}
|
}
|
||||||
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
|
||||||
() => {}
|
|
||||||
setIsCamMovingCallback(cb: (isMoving: boolean, isTween: boolean) => void) {
|
|
||||||
this._isCamMovingCallback = cb
|
|
||||||
}
|
|
||||||
private _onCamChange: () => void = () => {}
|
|
||||||
setOnCamChange(cb: () => void) {
|
|
||||||
this._onCamChange = cb
|
|
||||||
}
|
|
||||||
setInteractionGuards = (guard: MouseGuard) => {
|
|
||||||
this.interactionGuards = guard
|
|
||||||
// setMouseGuards is oun patch-package patch to orbit controls
|
|
||||||
// see patches/three+0.160.0.patch
|
|
||||||
;(this.controls as any).setMouseGuards(guard)
|
|
||||||
}
|
|
||||||
private createPerspectiveCamera = () => {
|
|
||||||
const { z_near, z_far } = calculateNearFarFromFOV(this.fov)
|
|
||||||
this.camera = new PerspectiveCamera(
|
|
||||||
this.fov,
|
|
||||||
window.innerWidth / window.innerHeight,
|
|
||||||
z_near,
|
|
||||||
z_far
|
|
||||||
)
|
|
||||||
this.camera.up.set(0, 0, 1)
|
|
||||||
this.camera.layers.enable(SKETCH_LAYER)
|
|
||||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
|
||||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
|
||||||
|
|
||||||
return this.camera
|
|
||||||
}
|
|
||||||
setupOrbitControls = (target?: [number, number, number]): OrbitControls => {
|
|
||||||
if (this.controls) this.controls.dispose()
|
|
||||||
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
|
|
||||||
if (target) {
|
|
||||||
// if we're swapping from perspective to orthographic,
|
|
||||||
// we'll need to recreate the orbit controls
|
|
||||||
// and most likely want the target to be the same
|
|
||||||
this.controls.target.set(...target)
|
|
||||||
}
|
|
||||||
this.controls.update()
|
|
||||||
this.controls.addEventListener('change', this.onCameraChange)
|
|
||||||
// debounce is needed because the start and end events are fired too often for zoom on scroll
|
|
||||||
let debounceTimer = 0
|
|
||||||
const handleStart = () => {
|
|
||||||
if (debounceTimer) clearTimeout(debounceTimer)
|
|
||||||
this._isCamMovingCallback(true, false)
|
|
||||||
}
|
|
||||||
const handleEnd = () => {
|
|
||||||
debounceTimer = setTimeout(() => {
|
|
||||||
this._isCamMovingCallback(false, false)
|
|
||||||
}, 400) as any as number
|
|
||||||
}
|
|
||||||
this.controls.addEventListener('start', handleStart)
|
|
||||||
this.controls.addEventListener('end', handleEnd)
|
|
||||||
|
|
||||||
// setMouseGuards is oun patch-package patch to orbit controls
|
|
||||||
// see patches/three+0.160.0.patch
|
|
||||||
;(this.controls as any).setMouseGuards(this.interactionGuards)
|
|
||||||
return this.controls
|
|
||||||
}
|
|
||||||
onStreamStart = () => this.onCameraChange()
|
|
||||||
|
|
||||||
deferReactUpdate = throttle((a: ReactCameraProperties) => {
|
|
||||||
this.reactCameraPropertiesCallback(a)
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
onCameraChange = () => {
|
onCameraChange = () => {
|
||||||
const scale = getSceneScale(this.camera, this.controls.target)
|
const scale = getSceneScale(
|
||||||
|
this.camControls.camera,
|
||||||
|
this.camControls.target
|
||||||
|
)
|
||||||
const planesGroup = this.scene.getObjectByName(DEFAULT_PLANES)
|
const planesGroup = this.scene.getObjectByName(DEFAULT_PLANES)
|
||||||
const axisGroup = this.scene
|
const axisGroup = this.scene
|
||||||
.getObjectByName(AXIS_GROUP)
|
.getObjectByName(AXIS_GROUP)
|
||||||
?.getObjectByName('gridHelper')
|
?.getObjectByName('gridHelper')
|
||||||
planesGroup && planesGroup.scale.set(scale, scale, scale)
|
planesGroup && planesGroup.scale.set(scale, scale, scale)
|
||||||
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
||||||
|
|
||||||
throttledUpdateEngineCamera({
|
|
||||||
quaternion: this.camera.quaternion,
|
|
||||||
position: this.camera.position,
|
|
||||||
zoom: this.camera.zoom,
|
|
||||||
isPerspective: this.isPerspective,
|
|
||||||
target: this.controls.target,
|
|
||||||
})
|
|
||||||
this.deferReactUpdate({
|
|
||||||
type:
|
|
||||||
this.camera instanceof PerspectiveCamera
|
|
||||||
? 'perspective'
|
|
||||||
: 'orthographic',
|
|
||||||
[this.camera instanceof PerspectiveCamera ? 'fov' : 'zoom']:
|
|
||||||
this.camera instanceof PerspectiveCamera
|
|
||||||
? this.camera.fov
|
|
||||||
: this.camera.zoom,
|
|
||||||
position: [
|
|
||||||
roundOff(this.camera.position.x, 2),
|
|
||||||
roundOff(this.camera.position.y, 2),
|
|
||||||
roundOff(this.camera.position.z, 2),
|
|
||||||
],
|
|
||||||
quaternion: [
|
|
||||||
roundOff(this.camera.quaternion.x, 2),
|
|
||||||
roundOff(this.camera.quaternion.y, 2),
|
|
||||||
roundOff(this.camera.quaternion.z, 2),
|
|
||||||
roundOff(this.camera.quaternion.w, 2),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
this._onCamChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onWindowResize = () => {
|
onWindowResize = () => {
|
||||||
if (this.camera instanceof PerspectiveCamera) {
|
|
||||||
this.camera.aspect = window.innerWidth / window.innerHeight
|
|
||||||
} else if (this.camera instanceof OrthographicCamera) {
|
|
||||||
const aspect = window.innerWidth / window.innerHeight
|
|
||||||
this.camera.left = -ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
|
||||||
this.camera.right = ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
|
||||||
this.camera.top = ORTHOGRAPHIC_CAMERA_SIZE
|
|
||||||
this.camera.bottom = -ORTHOGRAPHIC_CAMERA_SIZE
|
|
||||||
}
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
animate = () => {
|
animate = () => {
|
||||||
requestAnimationFrame(this.animate)
|
requestAnimationFrame(this.animate)
|
||||||
TWEEN.update() // This will update all tweens during the animation loop
|
TWEEN.update() // This will update all tweens during the animation loop
|
||||||
if (!this.isFovAnimationInProgress)
|
if (!this.isFovAnimationInProgress) {
|
||||||
this.renderer.render(this.scene, this.camera)
|
// console.log('animation frame', this.cameraControls.camera)
|
||||||
}
|
this.camControls.update()
|
||||||
async tweenCameraToQuaternion(
|
this.renderer.render(this.scene, this.camControls.camera)
|
||||||
targetQuaternion: Quaternion,
|
|
||||||
duration = 500,
|
|
||||||
toOrthographic = true
|
|
||||||
): Promise<void> {
|
|
||||||
const isVertical = isQuaternionVertical(targetQuaternion)
|
|
||||||
let _duration = duration
|
|
||||||
if (isVertical) {
|
|
||||||
_duration = duration * 0.6
|
|
||||||
await this._tweenCameraToQuaternion(new Quaternion(), _duration, false)
|
|
||||||
}
|
}
|
||||||
await this._tweenCameraToQuaternion(
|
|
||||||
targetQuaternion,
|
|
||||||
_duration,
|
|
||||||
toOrthographic
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_tweenCameraToQuaternion(
|
|
||||||
targetQuaternion: Quaternion,
|
|
||||||
duration = 500,
|
|
||||||
toOrthographic = false
|
|
||||||
): Promise<void> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const camera = this.camera
|
|
||||||
this._isCamMovingCallback(true, true)
|
|
||||||
const initialQuaternion = camera.quaternion.clone()
|
|
||||||
const isVertical = isQuaternionVertical(targetQuaternion)
|
|
||||||
let tweenEnd = isVertical ? 0.99 : 1
|
|
||||||
const controlsTarget = this.controls.target.clone()
|
|
||||||
const initialDistance = controlsTarget.distanceTo(camera.position.clone())
|
|
||||||
|
|
||||||
const cameraAtTime = (animationProgress: number /* 0 - 1 */) => {
|
|
||||||
const currentQ = tempQuaternion.slerpQuaternions(
|
|
||||||
initialQuaternion,
|
|
||||||
targetQuaternion,
|
|
||||||
animationProgress
|
|
||||||
)
|
|
||||||
if (this.camera instanceof PerspectiveCamera)
|
|
||||||
// changing the camera position back when it's orthographic doesn't do anything
|
|
||||||
// and it messes up animating back to perspective later
|
|
||||||
this.camera.position
|
|
||||||
.set(0, 0, 1)
|
|
||||||
.applyQuaternion(currentQ)
|
|
||||||
.multiplyScalar(initialDistance)
|
|
||||||
.add(controlsTarget)
|
|
||||||
|
|
||||||
this.camera.up.set(0, 1, 0).applyQuaternion(currentQ).normalize()
|
|
||||||
this.camera.quaternion.copy(currentQ)
|
|
||||||
this.controls.target.copy(controlsTarget)
|
|
||||||
this.controls.update()
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onComplete = async () => {
|
|
||||||
if (isReducedMotion() && toOrthographic) {
|
|
||||||
cameraAtTime(0.99)
|
|
||||||
this.useOrthographicCamera()
|
|
||||||
} else if (toOrthographic) {
|
|
||||||
await this.animateToOrthographic()
|
|
||||||
}
|
|
||||||
if (isVertical) cameraAtTime(1)
|
|
||||||
this.camera.up.set(0, 0, 1)
|
|
||||||
this.controls.enableRotate = false
|
|
||||||
this._isCamMovingCallback(false, true)
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isReducedMotion()) {
|
|
||||||
onComplete()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
new TWEEN.Tween({ t: 0 })
|
|
||||||
.to({ t: tweenEnd }, duration)
|
|
||||||
.easing(TWEEN.Easing.Quadratic.InOut)
|
|
||||||
.onUpdate(({ t }) => cameraAtTime(t))
|
|
||||||
.onComplete(onComplete)
|
|
||||||
.start()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
animateToOrthographic = () =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
this.isFovAnimationInProgress = true
|
|
||||||
let currentFov = this.fov
|
|
||||||
this.fovBeforeAnimate = this.fov
|
|
||||||
|
|
||||||
const targetFov = 4
|
|
||||||
const fovAnimationStep = (currentFov - targetFov) / FRAMES_TO_ANIMATE_IN
|
|
||||||
let frameWaitOnFinish = 10
|
|
||||||
|
|
||||||
const animateFovChange = () => {
|
|
||||||
if (this.camera instanceof PerspectiveCamera) {
|
|
||||||
if (this.camera.fov > targetFov) {
|
|
||||||
// Decrease the FOV
|
|
||||||
currentFov = Math.max(currentFov - fovAnimationStep, targetFov)
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
this.dollyZoom(currentFov)
|
|
||||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
|
||||||
} else if (frameWaitOnFinish > 0) {
|
|
||||||
frameWaitOnFinish--
|
|
||||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
|
||||||
} else {
|
|
||||||
// Once the target FOV is reached, switch to the orthographic camera
|
|
||||||
// Needs to wait a couple frames after the FOV animation is complete
|
|
||||||
this.useOrthographicCamera()
|
|
||||||
this.isFovAnimationInProgress = false
|
|
||||||
resolve(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
animateFovChange() // Start the animation
|
|
||||||
})
|
|
||||||
|
|
||||||
animateToPerspective = () =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
this.isFovAnimationInProgress = true
|
|
||||||
// Immediately set the camera to perspective with a very low FOV
|
|
||||||
this.fov = 4
|
|
||||||
let currentFov = 4
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
const targetFov = this.fovBeforeAnimate // Target FOV for perspective
|
|
||||||
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
|
|
||||||
this.usePerspectiveCamera()
|
|
||||||
|
|
||||||
const animateFovChange = () => {
|
|
||||||
if (this.camera instanceof OrthographicCamera) return
|
|
||||||
if (this.camera.fov < targetFov) {
|
|
||||||
// Increase the FOV
|
|
||||||
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
|
|
||||||
// this.camera.fov = currentFov
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
this.dollyZoom(currentFov)
|
|
||||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
|
||||||
} else {
|
|
||||||
// Set the flag to false as the FOV animation is complete
|
|
||||||
this.isFovAnimationInProgress = false
|
|
||||||
resolve(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
animateFovChange() // Start the animation
|
|
||||||
})
|
|
||||||
dispose = () => {
|
dispose = () => {
|
||||||
// Dispose of scene resources, renderer, and controls
|
// Dispose of scene resources, renderer, and controls
|
||||||
this.renderer.dispose()
|
this.renderer.dispose()
|
||||||
window.removeEventListener('resize', this.onWindowResize)
|
window.removeEventListener('resize', this.onWindowResize)
|
||||||
// Dispose of any other resources like geometries, materials, textures
|
// Dispose of any other resources like geometries, materials, textures
|
||||||
}
|
}
|
||||||
|
|
||||||
useOrthographicCamera = () => {
|
|
||||||
this.isPerspective = false
|
|
||||||
const { x: px, y: py, z: pz } = this.camera.position
|
|
||||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
|
||||||
const { x: tx, y: ty, z: tz } = this.controls.target
|
|
||||||
const aspect = window.innerWidth / window.innerHeight
|
|
||||||
const { z_near, z_far } = calculateNearFarFromFOV(this.fov)
|
|
||||||
this.camera = new OrthographicCamera(
|
|
||||||
-ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
|
||||||
ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
|
||||||
ORTHOGRAPHIC_CAMERA_SIZE,
|
|
||||||
-ORTHOGRAPHIC_CAMERA_SIZE,
|
|
||||||
z_near,
|
|
||||||
z_far
|
|
||||||
)
|
|
||||||
this.camera.up.set(0, 0, 1)
|
|
||||||
this.camera.layers.enable(SKETCH_LAYER)
|
|
||||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
|
||||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
|
||||||
this.camera.position.set(px, py, pz)
|
|
||||||
const distance = this.camera.position.distanceTo(new Vector3(tx, ty, tz))
|
|
||||||
const fovFactor = 45 / this.fov
|
|
||||||
this.camera.zoom = (ZOOM_MAGIC_NUMBER * fovFactor * 0.8) / distance
|
|
||||||
|
|
||||||
this.setupOrbitControls([tx, ty, tz])
|
|
||||||
this.camera.quaternion.set(qx, qy, qz, qw)
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
this.controls.update()
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_set_orthographic',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
usePerspectiveCamera = () => {
|
|
||||||
this.isPerspective = true
|
|
||||||
const { x: px, y: py, z: pz } = this.camera.position
|
|
||||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
|
||||||
const { x: tx, y: ty, z: tz } = this.controls.target
|
|
||||||
const zoom = this.camera.zoom
|
|
||||||
this.camera = this.createPerspectiveCamera()
|
|
||||||
|
|
||||||
this.camera.position.set(px, py, pz)
|
|
||||||
this.camera.quaternion.set(qx, qy, qz, qw)
|
|
||||||
const zoomFudgeFactor = 2280
|
|
||||||
const distance = zoomFudgeFactor / (zoom * this.fov)
|
|
||||||
const direction = new Vector3().subVectors(
|
|
||||||
this.camera.position,
|
|
||||||
this.controls.target
|
|
||||||
)
|
|
||||||
direction.normalize()
|
|
||||||
this.camera.position
|
|
||||||
.copy(this.controls.target)
|
|
||||||
.addScaledVector(direction, distance)
|
|
||||||
|
|
||||||
this.setupOrbitControls([tx, ty, tz])
|
|
||||||
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_set_perspective',
|
|
||||||
parameters: {
|
|
||||||
fov_y: this.camera.fov,
|
|
||||||
...calculateNearFarFromFOV(this.fov),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
this.onCameraChange()
|
|
||||||
return this.camera
|
|
||||||
}
|
|
||||||
|
|
||||||
dollyZoom = (newFov: number) => {
|
|
||||||
if (!(this.camera instanceof PerspectiveCamera)) {
|
|
||||||
console.warn('Dolly zoom is only applicable to perspective cameras.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.fov = newFov
|
|
||||||
|
|
||||||
// Calculate the direction vector from the camera towards the controls target
|
|
||||||
const direction = new Vector3()
|
|
||||||
.subVectors(this.controls.target, this.camera.position)
|
|
||||||
.normalize()
|
|
||||||
|
|
||||||
// Calculate the distance to the controls target before changing the FOV
|
|
||||||
const distanceBefore = this.camera.position.distanceTo(this.controls.target)
|
|
||||||
|
|
||||||
// Calculate the scale factor for the new FOV compared to the old one
|
|
||||||
// This needs to be calculated before updating the camera's FOV
|
|
||||||
const oldFov = this.camera.fov
|
|
||||||
|
|
||||||
const viewHeightFactor = (fov: number) => {
|
|
||||||
/* *
|
|
||||||
/|
|
|
||||||
/ |
|
|
||||||
/ |
|
|
||||||
/ |
|
|
||||||
/ | viewHeight/2
|
|
||||||
/ |
|
|
||||||
/ |
|
|
||||||
/↙️fov/2 |
|
|
||||||
/________|
|
|
||||||
\ |
|
|
||||||
\._._._.|
|
|
||||||
*/
|
|
||||||
return Math.tan(deg2Rad(fov / 2))
|
|
||||||
}
|
|
||||||
const scaleFactor = viewHeightFactor(oldFov) / viewHeightFactor(newFov)
|
|
||||||
|
|
||||||
this.camera.fov = newFov
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
|
|
||||||
const distanceAfter = distanceBefore * scaleFactor
|
|
||||||
|
|
||||||
const newPosition = this.controls.target
|
|
||||||
.clone()
|
|
||||||
.add(direction.multiplyScalar(-distanceAfter))
|
|
||||||
this.camera.position.copy(newPosition)
|
|
||||||
|
|
||||||
const { z_near, z_far } = calculateNearFarFromFOV(this.fov)
|
|
||||||
this.camera.near = z_near
|
|
||||||
this.camera.far = z_far
|
|
||||||
|
|
||||||
throttledUpdateEngineFov({
|
|
||||||
fov: newFov,
|
|
||||||
position: newPosition,
|
|
||||||
quaternion: this.camera.quaternion,
|
|
||||||
zoom: this.camera.zoom,
|
|
||||||
target: this.controls.target,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
getPlaneIntersectPoint = (): {
|
getPlaneIntersectPoint = (): {
|
||||||
intersection2d?: Vector2
|
intersection2d?: Vector2
|
||||||
intersectPoint: Vector3
|
intersectPoint: Vector3
|
||||||
@ -769,7 +233,7 @@ class SceneInfra {
|
|||||||
} | null => {
|
} | null => {
|
||||||
this.planeRaycaster.setFromCamera(
|
this.planeRaycaster.setFromCamera(
|
||||||
this.currentMouseVector,
|
this.currentMouseVector,
|
||||||
sceneInfra.camera
|
sceneInfra.camControls.camera
|
||||||
)
|
)
|
||||||
const planeIntersects = this.planeRaycaster.intersectObjects(
|
const planeIntersects = this.planeRaycaster.intersectObjects(
|
||||||
this.scene.children,
|
this.scene.children,
|
||||||
@ -907,7 +371,7 @@ class SceneInfra {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check the center point
|
// Check the center point
|
||||||
this.raycaster.setFromCamera(mouseDownVector, this.camera)
|
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
|
||||||
updateClosestIntersection(
|
updateClosestIntersection(
|
||||||
this.raycaster.intersectObjects(this.scene.children, true)
|
this.raycaster.intersectObjects(this.scene.children, true)
|
||||||
)
|
)
|
||||||
@ -922,7 +386,7 @@ class SceneInfra {
|
|||||||
mouseDownVector.x + offsetX,
|
mouseDownVector.x + offsetX,
|
||||||
mouseDownVector.y - offsetY
|
mouseDownVector.y - offsetY
|
||||||
)
|
)
|
||||||
this.raycaster.setFromCamera(ringVector, this.camera)
|
this.raycaster.setFromCamera(ringVector, this.camControls.camera)
|
||||||
updateClosestIntersection(
|
updateClosestIntersection(
|
||||||
this.raycaster.intersectObjects(this.scene.children, true)
|
this.raycaster.intersectObjects(this.scene.children, true)
|
||||||
)
|
)
|
||||||
@ -1015,7 +479,10 @@ class SceneInfra {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
planesGroup.layers.enable(SKETCH_LAYER)
|
planesGroup.layers.enable(SKETCH_LAYER)
|
||||||
const sceneScale = getSceneScale(this.camera, this.controls.target)
|
const sceneScale = getSceneScale(
|
||||||
|
this.camControls.camera,
|
||||||
|
this.camControls.target
|
||||||
|
)
|
||||||
planesGroup.scale.set(sceneScale, sceneScale, sceneScale)
|
planesGroup.scale.set(sceneScale, sceneScale, sceneScale)
|
||||||
this.scene.add(planesGroup)
|
this.scene.add(planesGroup)
|
||||||
}
|
}
|
||||||
@ -1050,52 +517,6 @@ class SceneInfra {
|
|||||||
|
|
||||||
export const sceneInfra = new SceneInfra()
|
export const sceneInfra = new SceneInfra()
|
||||||
|
|
||||||
function convertThreeCamValuesToEngineCam({
|
|
||||||
target,
|
|
||||||
position,
|
|
||||||
quaternion,
|
|
||||||
zoom,
|
|
||||||
isPerspective,
|
|
||||||
}: ThreeCamValues): {
|
|
||||||
center: Vector3
|
|
||||||
up: Vector3
|
|
||||||
vantage: Vector3
|
|
||||||
} {
|
|
||||||
// Something to consider is that the orbit controls have a target,
|
|
||||||
// we're kind of deriving the target/lookAtVector here when it might not be needed
|
|
||||||
// leaving for now since it's working but maybe revisit later
|
|
||||||
const euler = new Euler().setFromQuaternion(quaternion, 'XYZ')
|
|
||||||
|
|
||||||
const lookAtVector = new Vector3(0, 0, -1)
|
|
||||||
.applyEuler(euler)
|
|
||||||
.normalize()
|
|
||||||
.add(position)
|
|
||||||
|
|
||||||
const upVector = new Vector3(0, 1, 0).applyEuler(euler).normalize()
|
|
||||||
if (isPerspective) {
|
|
||||||
return {
|
|
||||||
center: target,
|
|
||||||
up: upVector,
|
|
||||||
vantage: position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const zoomFactor = -ZOOM_MAGIC_NUMBER / zoom
|
|
||||||
const direction = lookAtVector.clone().sub(position).normalize()
|
|
||||||
const newVantage = position.clone().add(direction.multiplyScalar(zoomFactor))
|
|
||||||
return {
|
|
||||||
center: lookAtVector,
|
|
||||||
up: upVector,
|
|
||||||
vantage: newVantage,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateNearFarFromFOV(fov: number) {
|
|
||||||
const nearFarRatio = (fov - 3) / (45 - 3)
|
|
||||||
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
|
||||||
const z_far = 1000 + nearFarRatio * (100000 - 1000)
|
|
||||||
return { z_near: 0.1, z_far }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSceneScale(
|
export function getSceneScale(
|
||||||
camera: PerspectiveCamera | OrthographicCamera,
|
camera: PerspectiveCamera | OrthographicCamera,
|
||||||
target: Vector3
|
target: Vector3
|
||||||
@ -1131,12 +552,6 @@ function baseUnitTomm(baseUnit: BaseUnit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isQuaternionVertical(q: Quaternion) {
|
|
||||||
const v = new Vector3(0, 0, 1).applyQuaternion(q)
|
|
||||||
// no x or y components means it's vertical
|
|
||||||
return compareVec2Epsilon2([v.x, v.y], [0, 0])
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DefaultPlane =
|
export type DefaultPlane =
|
||||||
| 'xy-default-plane'
|
| 'xy-default-plane'
|
||||||
| 'xz-default-plane'
|
| 'xz-default-plane'
|
||||||
|
@ -4,7 +4,7 @@ import { engineCommandManager } from 'lang/std/engineConnection'
|
|||||||
import { throttle, isReducedMotion } from 'lib/utils'
|
import { throttle, isReducedMotion } from 'lib/utils'
|
||||||
|
|
||||||
const updateDollyZoom = throttle(
|
const updateDollyZoom = throttle(
|
||||||
(newFov: number) => sceneInfra.dollyZoom(newFov),
|
(newFov: number) => sceneInfra.camControls.dollyZoom(newFov),
|
||||||
1000 / 15
|
1000 / 15
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,19 +15,19 @@ export const CamToggle = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
engineCommandManager.waitForReady.then(async () => {
|
engineCommandManager.waitForReady.then(async () => {
|
||||||
sceneInfra.dollyZoom(fov)
|
sceneInfra.camControls.dollyZoom(fov)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const toggleCamera = () => {
|
const toggleCamera = () => {
|
||||||
if (isPerspective) {
|
if (isPerspective) {
|
||||||
isReducedMotion()
|
isReducedMotion()
|
||||||
? sceneInfra.useOrthographicCamera()
|
? sceneInfra.camControls.useOrthographicCamera()
|
||||||
: sceneInfra.animateToOrthographic()
|
: sceneInfra.camControls.animateToOrthographic()
|
||||||
} else {
|
} else {
|
||||||
isReducedMotion()
|
isReducedMotion()
|
||||||
? sceneInfra.usePerspectiveCamera()
|
? sceneInfra.camControls.usePerspectiveCamera()
|
||||||
: sceneInfra.animateToPerspective()
|
: sceneInfra.camControls.animateToPerspective()
|
||||||
}
|
}
|
||||||
setIsPerspective(!isPerspective)
|
setIsPerspective(!isPerspective)
|
||||||
}
|
}
|
||||||
@ -60,9 +60,9 @@ export const CamToggle = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (enableRotate) {
|
if (enableRotate) {
|
||||||
sceneInfra.controls.enableRotate = false
|
sceneInfra.camControls.enableRotate = false
|
||||||
} else {
|
} else {
|
||||||
sceneInfra.controls.enableRotate = true
|
sceneInfra.camControls.enableRotate = true
|
||||||
}
|
}
|
||||||
setEnableRotate(!enableRotate)
|
setEnableRotate(!enableRotate)
|
||||||
}}
|
}}
|
||||||
|
@ -213,7 +213,7 @@ export const ModelingMachineProvider = ({
|
|||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
const quaternion = getSketchQuaternion(pathToNode, normal)
|
const quaternion = getSketchQuaternion(pathToNode, normal)
|
||||||
await sceneInfra.tweenCameraToQuaternion(quaternion)
|
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
||||||
return {
|
return {
|
||||||
sketchPathToNode: pathToNode,
|
sketchPathToNode: pathToNode,
|
||||||
sketchNormalBackUp: normal,
|
sketchNormalBackUp: normal,
|
||||||
@ -227,7 +227,7 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchPathToNode || [],
|
sketchPathToNode || [],
|
||||||
sketchNormalBackUp
|
sketchNormalBackUp
|
||||||
)
|
)
|
||||||
await sceneInfra.tweenCameraToQuaternion(quaternion)
|
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
||||||
},
|
},
|
||||||
'Get horizontal info': async ({
|
'Get horizontal info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
@ -1017,7 +1017,7 @@ export class EngineCommandManager {
|
|||||||
gizmo_mode: true,
|
gizmo_mode: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
sceneInfra.onStreamStart()
|
sceneInfra.camControls.onCameraChange()
|
||||||
|
|
||||||
this.initPlanes().then(() => {
|
this.initPlanes().then(() => {
|
||||||
executeCode(undefined, true)
|
executeCode(undefined, true)
|
||||||
|
@ -39,30 +39,36 @@ export interface MouseGuard {
|
|||||||
rotate: MouseGuardHandler
|
rotate: MouseGuardHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const butName = (e: React.MouseEvent) => ({
|
||||||
|
middle: !!(e.buttons & 4),
|
||||||
|
right: !!(e.buttons & 2),
|
||||||
|
left: !!(e.buttons & 1),
|
||||||
|
})
|
||||||
|
|
||||||
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||||
KittyCAD: {
|
KittyCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Shift + drag or middle click + drag',
|
description: 'Right click + Shift + drag or middle click + drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(e.button === 1 && noModifiersPressed(e)) ||
|
(butName(e).middle && noModifiersPressed(e)) ||
|
||||||
(e.button === 2 && e.shiftKey),
|
(butName(e).right && e.shiftKey),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
description: 'Scroll wheel or Right click + Ctrl + drag',
|
||||||
dragCallback: (e) => e.button === 2 && e.ctrlKey,
|
dragCallback: (e) => !!(e.buttons & 2) && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Right click + drag',
|
description: 'Right click + drag',
|
||||||
callback: (e) => e.button === 2 && noModifiersPressed(e),
|
callback: (e) => butName(e).right && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OnShape: {
|
OnShape: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Ctrl + drag or middle click + drag',
|
description: 'Right click + Ctrl + drag or middle click + drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(e.button === 2 && e.ctrlKey) ||
|
(butName(e).right && e.ctrlKey) ||
|
||||||
(e.button === 1 && noModifiersPressed(e)),
|
(butName(e).middle && noModifiersPressed(e)),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel',
|
description: 'Scroll wheel',
|
||||||
@ -71,77 +77,77 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Right click + drag',
|
description: 'Right click + drag',
|
||||||
callback: (e) => e.button === 2 && noModifiersPressed(e),
|
callback: (e) => butName(e).right && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'Trackpad Friendly': {
|
'Trackpad Friendly': {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Left click + Alt + Shift + drag or middle click + drag',
|
description: 'Left click + Alt + Shift + drag or middle click + drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
(e.button === 0 && e.altKey && e.shiftKey && !e.metaKey) ||
|
(butName(e).left && e.altKey && e.shiftKey && !e.metaKey) ||
|
||||||
(e.button === 1 && noModifiersPressed(e)),
|
(butName(e).middle && noModifiersPressed(e)),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Left click + Alt + OS + drag',
|
description: 'Scroll wheel or Left click + Alt + OS + drag',
|
||||||
dragCallback: (e) => e.button === 0 && e.altKey && e.metaKey,
|
dragCallback: (e) => butName(e).left && e.altKey && e.metaKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Left click + Alt + drag',
|
description: 'Left click + Alt + drag',
|
||||||
callback: (e) => e.button === 0 && e.altKey && !e.shiftKey && !e.metaKey,
|
callback: (e) => butName(e).left && e.altKey && !e.shiftKey && !e.metaKey,
|
||||||
lenientDragStartButton: 0,
|
lenientDragStartButton: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Solidworks: {
|
Solidworks: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Right click + Ctrl + drag',
|
description: 'Right click + Ctrl + drag',
|
||||||
callback: (e) => e.button === 2 && e.ctrlKey,
|
callback: (e) => butName(e).right && e.ctrlKey,
|
||||||
lenientDragStartButton: 2,
|
lenientDragStartButton: 2,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Shift + drag',
|
description: 'Scroll wheel or Middle click + Shift + drag',
|
||||||
dragCallback: (e) => e.button === 1 && e.shiftKey,
|
dragCallback: (e) => butName(e).middle && e.shiftKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NX: {
|
NX: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Middle click + Shift + drag',
|
||||||
callback: (e) => e.button === 1 && e.shiftKey,
|
callback: (e) => butName(e).middle && e.shiftKey,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
||||||
dragCallback: (e) => e.button === 1 && e.ctrlKey,
|
dragCallback: (e) => butName(e).middle && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Creo: {
|
Creo: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Middle click + Shift + drag',
|
||||||
callback: (e) => e.button === 1 && e.shiftKey,
|
callback: (e) => butName(e).middle && e.shiftKey,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
||||||
dragCallback: (e) => e.button === 1 && e.ctrlKey,
|
dragCallback: (e) => butName(e).middle && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AutoCAD: {
|
AutoCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Middle click + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => e.button === 1 && noModifiersPressed(e),
|
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel',
|
description: 'Scroll wheel',
|
||||||
@ -150,7 +156,7 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle click + Shift + drag',
|
description: 'Middle click + Shift + drag',
|
||||||
callback: (e) => e.button === 1 && e.shiftKey,
|
callback: (e) => butName(e).middle && e.shiftKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
124
yarn.lock
124
yarn.lock
@ -2943,11 +2943,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@xstate/machine-extractor" "^0.16.0"
|
"@xstate/machine-extractor" "^0.16.0"
|
||||||
|
|
||||||
"@yarnpkg/lockfile@^1.1.0":
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
|
|
||||||
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
|
|
||||||
|
|
||||||
acorn-jsx@^5.3.2:
|
acorn-jsx@^5.3.2:
|
||||||
version "5.3.2"
|
version "5.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||||
@ -3243,11 +3238,6 @@ asynckit@^0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||||
|
|
||||||
at-least-node@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
|
||||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
|
||||||
|
|
||||||
autoprefixer@^10.4.13:
|
autoprefixer@^10.4.13:
|
||||||
version "10.4.14"
|
version "10.4.14"
|
||||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
|
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
|
||||||
@ -3670,7 +3660,7 @@ chromium-bidi@0.4.16:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mitt "3.0.0"
|
mitt "3.0.0"
|
||||||
|
|
||||||
ci-info@^3.2.0, ci-info@^3.7.0:
|
ci-info@^3.2.0:
|
||||||
version "3.9.0"
|
version "3.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
||||||
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
||||||
@ -4890,13 +4880,6 @@ find-up@^6.3.0:
|
|||||||
locate-path "^7.1.0"
|
locate-path "^7.1.0"
|
||||||
path-exists "^5.0.0"
|
path-exists "^5.0.0"
|
||||||
|
|
||||||
find-yarn-workspace-root@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
|
|
||||||
integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
|
|
||||||
dependencies:
|
|
||||||
micromatch "^4.0.2"
|
|
||||||
|
|
||||||
flat-cache@^3.0.4:
|
flat-cache@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
|
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
|
||||||
@ -4991,16 +4974,6 @@ fs-extra@^8.1.0:
|
|||||||
jsonfile "^4.0.0"
|
jsonfile "^4.0.0"
|
||||||
universalify "^0.1.0"
|
universalify "^0.1.0"
|
||||||
|
|
||||||
fs-extra@^9.0.0:
|
|
||||||
version "9.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
|
||||||
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
|
|
||||||
dependencies:
|
|
||||||
at-least-node "^1.0.0"
|
|
||||||
graceful-fs "^4.2.0"
|
|
||||||
jsonfile "^6.0.1"
|
|
||||||
universalify "^2.0.0"
|
|
||||||
|
|
||||||
fs-minipass@^2.0.0:
|
fs-minipass@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||||
@ -5341,7 +5314,7 @@ got@^13.0.0:
|
|||||||
p-cancelable "^3.0.0"
|
p-cancelable "^3.0.0"
|
||||||
responselike "^3.0.0"
|
responselike "^3.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.9:
|
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.9:
|
||||||
version "4.2.11"
|
version "4.2.11"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||||
@ -5703,11 +5676,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-tostringtag "^1.0.0"
|
has-tostringtag "^1.0.0"
|
||||||
|
|
||||||
is-docker@^2.0.0:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
|
|
||||||
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
|
|
||||||
|
|
||||||
is-extglob@^2.1.1:
|
is-extglob@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||||
@ -5870,13 +5838,6 @@ is-weakset@^2.0.1:
|
|||||||
call-bind "^1.0.2"
|
call-bind "^1.0.2"
|
||||||
get-intrinsic "^1.1.1"
|
get-intrinsic "^1.1.1"
|
||||||
|
|
||||||
is-wsl@^2.1.1:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
|
||||||
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
|
||||||
dependencies:
|
|
||||||
is-docker "^2.0.0"
|
|
||||||
|
|
||||||
isarray@^2.0.5:
|
isarray@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||||
@ -6106,16 +6067,6 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||||
|
|
||||||
json-stable-stringify@^1.0.2:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454"
|
|
||||||
integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==
|
|
||||||
dependencies:
|
|
||||||
call-bind "^1.0.5"
|
|
||||||
isarray "^2.0.5"
|
|
||||||
jsonify "^0.0.1"
|
|
||||||
object-keys "^1.1.1"
|
|
||||||
|
|
||||||
json5@^1.0.2:
|
json5@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
||||||
@ -6140,20 +6091,6 @@ jsonfile@^4.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
jsonfile@^6.0.1:
|
|
||||||
version "6.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
|
||||||
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
|
||||||
dependencies:
|
|
||||||
universalify "^2.0.0"
|
|
||||||
optionalDependencies:
|
|
||||||
graceful-fs "^4.1.6"
|
|
||||||
|
|
||||||
jsonify@^0.0.1:
|
|
||||||
version "0.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
|
|
||||||
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
|
|
||||||
|
|
||||||
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3:
|
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3:
|
||||||
version "3.3.5"
|
version "3.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
|
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
|
||||||
@ -6171,13 +6108,6 @@ keyv@^4.5.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
json-buffer "3.0.1"
|
json-buffer "3.0.1"
|
||||||
|
|
||||||
klaw-sync@^6.0.0:
|
|
||||||
version "6.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
|
|
||||||
integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
|
|
||||||
dependencies:
|
|
||||||
graceful-fs "^4.1.11"
|
|
||||||
|
|
||||||
ky@^0.33.0:
|
ky@^0.33.0:
|
||||||
version "0.33.3"
|
version "0.33.3"
|
||||||
resolved "https://registry.yarnpkg.com/ky/-/ky-0.33.3.tgz#bf1ad322a3f2c3428c13cfa4b3af95e6c4a2f543"
|
resolved "https://registry.yarnpkg.com/ky/-/ky-0.33.3.tgz#bf1ad322a3f2c3428c13cfa4b3af95e6c4a2f543"
|
||||||
@ -6421,7 +6351,7 @@ meshoptimizer@~0.18.1:
|
|||||||
resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8"
|
resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8"
|
||||||
integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==
|
integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==
|
||||||
|
|
||||||
micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
|
micromatch@^4.0.4, micromatch@^4.0.5:
|
||||||
version "4.0.5"
|
version "4.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||||
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
||||||
@ -6850,14 +6780,6 @@ onetime@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^4.0.0"
|
mimic-fn "^4.0.0"
|
||||||
|
|
||||||
open@^7.4.2:
|
|
||||||
version "7.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
|
|
||||||
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
|
|
||||||
dependencies:
|
|
||||||
is-docker "^2.0.0"
|
|
||||||
is-wsl "^2.1.1"
|
|
||||||
|
|
||||||
openapi-types@^12.0.0:
|
openapi-types@^12.0.0:
|
||||||
version "12.1.3"
|
version "12.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3"
|
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3"
|
||||||
@ -7003,27 +6925,6 @@ parse-ms@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
|
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
|
||||||
integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==
|
integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==
|
||||||
|
|
||||||
patch-package@^8.0.0:
|
|
||||||
version "8.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61"
|
|
||||||
integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==
|
|
||||||
dependencies:
|
|
||||||
"@yarnpkg/lockfile" "^1.1.0"
|
|
||||||
chalk "^4.1.2"
|
|
||||||
ci-info "^3.7.0"
|
|
||||||
cross-spawn "^7.0.3"
|
|
||||||
find-yarn-workspace-root "^2.0.0"
|
|
||||||
fs-extra "^9.0.0"
|
|
||||||
json-stable-stringify "^1.0.2"
|
|
||||||
klaw-sync "^6.0.0"
|
|
||||||
minimist "^1.2.6"
|
|
||||||
open "^7.4.2"
|
|
||||||
rimraf "^2.6.3"
|
|
||||||
semver "^7.5.3"
|
|
||||||
slash "^2.0.0"
|
|
||||||
tmp "^0.0.33"
|
|
||||||
yaml "^2.2.2"
|
|
||||||
|
|
||||||
path-exists@^4.0.0:
|
path-exists@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||||
@ -7721,7 +7622,7 @@ rgb2hex@0.2.5:
|
|||||||
resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.2.5.tgz#f82230cd3ab1364fa73c99be3a691ed688f8dbdc"
|
resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.2.5.tgz#f82230cd3ab1364fa73c99be3a691ed688f8dbdc"
|
||||||
integrity sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==
|
integrity sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==
|
||||||
|
|
||||||
rimraf@2, rimraf@^2.6.3:
|
rimraf@2:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||||
@ -7839,7 +7740,7 @@ semver@^6.3.0, semver@^6.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||||
|
|
||||||
semver@^7.3.5, semver@^7.3.7, semver@^7.5.3:
|
semver@^7.3.5, semver@^7.3.7:
|
||||||
version "7.5.4"
|
version "7.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||||
@ -7917,11 +7818,6 @@ sketch-helpers@^0.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/sketch-helpers/-/sketch-helpers-0.0.4.tgz#c6e4257451cd65483ab99ff7d3b10da04e98374d"
|
resolved "https://registry.yarnpkg.com/sketch-helpers/-/sketch-helpers-0.0.4.tgz#c6e4257451cd65483ab99ff7d3b10da04e98374d"
|
||||||
integrity sha512-xSt+Ku4VFDk4fBW3kRj+raZ49fFSJ32q1ph05GKQvZ9mIUI+W2/3iJJSBfBWwIdxlNiMx6RoUe2O+5vwtkPT3A==
|
integrity sha512-xSt+Ku4VFDk4fBW3kRj+raZ49fFSJ32q1ph05GKQvZ9mIUI+W2/3iJJSBfBWwIdxlNiMx6RoUe2O+5vwtkPT3A==
|
||||||
|
|
||||||
slash@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
|
|
||||||
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
|
|
||||||
|
|
||||||
slash@^3.0.0:
|
slash@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||||
@ -8587,11 +8483,6 @@ universalify@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
universalify@^2.0.0:
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
|
||||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
|
||||||
|
|
||||||
unzipper@^0.10.14:
|
unzipper@^0.10.14:
|
||||||
version "0.10.14"
|
version "0.10.14"
|
||||||
resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1"
|
resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1"
|
||||||
@ -9084,11 +8975,6 @@ yaml@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
||||||
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
||||||
|
|
||||||
yaml@^2.2.2:
|
|
||||||
version "2.3.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
|
|
||||||
integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
|
|
||||||
|
|
||||||
yargs-parser@20.2.4:
|
yargs-parser@20.2.4:
|
||||||
version "20.2.4"
|
version "20.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
|
||||||
|
Reference in New Issue
Block a user