@pascal-app/editor 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/components/editor/custom-camera-controls.tsx +2 -1
- package/src/components/editor/editor-layout-v2.tsx +4 -3
- package/src/components/editor/first-person/build-collider-world.ts +5 -7
- package/src/components/editor/first-person/bvh-ecctrl.tsx +119 -54
- package/src/components/editor/first-person-controls.tsx +11 -11
- package/src/components/editor/floating-action-menu.tsx +0 -0
- package/src/components/editor/floorplan-panel.tsx +44 -37
- package/src/components/editor/index.tsx +68 -53
- package/src/components/editor/selection-manager.tsx +2 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +465 -0
- package/src/components/editor/thumbnail-generator.tsx +18 -61
- package/src/components/editor/use-floorplan-background-placement.ts +3 -3
- package/src/components/editor/wall-measurement-label.tsx +0 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +6 -1
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +6 -1
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +5 -5
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +10 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1 -1
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
- package/src/components/systems/zone/zone-system.tsx +0 -0
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +9 -2
- package/src/components/tools/fence/curve-fence-tool.tsx +4 -5
- package/src/components/tools/fence/fence-tool.tsx +2 -2
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +11 -8
- package/src/components/tools/fence/move-fence-tool.tsx +13 -9
- package/src/components/tools/item/move-tool.tsx +3 -6
- package/src/components/tools/item/placement-math.ts +2 -4
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/use-draft-node.ts +0 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +9 -111
- package/src/components/tools/roof/move-roof-tool.tsx +7 -2
- package/src/components/tools/select/box-select-tool.tsx +12 -17
- package/src/components/tools/shared/segment-angle.ts +1 -1
- package/src/components/tools/tool-manager.tsx +12 -12
- package/src/components/tools/wall/curve-wall-tool.tsx +8 -6
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +11 -8
- package/src/components/tools/wall/move-wall-tool.tsx +6 -4
- package/src/components/tools/wall/wall-drafting.ts +0 -0
- package/src/components/tools/wall/wall-tool.tsx +3 -3
- package/src/components/tools/zone/zone-tool.tsx +20 -5
- package/src/components/ui/action-menu/camera-actions.tsx +0 -0
- package/src/components/ui/action-menu/control-modes.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
- package/src/components/ui/action-menu/index.tsx +35 -86
- package/src/components/ui/action-menu/view-toggles.tsx +19 -31
- package/src/components/ui/command-palette/editor-commands.tsx +6 -4
- package/src/components/ui/command-palette/index.tsx +4 -255
- package/src/components/ui/controls/material-picker.tsx +8 -5
- package/src/components/ui/floating-level-selector.tsx +1 -1
- package/src/components/ui/helpers/helper-manager.tsx +5 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1742 -315
- package/src/components/ui/item-catalog/item-catalog.tsx +88 -46
- package/src/components/ui/level-duplicate-dialog.tsx +3 -5
- package/src/components/ui/panels/ceiling-panel.tsx +2 -3
- package/src/components/ui/panels/column-panel.tsx +62 -18
- package/src/components/ui/panels/door-panel.tsx +272 -265
- package/src/components/ui/panels/fence-panel.tsx +0 -5
- package/src/components/ui/panels/paint-panel.tsx +66 -41
- package/src/components/ui/panels/panel-manager.tsx +3 -32
- package/src/components/ui/panels/reference-panel.tsx +28 -13
- package/src/components/ui/panels/roof-panel.tsx +52 -2
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -0
- package/src/components/ui/panels/slab-panel.tsx +0 -0
- package/src/components/ui/panels/spawn-panel.tsx +10 -4
- package/src/components/ui/panels/stair-panel.tsx +66 -14
- package/src/components/ui/panels/wall-panel.tsx +97 -1
- package/src/components/ui/panels/window-panel.tsx +13 -5
- package/src/components/ui/primitives/number-input.tsx +1 -1
- package/src/components/ui/primitives/sidebar.tsx +0 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
- package/src/components/ui/sidebar/icon-rail.tsx +0 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +330 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +4 -6
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +1 -1
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/viewer-overlay.tsx +0 -0
- package/src/components/viewer-zone-system.tsx +0 -0
- package/src/hooks/use-auto-save.ts +14 -0
- package/src/hooks/use-keyboard.ts +10 -0
- package/src/index.tsx +8 -1
- package/src/lib/level-duplication.test.ts +0 -2
- package/src/lib/level-duplication.ts +1 -1
- package/src/lib/material-paint.ts +1 -1
- package/src/lib/roof-duplication.ts +1 -1
- package/src/lib/scene-bounds.ts +1 -1
- package/src/lib/scene.ts +0 -0
- package/src/lib/sfx-bus.ts +2 -0
- package/src/lib/sfx-player.ts +5 -5
- package/src/lib/stair-duplication.ts +2 -2
- package/src/store/use-editor.tsx +27 -59
- package/tsconfig.json +2 -1
- package/src/components/feedback-dialog.tsx +0 -265
- package/src/components/pascal-radio.tsx +0 -280
- package/src/components/preview-button.tsx +0 -16
- package/src/components/ui/viewer-toolbar.tsx +0 -436
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Pascal building editor component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -44,24 +44,24 @@
|
|
|
44
44
|
"clsx": "^2.1.1",
|
|
45
45
|
"cmdk": "^1.1.1",
|
|
46
46
|
"howler": "^2.2.4",
|
|
47
|
-
"lucide-react": "^
|
|
47
|
+
"lucide-react": "^1.7.0",
|
|
48
48
|
"mitt": "^3.0.1",
|
|
49
49
|
"motion": "^12.34.3",
|
|
50
50
|
"nanoid": "^5.1.6",
|
|
51
51
|
"tailwind-merge": "^3.5.0",
|
|
52
|
-
"three-mesh-bvh": "^0.9.8",
|
|
53
52
|
"zod": "^4.3.6",
|
|
54
|
-
"zustand": "^5.0.11"
|
|
53
|
+
"zustand": "^5.0.11",
|
|
54
|
+
"three-mesh-bvh": "~0.9.8"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@pascal-app/core": "^0.7.0",
|
|
58
58
|
"@pascal-app/viewer": "^0.7.0",
|
|
59
59
|
"@pascal/typescript-config": "*",
|
|
60
|
+
"@types/bun": "^1.3.0",
|
|
60
61
|
"@types/howler": "^2.2.12",
|
|
61
|
-
"@types/node": "^22.19.12",
|
|
62
62
|
"@types/react": "19.2.2",
|
|
63
63
|
"@types/react-dom": "19.2.2",
|
|
64
64
|
"@types/three": "^0.184.0",
|
|
65
|
-
"typescript": "
|
|
65
|
+
"typescript": "6.0.2"
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
+
|
|
2
3
|
import {
|
|
3
4
|
type CameraControlEvent,
|
|
4
5
|
type CameraControlFitSceneEvent,
|
|
@@ -6,7 +7,7 @@ import {
|
|
|
6
7
|
sceneRegistry,
|
|
7
8
|
useScene,
|
|
8
9
|
} from '@pascal-app/core'
|
|
9
|
-
import { useViewer, ZONE_LAYER } from '@pascal-app/viewer'
|
|
10
|
+
import { useViewer, WalkthroughControls, ZONE_LAYER } from '@pascal-app/viewer'
|
|
10
11
|
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
|
11
12
|
import { useThree } from '@react-three/fiber'
|
|
12
13
|
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
@@ -205,6 +205,7 @@ export function EditorLayoutV2({
|
|
|
205
205
|
viewerContent,
|
|
206
206
|
overlays,
|
|
207
207
|
}: EditorLayoutV2Props) {
|
|
208
|
+
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
208
209
|
const isMobile = useIsMobile()
|
|
209
210
|
|
|
210
211
|
if (isMobile) {
|
|
@@ -229,7 +230,7 @@ export function EditorLayoutV2({
|
|
|
229
230
|
|
|
230
231
|
{/* Main content: left column + right column */}
|
|
231
232
|
<div className="flex min-h-0 flex-1">
|
|
232
|
-
{sidebarTabs.length > 0 && (
|
|
233
|
+
{!isCaptureMode && sidebarTabs.length > 0 && (
|
|
233
234
|
<LeftColumn
|
|
234
235
|
renderTabContent={renderTabContent}
|
|
235
236
|
sidebarOverlay={sidebarOverlay}
|
|
@@ -238,8 +239,8 @@ export function EditorLayoutV2({
|
|
|
238
239
|
)}
|
|
239
240
|
<RightColumn
|
|
240
241
|
overlays={overlays}
|
|
241
|
-
toolbarLeft={viewerToolbarLeft}
|
|
242
|
-
toolbarRight={viewerToolbarRight}
|
|
242
|
+
toolbarLeft={isCaptureMode ? undefined : viewerToolbarLeft}
|
|
243
|
+
toolbarRight={isCaptureMode ? undefined : viewerToolbarRight}
|
|
243
244
|
>
|
|
244
245
|
{viewerContent}
|
|
245
246
|
</RightColumn>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getGarageVisibleOpeningRatio,
|
|
3
2
|
type AnyNodeId,
|
|
4
3
|
type DoorNode,
|
|
4
|
+
getGarageVisibleOpeningRatio,
|
|
5
5
|
isOperationDoorType,
|
|
6
6
|
sceneRegistry,
|
|
7
7
|
useInteractive,
|
|
@@ -97,11 +97,11 @@ function shouldSkipColliderNode(nodeId: string, type: (typeof COLLIDER_NODE_TYPE
|
|
|
97
97
|
const node = useScene.getState().nodes[nodeId as AnyNodeId]
|
|
98
98
|
if (!node || node.type !== 'door') return false
|
|
99
99
|
|
|
100
|
-
if (node.openingKind === 'opening') return true
|
|
101
|
-
|
|
102
100
|
if (!node.segments.length) return true
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
if (node.openingKind === 'opening') return true
|
|
103
|
+
|
|
104
|
+
return node.segments.every((segment: { type: string }) => segment.type === 'empty')
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function createDoorLeafColliderGeometry(root: THREE.Object3D, node: DoorNode) {
|
|
@@ -273,9 +273,7 @@ export function buildFirstPersonColliderWorldFromRegistry(): FirstPersonCollider
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
const mergedGeometry = mergeGeometries(geometries, false)
|
|
276
|
-
geometries.forEach((geometry) =>
|
|
277
|
-
geometry.dispose()
|
|
278
|
-
})
|
|
276
|
+
geometries.forEach((geometry) => geometry.dispose())
|
|
279
277
|
|
|
280
278
|
if (!mergedGeometry || mergedGeometry.getAttribute('position') == null) {
|
|
281
279
|
mergedGeometry?.dispose()
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import '../../../three-types'
|
|
2
2
|
import { TransformControls, useKeyboardControls } from '@react-three/drei'
|
|
3
|
-
import { useFrame, useThree
|
|
4
|
-
import {
|
|
5
|
-
Suspense,
|
|
6
|
-
forwardRef,
|
|
7
|
-
useCallback,
|
|
8
|
-
useImperativeHandle,
|
|
9
|
-
useMemo,
|
|
10
|
-
useRef,
|
|
11
|
-
} from 'react'
|
|
3
|
+
import { type ThreeElements, useFrame, useThree } from '@react-three/fiber'
|
|
12
4
|
import type { ReactNode } from 'react'
|
|
5
|
+
import { forwardRef, Suspense, useCallback, useImperativeHandle, useMemo, useRef } from 'react'
|
|
13
6
|
import * as THREE from 'three'
|
|
14
7
|
import { clamp } from 'three/src/math/MathUtils.js'
|
|
15
8
|
|
|
@@ -156,16 +149,7 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
156
149
|
const moveDirRef = useRef<THREE.ArrowHelper | null>(null)
|
|
157
150
|
const elapsedRef = useRef(0)
|
|
158
151
|
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
return !!useKeyboardControls()
|
|
162
|
-
} catch {
|
|
163
|
-
return false
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const isInsideKeyboardControls = useIsInsideKeyboardControls()
|
|
168
|
-
const [_, getKeys] = isInsideKeyboardControls ? useKeyboardControls() : [null, null]
|
|
152
|
+
const [, getKeys] = useKeyboardControls()
|
|
169
153
|
const presetKeys = {
|
|
170
154
|
forward: false,
|
|
171
155
|
backward: false,
|
|
@@ -222,11 +206,11 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
222
206
|
const scaledContactRadiusVec = useRef(new THREE.Vector3())
|
|
223
207
|
const deltaDist = useRef(new THREE.Vector3())
|
|
224
208
|
const currSlopeAngle = useRef(0)
|
|
225
|
-
const localMinDistance = useRef(
|
|
209
|
+
const localMinDistance = useRef(Number.POSITIVE_INFINITY)
|
|
226
210
|
const localClosestPoint = useRef(new THREE.Vector3())
|
|
227
211
|
const localHitNormal = useRef(new THREE.Vector3())
|
|
228
212
|
const triNormal = useRef(new THREE.Vector3())
|
|
229
|
-
const globalMinDistance = useRef(
|
|
213
|
+
const globalMinDistance = useRef(Number.POSITIVE_INFINITY)
|
|
230
214
|
const globalClosestPoint = useRef(new THREE.Vector3())
|
|
231
215
|
const triHitPoint = useRef(new THREE.Vector3())
|
|
232
216
|
const segHitPoint = useRef(new THREE.Vector3())
|
|
@@ -286,7 +270,13 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
286
270
|
const moving = currentLinVel.current.lengthSq() > 1e-6
|
|
287
271
|
const platformIsMoving = totalPlatformDeltaPos.current.lengthSq() > 1e-6
|
|
288
272
|
|
|
289
|
-
if (
|
|
273
|
+
if (
|
|
274
|
+
!moving &&
|
|
275
|
+
isOnGround.current &&
|
|
276
|
+
!jump &&
|
|
277
|
+
!isOnMovingPlatform.current &&
|
|
278
|
+
!platformIsMoving
|
|
279
|
+
) {
|
|
290
280
|
idleTime.current += delta
|
|
291
281
|
if (idleTime.current > sleepTimeout) isSleeping.current = true
|
|
292
282
|
} else {
|
|
@@ -340,7 +330,10 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
340
330
|
upAxis.current,
|
|
341
331
|
)
|
|
342
332
|
characterModelTargetQuat.current.setFromRotationMatrix(characterModelLookMatrix.current)
|
|
343
|
-
characterModelRef.current.quaternion.slerp(
|
|
333
|
+
characterModelRef.current.quaternion.slerp(
|
|
334
|
+
characterModelTargetQuat.current,
|
|
335
|
+
delta * turnSpeed,
|
|
336
|
+
)
|
|
344
337
|
}
|
|
345
338
|
|
|
346
339
|
const maxSpeed = run ? maxRunSpeed : maxWalkSpeed
|
|
@@ -358,18 +351,33 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
358
351
|
)
|
|
359
352
|
currentLinVel.current.add(deltaLinVel.current)
|
|
360
353
|
} else if (isOnGround.current) {
|
|
361
|
-
deltaLinVel.current
|
|
354
|
+
deltaLinVel.current
|
|
355
|
+
.copy(currentLinVelOnPlane.current)
|
|
356
|
+
.clampLength(0, deceleration * friction * delta)
|
|
362
357
|
currentLinVel.current.sub(deltaLinVel.current)
|
|
363
358
|
}
|
|
364
359
|
},
|
|
365
|
-
[
|
|
360
|
+
[
|
|
361
|
+
acceleration,
|
|
362
|
+
airDragFactor,
|
|
363
|
+
counterAccFactor,
|
|
364
|
+
deceleration,
|
|
365
|
+
maxRunSpeed,
|
|
366
|
+
maxWalkSpeed,
|
|
367
|
+
turnSpeed,
|
|
368
|
+
characterOrigin,
|
|
369
|
+
],
|
|
366
370
|
)
|
|
367
371
|
|
|
368
372
|
const updateSegmentBBox = useCallback(() => {
|
|
369
373
|
if (!characterGroupRef.current) return
|
|
370
374
|
|
|
371
|
-
characterSegment.current.start
|
|
372
|
-
|
|
375
|
+
characterSegment.current.start
|
|
376
|
+
.set(0, capsuleLength / 2, 0)
|
|
377
|
+
.add(characterGroupRef.current.position)
|
|
378
|
+
characterSegment.current.end
|
|
379
|
+
.set(0, -capsuleLength / 2, 0)
|
|
380
|
+
.add(characterGroupRef.current.position)
|
|
373
381
|
|
|
374
382
|
characterBbox.current
|
|
375
383
|
.makeEmpty()
|
|
@@ -394,11 +402,18 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
394
402
|
|
|
395
403
|
const collisionCheck = useCallback(
|
|
396
404
|
(mesh: THREE.Mesh, originMatrix: THREE.Matrix4, delta: number) => {
|
|
397
|
-
if (!mesh.visible
|
|
405
|
+
if (!(mesh.visible && mesh.geometry.boundsTree) || mesh.userData.excludeCollisionCheck)
|
|
406
|
+
return
|
|
398
407
|
|
|
399
|
-
originMatrix.decompose(
|
|
408
|
+
originMatrix.decompose(
|
|
409
|
+
contactTempPos.current,
|
|
410
|
+
contactTempQuat.current,
|
|
411
|
+
contactTempScale.current,
|
|
412
|
+
)
|
|
400
413
|
collideInvertMatrix.current.copy(originMatrix).invert()
|
|
401
|
-
localCharacterSegment.current
|
|
414
|
+
localCharacterSegment.current
|
|
415
|
+
.copy(characterSegment.current)
|
|
416
|
+
.applyMatrix4(collideInvertMatrix.current)
|
|
402
417
|
|
|
403
418
|
scaledContactRadiusVec.current.set(
|
|
404
419
|
capsuleRadius / contactTempScale.current.x,
|
|
@@ -445,7 +460,10 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
445
460
|
contactDepth.current =
|
|
446
461
|
capsuleRadius - capsuleContactPoint.current.distanceTo(triContactPoint.current)
|
|
447
462
|
|
|
448
|
-
accumulatedContactNormal.current.addScaledVector(
|
|
463
|
+
accumulatedContactNormal.current.addScaledVector(
|
|
464
|
+
contactNormal.current,
|
|
465
|
+
contactDepth.current,
|
|
466
|
+
)
|
|
449
467
|
accumulatedContactPoint.current.add(triContactPoint.current)
|
|
450
468
|
totalDepth.current += contactDepth.current
|
|
451
469
|
triangleCount.current += 1
|
|
@@ -492,14 +510,16 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
492
510
|
|
|
493
511
|
const floatingCheck = useCallback(
|
|
494
512
|
(mesh: THREE.Mesh, originMatrix: THREE.Matrix4) => {
|
|
495
|
-
if (!mesh.visible
|
|
513
|
+
if (!(mesh.visible && mesh.geometry.boundsTree) || mesh.userData.excludeFloatHit) return
|
|
496
514
|
|
|
497
515
|
originMatrix.decompose(floatTempPos.current, floatTempQuat.current, floatTempScale.current)
|
|
498
516
|
floatInvertMatrix.current.copy(originMatrix).invert()
|
|
499
517
|
floatNormalInverseMatrix.current.getNormalMatrix(floatInvertMatrix.current)
|
|
500
518
|
floatNormalMatrix.current.getNormalMatrix(originMatrix)
|
|
501
519
|
|
|
502
|
-
localFloatSensorSegment.current
|
|
520
|
+
localFloatSensorSegment.current
|
|
521
|
+
.copy(floatSensorSegment.current)
|
|
522
|
+
.applyMatrix4(floatInvertMatrix.current)
|
|
503
523
|
localFloatSensorBboxExpendPoint.current
|
|
504
524
|
.copy(floatSensorBboxExpendPoint.current)
|
|
505
525
|
.applyMatrix4(floatInvertMatrix.current)
|
|
@@ -517,20 +537,33 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
517
537
|
localFloatSensorBbox.current.min.addScaledVector(scaledFloatRadiusVec.current, -1)
|
|
518
538
|
localFloatSensorBbox.current.max.add(scaledFloatRadiusVec.current)
|
|
519
539
|
|
|
520
|
-
localMinDistance.current =
|
|
521
|
-
localClosestPoint.current.set(
|
|
540
|
+
localMinDistance.current = Number.POSITIVE_INFINITY
|
|
541
|
+
localClosestPoint.current.set(
|
|
542
|
+
Number.POSITIVE_INFINITY,
|
|
543
|
+
Number.POSITIVE_INFINITY,
|
|
544
|
+
Number.POSITIVE_INFINITY,
|
|
545
|
+
)
|
|
522
546
|
|
|
523
547
|
mesh.geometry.boundsTree.shapecast({
|
|
524
548
|
intersectsBounds: (box) => box.intersectsBox(localFloatSensorBbox.current),
|
|
525
549
|
intersectsTriangle: (tri) => {
|
|
526
|
-
tri.closestPointToSegment(
|
|
527
|
-
|
|
550
|
+
tri.closestPointToSegment(
|
|
551
|
+
localFloatSensorSegment.current,
|
|
552
|
+
triHitPoint.current,
|
|
553
|
+
segHitPoint.current,
|
|
554
|
+
)
|
|
555
|
+
localUpAxis.current
|
|
556
|
+
.copy(upAxis.current)
|
|
557
|
+
.applyMatrix3(floatNormalInverseMatrix.current)
|
|
558
|
+
.normalize()
|
|
528
559
|
deltaHit.current.subVectors(triHitPoint.current, localFloatSensorSegment.current.start)
|
|
529
560
|
deltaHit.current.divide(scaledFloatRadiusVec.current)
|
|
530
561
|
|
|
531
562
|
const totalLengthSq = deltaHit.current.lengthSq()
|
|
532
563
|
const dot = deltaHit.current.dot(localUpAxis.current)
|
|
533
|
-
const verticalLength =
|
|
564
|
+
const verticalLength =
|
|
565
|
+
Math.abs(dot) /
|
|
566
|
+
((capsuleRadius + floatHeight + floatPullBackHeight) / floatSensorRadius)
|
|
534
567
|
const horizontalLength = Math.sqrt(Math.max(0, totalLengthSq - dot * dot))
|
|
535
568
|
|
|
536
569
|
if (horizontalLength < 1 && verticalLength < 1) {
|
|
@@ -560,9 +593,14 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
560
593
|
const handleFloatingResponse = useCallback(
|
|
561
594
|
(meshes: THREE.Mesh[], jump: boolean, delta: number) => {
|
|
562
595
|
if (meshes.length === 0) return
|
|
596
|
+
let shouldJump = jump
|
|
563
597
|
|
|
564
|
-
globalMinDistance.current =
|
|
565
|
-
globalClosestPoint.current.set(
|
|
598
|
+
globalMinDistance.current = Number.POSITIVE_INFINITY
|
|
599
|
+
globalClosestPoint.current.set(
|
|
600
|
+
Number.POSITIVE_INFINITY,
|
|
601
|
+
Number.POSITIVE_INFINITY,
|
|
602
|
+
Number.POSITIVE_INFINITY,
|
|
603
|
+
)
|
|
566
604
|
floatHitNormal.current.set(0, 1, 0)
|
|
567
605
|
isOnGround.current = false
|
|
568
606
|
totalPlatformDeltaPos.current.set(0, 0, 0)
|
|
@@ -574,7 +612,11 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
574
612
|
}
|
|
575
613
|
}
|
|
576
614
|
|
|
577
|
-
if (
|
|
615
|
+
if (
|
|
616
|
+
floatCheckType !== 'SHAPECAST' &&
|
|
617
|
+
floatRaycastCandidates.length > 0 &&
|
|
618
|
+
globalMinDistance.current === Number.POSITIVE_INFINITY
|
|
619
|
+
) {
|
|
578
620
|
floatRaycaster.current.ray.origin.copy(floatSensorSegment.current.start)
|
|
579
621
|
floatRaycaster.current.ray.direction.copy(gravityDir.current)
|
|
580
622
|
const hits = floatRaycaster.current.intersectObjects(floatRaycastCandidates, false)
|
|
@@ -582,23 +624,28 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
582
624
|
if (hit?.point) {
|
|
583
625
|
globalClosestPoint.current.copy(hit.point)
|
|
584
626
|
if (hit.face) {
|
|
585
|
-
floatHitNormal.current
|
|
627
|
+
floatHitNormal.current
|
|
628
|
+
.copy(hit.face.normal)
|
|
629
|
+
.transformDirection(hit.object.matrixWorld)
|
|
630
|
+
.normalize()
|
|
586
631
|
}
|
|
587
632
|
}
|
|
588
633
|
}
|
|
589
634
|
|
|
590
|
-
if (globalClosestPoint.current.x ===
|
|
635
|
+
if (globalClosestPoint.current.x === Number.POSITIVE_INFINITY) return
|
|
591
636
|
|
|
592
|
-
relativeHitPoint.current
|
|
637
|
+
relativeHitPoint.current
|
|
638
|
+
.copy(globalClosestPoint.current)
|
|
639
|
+
.sub(floatSensorSegment.current.start)
|
|
593
640
|
const currentDistance = relativeHitPoint.current.length()
|
|
594
641
|
currSlopeAngle.current = floatHitNormal.current.angleTo(upAxis.current)
|
|
595
642
|
|
|
596
643
|
if (currentDistance < floatHeight + capsuleRadius) {
|
|
597
644
|
isOnGround.current = true
|
|
598
|
-
|
|
645
|
+
shouldJump = false
|
|
599
646
|
}
|
|
600
647
|
|
|
601
|
-
if (!
|
|
648
|
+
if (!shouldJump) {
|
|
602
649
|
const displacement = floatHeight + capsuleRadius - currentDistance
|
|
603
650
|
const velocityOnHitNormal = currentLinVel.current.dot(floatHitNormal.current)
|
|
604
651
|
const springForce = displacement * floatSpringK
|
|
@@ -608,7 +655,17 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
608
655
|
currentLinVel.current.addScaledVector(floatHitNormal.current, (totalForce / mass) * delta)
|
|
609
656
|
}
|
|
610
657
|
},
|
|
611
|
-
[
|
|
658
|
+
[
|
|
659
|
+
capsuleRadius,
|
|
660
|
+
floatCheckType,
|
|
661
|
+
floatDampingC,
|
|
662
|
+
floatHeight,
|
|
663
|
+
floatRaycastCandidates,
|
|
664
|
+
floatSpringK,
|
|
665
|
+
floatingCheck,
|
|
666
|
+
gravity,
|
|
667
|
+
mass,
|
|
668
|
+
],
|
|
612
669
|
)
|
|
613
670
|
|
|
614
671
|
const updateCharacterWithPlatform = useCallback(() => {
|
|
@@ -646,8 +703,14 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
646
703
|
)
|
|
647
704
|
|
|
648
705
|
const resetLinVel = useCallback(() => currentLinVel.current.set(0, 0, 0), [])
|
|
649
|
-
const addLinVel = useCallback(
|
|
650
|
-
|
|
706
|
+
const addLinVel = useCallback(
|
|
707
|
+
(velocity: THREE.Vector3) => currentLinVel.current.add(velocity),
|
|
708
|
+
[],
|
|
709
|
+
)
|
|
710
|
+
const setLinVel = useCallback(
|
|
711
|
+
(velocity: THREE.Vector3) => currentLinVel.current.copy(velocity),
|
|
712
|
+
[],
|
|
713
|
+
)
|
|
651
714
|
const setMovement = useCallback((movement: MovementInput) => {
|
|
652
715
|
if (movement.forward !== undefined) forwardState.current = movement.forward
|
|
653
716
|
if (movement.backward !== undefined) backwardState.current = movement.backward
|
|
@@ -682,7 +745,9 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
682
745
|
debugRaySensorEnd.current?.position.copy(floatSensorSegment.current.end)
|
|
683
746
|
standPointRef.current?.position.copy(globalClosestPoint.current)
|
|
684
747
|
if (characterGroupRef.current) {
|
|
685
|
-
lookDirRef.current?.position
|
|
748
|
+
lookDirRef.current?.position
|
|
749
|
+
.copy(characterGroupRef.current.position)
|
|
750
|
+
.addScaledVector(upAxis.current, 0.7)
|
|
686
751
|
}
|
|
687
752
|
lookDirRef.current?.lookAt(lookDirRef.current.position.clone().add(camProjDir.current))
|
|
688
753
|
inputDirRef.current?.position.copy(characterSegment.current.end)
|
|
@@ -698,7 +763,7 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
698
763
|
if (paused || elapsedRef.current < delay) return
|
|
699
764
|
|
|
700
765
|
const deltaTime = Math.min(1 / 45, delta) * slowMotionFactor
|
|
701
|
-
const keys =
|
|
766
|
+
const keys = getKeys() ?? presetKeys
|
|
702
767
|
const forward = forwardState.current || keys.forward
|
|
703
768
|
const backward = backwardState.current || keys.backward
|
|
704
769
|
const leftward = leftwardState.current || keys.leftward
|
|
@@ -740,7 +805,7 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
740
805
|
|
|
741
806
|
return (
|
|
742
807
|
<Suspense fallback={null}>
|
|
743
|
-
<group {...props}
|
|
808
|
+
<group {...props} dispose={null} ref={characterGroupRef}>
|
|
744
809
|
{debug && (
|
|
745
810
|
<mesh ref={characterColliderRef}>
|
|
746
811
|
<capsuleGeometry args={colliderCapsuleArgs} />
|
|
@@ -777,8 +842,8 @@ const BVHEcctrl = forwardRef<BVHEcctrlApi, EcctrlProps>(
|
|
|
777
842
|
<octahedronGeometry args={[0.1, 0]} />
|
|
778
843
|
<meshNormalMaterial />
|
|
779
844
|
</mesh>
|
|
780
|
-
<arrowHelper
|
|
781
|
-
<arrowHelper
|
|
845
|
+
<arrowHelper args={[undefined, undefined, undefined, '#00f']} ref={inputDirRef} />
|
|
846
|
+
<arrowHelper args={[undefined, undefined, undefined, '#f00']} ref={moveDirRef} />
|
|
782
847
|
<mesh ref={standPointRef}>
|
|
783
848
|
<octahedronGeometry args={[0.12, 0]} />
|
|
784
849
|
<meshBasicMaterial color="red" opacity={0.2} transparent />
|
|
@@ -455,14 +455,15 @@ export const FirstPersonControls = () => {
|
|
|
455
455
|
{controllerStart && (
|
|
456
456
|
<KeyboardControls map={keyboardMap}>
|
|
457
457
|
<BVHEcctrl
|
|
458
|
-
|
|
459
|
-
|
|
458
|
+
acceleration={26}
|
|
459
|
+
airDragFactor={0.3}
|
|
460
460
|
colliderCapsuleArgs={[0.25, 0.8, 4, 8]}
|
|
461
461
|
colliderMeshes={[world.mesh]}
|
|
462
462
|
collisionCheckIteration={3}
|
|
463
463
|
collisionPushBackDamping={0.1}
|
|
464
464
|
collisionPushBackThreshold={0.001}
|
|
465
465
|
debug={false}
|
|
466
|
+
deceleration={30}
|
|
466
467
|
delay={0}
|
|
467
468
|
fallGravityFactor={4}
|
|
468
469
|
floatCheckType="BOTH"
|
|
@@ -473,13 +474,12 @@ export const FirstPersonControls = () => {
|
|
|
473
474
|
floatSpringK={1200}
|
|
474
475
|
gravity={9.81}
|
|
475
476
|
jumpVel={6}
|
|
477
|
+
key="first-person-controller"
|
|
476
478
|
maxRunSpeed={5.5}
|
|
477
479
|
maxSlope={1.2}
|
|
478
480
|
maxWalkSpeed={4}
|
|
479
481
|
position={controllerStart.position}
|
|
480
|
-
|
|
481
|
-
airDragFactor={0.3}
|
|
482
|
-
deceleration={30}
|
|
482
|
+
ref={controllerRef}
|
|
483
483
|
/>
|
|
484
484
|
</KeyboardControls>
|
|
485
485
|
)}
|
|
@@ -551,12 +551,12 @@ export const FirstPersonOverlay = ({ onExit }: { onExit: () => void }) => {
|
|
|
551
551
|
{isLocked && (
|
|
552
552
|
<div className="pointer-events-none fixed top-1/2 right-6 z-40 -translate-y-1/2">
|
|
553
553
|
<div className="flex min-w-[148px] flex-col gap-3 rounded-2xl border border-border/35 bg-background/80 px-4 py-4 shadow-lg backdrop-blur-xl">
|
|
554
|
-
<ControlHint
|
|
554
|
+
<ControlHint keys={['W', 'A', 'S', 'D']} label="Move" />
|
|
555
555
|
<div className="h-px w-full bg-border/30" />
|
|
556
|
-
<InlineControlHint
|
|
557
|
-
<InlineControlHint
|
|
558
|
-
<InlineControlHint
|
|
559
|
-
<InlineControlHint
|
|
556
|
+
<InlineControlHint keyLabel="Space" label="Jump" />
|
|
557
|
+
<InlineControlHint keyLabel="Shift" label="Sprint" />
|
|
558
|
+
<InlineControlHint keyLabel="E / R" label="Interact" />
|
|
559
|
+
<InlineControlHint keyLabel="T" label="Close" />
|
|
560
560
|
<div className="h-px w-full bg-border/30" />
|
|
561
561
|
<span className="text-center text-muted-foreground/60 text-xs">
|
|
562
562
|
Click to look around
|
|
@@ -591,7 +591,7 @@ function ControlHint({ label, keys }: { label: string; keys: string[] }) {
|
|
|
591
591
|
function InlineControlHint({ label, keyLabel }: { label: string; keyLabel: string }) {
|
|
592
592
|
return (
|
|
593
593
|
<div className="flex items-center justify-between gap-3">
|
|
594
|
-
<span className="font-medium text-[10px] text-muted-foreground/60 tracking-[0.03em]
|
|
594
|
+
<span className="font-medium text-[10px] text-muted-foreground/60 uppercase tracking-[0.03em]">
|
|
595
595
|
{label}
|
|
596
596
|
</span>
|
|
597
597
|
<kbd className="flex h-5 min-w-5 items-center justify-center rounded border border-border/50 bg-accent/40 px-1.5 font-mono text-[10px] text-foreground/80 leading-none">
|
|
File without changes
|