@pioneer_zmc/dsh-workbench 0.2.1
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/LICENSE +21 -0
- package/README.md +98 -0
- package/README.zh.md +100 -0
- package/client.js +122 -0
- package/cordis.patch.yml +8 -0
- package/dist/check-building.js +1669 -0
- package/dist/check-room.js +980 -0
- package/dist/index.js +2873 -0
- package/package.json +60 -0
- package/skills/specdev-building/SKILL.md +85 -0
- package/skills/specdev-building/assets/layouts/court.json +53 -0
- package/skills/specdev-building/assets/layouts/row.json +48 -0
- package/skills/specdev-building/assets/layouts/single.json +27 -0
- package/skills/specdev-building/assets/layouts/tower-9.json +71 -0
- package/skills/specdev-building/assets/layouts/tower.json +59 -0
- package/skills/specdev-building/references/blueprint-contract.md +126 -0
- package/skills/specdev-building/references/diagnostics.md +101 -0
- package/skills/specdev-building/references/layouts.md +112 -0
- package/skills/specdev-business/SKILL.md +91 -0
- package/skills/specdev-business/assets/plan-template.md +66 -0
- package/skills/specdev-business/assets/spec-template.md +111 -0
- package/skills/specdev-business/assets/tasks-template.md +36 -0
- package/skills/specdev-business/references/change.md +117 -0
- package/skills/specdev-business/references/evidence-review.md +125 -0
- package/skills/specdev-business/references/implementation.md +67 -0
- package/skills/specdev-business/references/project-contract.md +211 -0
- package/skills/specdev-business/references/source-map.md +110 -0
- package/skills/specdev-business/references/specification.md +93 -0
- package/skills/specdev-business/references/workflow.md +126 -0
- package/skills/specdev-room/SKILL.md +77 -0
- package/skills/specdev-room/assets/intro-room.json +14 -0
- package/skills/specdev-room/references/diagnostics.md +21 -0
- package/skills/specdev-room/references/room-contract.md +59 -0
- package/vendor/archify-renderer/VERSION.md +52 -0
- package/vendor/archify-renderer/archify/LICENSE +22 -0
- package/vendor/archify-renderer/archify/THIRD_PARTY_NOTICES.md +56 -0
- package/vendor/archify-renderer/archify/assets/template.html +14787 -0
- package/vendor/archify-renderer/archify/bin/archify.mjs +1990 -0
- package/vendor/archify-renderer/archify/renderers/shared/brand-marks.mjs +563 -0
- package/vendor/archify-renderer/archify/renderers/shared/cli.mjs +218 -0
- package/vendor/archify-renderer/archify/renderers/shared/desktop-readability.mjs +26 -0
- package/vendor/archify-renderer/archify/renderers/shared/diagnostics.mjs +127 -0
- package/vendor/archify-renderer/archify/renderers/shared/engineering-profiles.mjs +157 -0
- package/vendor/archify-renderer/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- package/vendor/archify-renderer/archify/renderers/shared/generated-validators.mjs +13 -0
- package/vendor/archify-renderer/archify/renderers/shared/geometry.mjs +1423 -0
- package/vendor/archify-renderer/archify/renderers/shared/i18n.mjs +594 -0
- package/vendor/archify-renderer/archify/renderers/shared/legend.mjs +217 -0
- package/vendor/archify-renderer/archify/renderers/shared/output-path.mjs +321 -0
- package/vendor/archify-renderer/archify/renderers/shared/repository-evidence.mjs +235 -0
- package/vendor/archify-renderer/archify/renderers/shared/text-fit.mjs +49 -0
- package/vendor/archify-renderer/archify/renderers/shared/utils.mjs +232 -0
- package/vendor/archify-renderer/archify/renderers/shared/validator.mjs +86 -0
- package/vendor/archify-renderer/archify/renderers/workflow/render-workflow.mjs +35 -0
- package/vendor/archify-renderer/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- package/vendor/archify-renderer/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- package/vendor/archify-renderer/archify/schemas/common.schema.json +115 -0
- package/vendor/archify-renderer/archify/schemas/workflow.schema.json +428 -0
- package/vendor/archify-renderer/archify/scripts/check-render-output.mjs +835 -0
- package/vendor/archify-renderer/shims/process.mjs +18 -0
- package/vendor/archify-renderer/stubs/child_process.mjs +2 -0
- package/vendor/archify-renderer/stubs/crypto.mjs +3 -0
- package/vendor/archify-renderer/stubs/dns-promises.mjs +2 -0
- package/vendor/archify-renderer/stubs/fs.mjs +3 -0
- package/vendor/archify-renderer/stubs/http.mjs +3 -0
- package/vendor/archify-renderer/stubs/https.mjs +3 -0
- package/vendor/archify-renderer/stubs/net.mjs +3 -0
- package/vendor/archify-renderer/stubs/path.mjs +3 -0
- package/vendor/archify-renderer/stubs/url.mjs +3 -0
- package/web/assets/building-kit.js +1157 -0
- package/web/assets/building-look.js +185 -0
- package/web/assets/building-plan.js +495 -0
- package/web/assets/building-scene.js +529 -0
- package/web/assets/building-walk.js +248 -0
- package/web/assets/building.js +859 -0
- package/web/assets/business.js +124 -0
- package/web/assets/chart-link.js +165 -0
- package/web/assets/common.js +302 -0
- package/web/assets/details.js +197 -0
- package/web/assets/evidence.js +30 -0
- package/web/assets/furniture/SOURCES.md +24 -0
- package/web/assets/furniture/assets/ceramic-display-stand-01/asset.json +25 -0
- package/web/assets/furniture/assets/ceramic-display-stand-01/model.js +116 -0
- package/web/assets/furniture/assets/ceramic-display-stand-01/preview.webp +0 -0
- package/web/assets/furniture/assets/ceramic-floor-lamp-01/asset.json +61 -0
- package/web/assets/furniture/assets/ceramic-floor-lamp-01/model.js +56 -0
- package/web/assets/furniture/assets/ceramic-floor-lamp-01/preview.webp +0 -0
- package/web/assets/furniture/assets/ceramic-low-cabinet-01/asset.json +81 -0
- package/web/assets/furniture/assets/ceramic-low-cabinet-01/model.js +67 -0
- package/web/assets/furniture/assets/ceramic-low-cabinet-01/preview.webp +0 -0
- package/web/assets/furniture/assets/ceramic-vase-01/asset.json +21 -0
- package/web/assets/furniture/assets/ceramic-vase-01/model.js +119 -0
- package/web/assets/furniture/assets/ceramic-vase-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-bed-01/asset.json +61 -0
- package/web/assets/furniture/assets/fairy-bed-01/model.js +63 -0
- package/web/assets/furniture/assets/fairy-bed-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-book-stack-01/asset.json +21 -0
- package/web/assets/furniture/assets/fairy-book-stack-01/model.js +71 -0
- package/web/assets/furniture/assets/fairy-book-stack-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-bookshelf-01/asset.json +32 -0
- package/web/assets/furniture/assets/fairy-bookshelf-01/model.js +168 -0
- package/web/assets/furniture/assets/fairy-bookshelf-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-desk-01/asset.json +25 -0
- package/web/assets/furniture/assets/fairy-desk-01/model.js +132 -0
- package/web/assets/furniture/assets/fairy-desk-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-notice-board-01/asset.json +23 -0
- package/web/assets/furniture/assets/fairy-notice-board-01/model.js +125 -0
- package/web/assets/furniture/assets/fairy-notice-board-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-potted-plant-01/asset.json +21 -0
- package/web/assets/furniture/assets/fairy-potted-plant-01/model.js +128 -0
- package/web/assets/furniture/assets/fairy-potted-plant-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-reading-chair-01/asset.json +61 -0
- package/web/assets/furniture/assets/fairy-reading-chair-01/model.js +66 -0
- package/web/assets/furniture/assets/fairy-reading-chair-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-table-lamp-01/asset.json +21 -0
- package/web/assets/furniture/assets/fairy-table-lamp-01/model.js +182 -0
- package/web/assets/furniture/assets/fairy-table-lamp-01/preview.webp +0 -0
- package/web/assets/furniture/catalog.js +137 -0
- package/web/assets/furniture/index.json +110 -0
- package/web/assets/furniture/registry.js +72 -0
- package/web/assets/furniture-page.css +68 -0
- package/web/assets/furniture-page.js +302 -0
- package/web/assets/home.js +65 -0
- package/web/assets/node-detail.js +148 -0
- package/web/assets/panel-link.js +47 -0
- package/web/assets/read.js +746 -0
- package/web/assets/room-scene.js +386 -0
- package/web/assets/room.css +287 -0
- package/web/assets/room.js +437 -0
- package/web/assets/rooms/bindings.js +449 -0
- package/web/assets/rooms/ceramic-reflection.js +193 -0
- package/web/assets/rooms/ceramic-room.js +302 -0
- package/web/assets/rooms/door.js +35 -0
- package/web/assets/rooms/fairy-room.js +492 -0
- package/web/assets/rooms/furniture-assembly.js +113 -0
- package/web/assets/rooms/navigation.js +262 -0
- package/web/assets/rooms/placeholder-catalog.json +71 -0
- package/web/assets/rooms/placeholder-furniture.js +76 -0
- package/web/assets/rooms/placement.js +588 -0
- package/web/assets/rooms/reader.js +441 -0
- package/web/assets/rooms/reading-gate.js +157 -0
- package/web/assets/rooms/room-lighting.js +50 -0
- package/web/assets/rooms/templates.json +50 -0
- package/web/assets/rooms/walk-frame.js +64 -0
- package/web/assets/rooms/walk-session.js +138 -0
- package/web/assets/save-result.js +89 -0
- package/web/assets/screenshots/business.png +0 -0
- package/web/assets/screenshots/node-detail.png +0 -0
- package/web/assets/screenshots/project-home.png +0 -0
- package/web/assets/screenshots/workflow.png +0 -0
- package/web/assets/showcase.js +232 -0
- package/web/assets/style.css +836 -0
- package/web/assets/template.js +7 -0
- package/web/assets/theme.js +29 -0
- package/web/assets/three/OrbitControls.js +1972 -0
- package/web/assets/three/VERSION.md +12 -0
- package/web/assets/three/three.core.js +60586 -0
- package/web/assets/three/three.module.js +19719 -0
- package/web/assets/workspaces.css +21 -0
- package/web/assets/workspaces.js +81 -0
- package/web/building.html +131 -0
- package/web/business.html +43 -0
- package/web/furniture.html +47 -0
- package/web/index.html +57 -0
- package/web/read.html +107 -0
- package/web/room.html +146 -0
- package/web/showcase.html +50 -0
- package/web/workspaces.html +32 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// 建筑漫游的两块纯逻辑,都不碰 Three.js,也不读页面布局:
|
|
2
|
+
// 1) createNavigator:只消费服务端算好的 present.surfaces(行走面)与 present.colliders
|
|
3
|
+
// (实心盒 AABB),判定脚下的支撑与撞墙。规则与阈值迁移自实验
|
|
4
|
+
// `experiments/2026-09-23-json房子生成器/navigation.mjs`(已验证的漫游手感):
|
|
5
|
+
// 每一小步只接受高度连续的支撑面、两脚周围(含斜向)都要有支撑、撞墙时不限步长地按轴滑动。
|
|
6
|
+
// 2) createWalkInput:键盘(WASD/方向键/Shift 加速)、屏幕方向按钮、拖动环顾的输入状态机;
|
|
7
|
+
// 失焦与页面隐藏立即清空,松开按键才算停止移动。
|
|
8
|
+
// 相机与场景装配在 building-scene.js;本模块 Node 与 jsdom 都能直接 import 测试。
|
|
9
|
+
|
|
10
|
+
const EPS = 1e-6
|
|
11
|
+
const contains = (b, x, z) => x >= b.x0 - EPS && x <= b.x1 + EPS && z >= b.z0 - EPS && z <= b.z1 + EPS
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 行走判定。位置是脚底坐标(x, y, z):y 是当前站立面的高度,不是眼睛高度。
|
|
15
|
+
* 返回 move(position, dx, dz)(撞墙/悬空则原地不动)、locate(position)(所在空间或 null)、
|
|
16
|
+
* canStand(position) 与半径 radius。
|
|
17
|
+
*/
|
|
18
|
+
export function createNavigator(model, presentation) {
|
|
19
|
+
const radius = 0.26
|
|
20
|
+
const eyeClearance = 1.9
|
|
21
|
+
const { surfaces, colliders } = presentation
|
|
22
|
+
|
|
23
|
+
/** 该平面位置上所有行走面的高度(楼梯面按沿线进度插值)。 */
|
|
24
|
+
function heights(x, z) {
|
|
25
|
+
return surfaces
|
|
26
|
+
.filter((surface) => contains(surface.bounds, x, z))
|
|
27
|
+
.map((surface) => ({
|
|
28
|
+
surface,
|
|
29
|
+
y:
|
|
30
|
+
surface.kind === 'flat'
|
|
31
|
+
? surface.y
|
|
32
|
+
: surface.from.y +
|
|
33
|
+
Math.max(0, Math.min(1, ((x - surface.from.x) * surface.ux + (z - surface.from.z) * surface.uz) / surface.length)) * surface.rise,
|
|
34
|
+
}))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 每一步只接受与当前高度连续的支撑面:不按最高楼板吸附,也不会突然吸到上一层。 */
|
|
38
|
+
function support(x, z, previousY) {
|
|
39
|
+
return heights(x, z)
|
|
40
|
+
.filter((value) => Math.abs(value.y - previousY) <= 0.14 + EPS)
|
|
41
|
+
.sort((a, b) => Math.abs(a.y - previousY) - Math.abs(b.y - previousY))[0]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 站位可用:躯干高度内不撞实心盒,且两脚周围都有支撑(相邻空间共边时不留下看不见的台阶)。 */
|
|
45
|
+
function clear(x, y, z) {
|
|
46
|
+
for (const box of colliders) {
|
|
47
|
+
if (box.y1 <= y + 0.09 || box.y0 >= y + eyeClearance) continue
|
|
48
|
+
const qx = Math.max(box.x0, Math.min(x, box.x1))
|
|
49
|
+
const qz = Math.max(box.z0, Math.min(z, box.z1))
|
|
50
|
+
if ((x - qx) ** 2 + (z - qz) ** 2 < radius ** 2 - EPS) return false
|
|
51
|
+
}
|
|
52
|
+
for (const [dx, dz] of [
|
|
53
|
+
[radius, 0], [-radius, 0], [0, radius], [0, -radius],
|
|
54
|
+
[radius * 0.707, radius * 0.707], [-radius * 0.707, radius * 0.707],
|
|
55
|
+
[radius * 0.707, -radius * 0.707], [-radius * 0.707, -radius * 0.707],
|
|
56
|
+
]) {
|
|
57
|
+
if (!heights(x + dx, z + dz).some((value) => Math.abs(value.y - y) <= radius + 0.02)) return false
|
|
58
|
+
}
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function step(position, dx, dz) {
|
|
63
|
+
const x = position.x + dx
|
|
64
|
+
const z = position.z + dz
|
|
65
|
+
const next = support(x, z, position.y)
|
|
66
|
+
return next && clear(x, next.y, z) ? { x, y: next.y, z } : position
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 走一小段:拆成不超过 0.06 米的子步,撞墙时按轴各试一次(贴着墙滑,不穿过去)。 */
|
|
70
|
+
function move(position, dx, dz) {
|
|
71
|
+
if (![position.x, position.y, position.z, dx, dz].every(Number.isFinite)) throw new TypeError('漫游坐标与位移必须是有限数值。')
|
|
72
|
+
const distance = Math.hypot(dx, dz)
|
|
73
|
+
// 单次超长位移不是行走,禁止不受限分步;目录定位由页面明确处理。
|
|
74
|
+
if (distance > 30) throw new RangeError('单次行走位移不得超过 30 米。')
|
|
75
|
+
const count = Math.max(1, Math.ceil(distance / 0.06))
|
|
76
|
+
let point = { x: position.x, y: position.y, z: position.z }
|
|
77
|
+
for (let i = 0; i < count; i += 1) {
|
|
78
|
+
const next = step(point, dx / count, dz / count)
|
|
79
|
+
if (next !== point) point = next
|
|
80
|
+
else {
|
|
81
|
+
point = step(point, dx / count, 0)
|
|
82
|
+
point = step(point, 0, dz / count)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return point
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 所在空间:高度对上(脚底与空间地坪差 0.06 米内)且平面落在其轮廓里;楼梯上返回 null。 */
|
|
89
|
+
function locate(position) {
|
|
90
|
+
return model.spaces.find((space) => Math.abs(space.y - position.y) < 0.06 && contains(space.bounds, position.x, position.z)) || null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { move, locate, canStand: (point) => Boolean(support(point.x, point.z, point.y)) && clear(point.x, point.y, point.z), radius }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 入口落点:y 缺失时按所在空间的地坪补(与 locate 同一口径);没有入口时返回 null。 */
|
|
97
|
+
export function entryPoint(model) {
|
|
98
|
+
const entry = model?.entry
|
|
99
|
+
if (!entry) return null
|
|
100
|
+
const space = model.spaces?.find((item) => item.id === entry.spaceId)
|
|
101
|
+
return { x: entry.x, y: entry.y ?? space?.y ?? 0, z: entry.z }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 看得见的实体构件才算挡住视线(与场景里"写深度才算挡住"的 0.6 门槛同口径:
|
|
105
|
+
* addBox 给 opacity ≥ 0.6 的构件开深度写入,也就是说它真的遮住了后面的东西)。 */
|
|
106
|
+
const OCCLUDING_OPACITY = 0.6
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 点牌子拾取的判定(纯函数;射线命中按距离从近到远传进来,每项 { kind, visible, opacity, spaceId })。
|
|
110
|
+
* 从近往远看:先撞上看得见的实体构件=被挡住(不隔墙、不隔楼板点牌);先撞上看得见的牌子才算点中。
|
|
111
|
+
* 两条过滤缺一不可:
|
|
112
|
+
* ① Three.js 的射线不看 visible,而楼层剖看只改可见性——不过滤就能点中被剖看藏起来的牌子;
|
|
113
|
+
* ② 只拿牌子求交不打构件,就会隔着墙把隔壁房间、甚至另一层的门牌点开。
|
|
114
|
+
* 半透明构件(玻璃 0.28、设计框架的薄墙 0.22 等)不当遮挡:看得见就点得到。
|
|
115
|
+
*/
|
|
116
|
+
export function pickSign(hits) {
|
|
117
|
+
for (const hit of Array.isArray(hits) ? hits : []) {
|
|
118
|
+
if (!hit || hit.visible === false) continue
|
|
119
|
+
if (hit.kind === 'sign') return { spaceId: hit.spaceId ?? null }
|
|
120
|
+
if ((hit.opacity ?? 1) >= OCCLUDING_OPACITY) return null
|
|
121
|
+
}
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 出生点能不能站。蓝图校验只要求入口落在空间内并与边缘留 0.35 米,而墙体半厚加人物半径
|
|
127
|
+
* 可能超过这个距离(例如墙厚 0.6 时门面墙占到 0.3+0.26=0.56 米):这样的蓝图合法、
|
|
128
|
+
* 设计框架下也没有实体墙,切到完整外观就会让入口与墙重叠——出生即卡死(每一步的落点
|
|
129
|
+
* 都在墙里,走不动)。进探索与换表现都必须先过这一关。
|
|
130
|
+
*/
|
|
131
|
+
export function canSpawn(navigator, point) {
|
|
132
|
+
return Boolean(point) && Boolean(navigator?.canStand({ x: point.x, y: point.y, z: point.z }))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 漫游按键:WASD 与方向键等价,Shift 加速。 */
|
|
136
|
+
export const WALK_KEYS = ['KeyW', 'KeyA', 'KeyS', 'KeyD', 'ArrowUp', 'ArrowLeft', 'ArrowDown', 'ArrowRight', 'ShiftLeft', 'ShiftRight']
|
|
137
|
+
|
|
138
|
+
const TYPING_TAGS = ['INPUT', 'TEXTAREA', 'SELECT']
|
|
139
|
+
const isTyping = (node) => Boolean(node) && (TYPING_TAGS.includes(node.tagName) || node.isContentEditable === true)
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 漫游输入状态机:只维护状态,不碰相机(场景每帧取用后施加到机位)。
|
|
143
|
+
* axis() → { forward, right, fast }:键盘与屏幕方向按钮合并后的方向与加速;
|
|
144
|
+
* 停用期间一律报零(与 setEnabled 同一道闸,虚拟按键也不例外)
|
|
145
|
+
* takeLook() → { yaw, pitch }:自上次取用以来拖动产生的环顾增量(弧度,俯仰夹取由场景做)
|
|
146
|
+
* press(code, down) → 屏幕方向按钮的虚拟按键;停用期间不记(读资料时按住方向按钮不许走动)
|
|
147
|
+
* setEnabled(bool) → 只有漫游模式接收键盘与拖动;总览模式留给 OrbitControls,不抢指针。
|
|
148
|
+
* 关掉输入时把按住的键、虚拟按键与拖动一并清空
|
|
149
|
+
* clear() / dispose()
|
|
150
|
+
* element 是三维画布(拖动环顾、聚焦),target 默认 window(键盘、失焦),document 取 visibilitychange。
|
|
151
|
+
*/
|
|
152
|
+
export function createWalkInput(element, { target = globalThis, lookPerPixel = 0.003 } = {}) {
|
|
153
|
+
const doc = target.document ?? globalThis.document
|
|
154
|
+
const held = new Set()
|
|
155
|
+
const virtual = new Set()
|
|
156
|
+
let enabled = false
|
|
157
|
+
let drag = null
|
|
158
|
+
let look = { yaw: 0, pitch: 0 }
|
|
159
|
+
|
|
160
|
+
function keyDown(event) {
|
|
161
|
+
if (!enabled || event.ctrlKey || event.altKey || event.metaKey) return
|
|
162
|
+
// 正在输入框、下拉框里操作时不抢键;焦点在按钮或画布上照常生效。
|
|
163
|
+
if (isTyping(event.target) || isTyping(doc?.activeElement)) return
|
|
164
|
+
if (!WALK_KEYS.includes(event.code)) return
|
|
165
|
+
held.add(event.code)
|
|
166
|
+
event.preventDefault()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const keyUp = (event) => held.delete(event.code)
|
|
170
|
+
|
|
171
|
+
function pointerDown(event) {
|
|
172
|
+
if (!enabled || event.button !== 0) return
|
|
173
|
+
element.focus?.({ preventScroll: true })
|
|
174
|
+
drag = { id: event.pointerId, x: event.clientX, y: event.clientY }
|
|
175
|
+
element.setPointerCapture?.(event.pointerId)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function pointerMove(event) {
|
|
179
|
+
if (!enabled || !drag || drag.id !== event.pointerId) return
|
|
180
|
+
look.yaw -= (event.clientX - drag.x) * lookPerPixel
|
|
181
|
+
look.pitch -= (event.clientY - drag.y) * lookPerPixel
|
|
182
|
+
drag.x = event.clientX
|
|
183
|
+
drag.y = event.clientY
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pointerUp(event) {
|
|
187
|
+
if (!drag || drag.id !== event.pointerId) return
|
|
188
|
+
drag = null
|
|
189
|
+
if (element.hasPointerCapture?.(event.pointerId)) element.releasePointerCapture(event.pointerId)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 丢焦点、切走页面、指针被系统取消:一律当"全部松开",不让移动继续跑。
|
|
193
|
+
function clear() {
|
|
194
|
+
held.clear()
|
|
195
|
+
virtual.clear()
|
|
196
|
+
drag = null
|
|
197
|
+
look = { yaw: 0, pitch: 0 }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const down = (code) => held.has(code) || virtual.has(code)
|
|
201
|
+
|
|
202
|
+
element.addEventListener('pointerdown', pointerDown)
|
|
203
|
+
element.addEventListener('pointermove', pointerMove)
|
|
204
|
+
element.addEventListener('pointerup', pointerUp)
|
|
205
|
+
element.addEventListener('pointercancel', clear)
|
|
206
|
+
target.addEventListener('keydown', keyDown)
|
|
207
|
+
target.addEventListener('keyup', keyUp)
|
|
208
|
+
target.addEventListener('blur', clear)
|
|
209
|
+
doc?.addEventListener('visibilitychange', clear)
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
axis: () => (enabled
|
|
213
|
+
? {
|
|
214
|
+
forward: Number(down('KeyW') || down('ArrowUp')) - Number(down('KeyS') || down('ArrowDown')),
|
|
215
|
+
right: Number(down('KeyD') || down('ArrowRight')) - Number(down('KeyA') || down('ArrowLeft')),
|
|
216
|
+
fast: down('ShiftLeft') || down('ShiftRight'),
|
|
217
|
+
}
|
|
218
|
+
: { forward: 0, right: 0, fast: false }),
|
|
219
|
+
takeLook() {
|
|
220
|
+
const value = look
|
|
221
|
+
look = { yaw: 0, pitch: 0 }
|
|
222
|
+
return value
|
|
223
|
+
},
|
|
224
|
+
press(code, down) {
|
|
225
|
+
// 停用期间不收虚拟按键:键盘与拖动已经被 setEnabled 挡住,屏幕按钮必须走同一道闸,
|
|
226
|
+
// 否则"读资料时暂停输入"只挡住一半(按住方向按钮仍会走)。
|
|
227
|
+
if (!enabled) return
|
|
228
|
+
if (down) virtual.add(code)
|
|
229
|
+
else virtual.delete(code)
|
|
230
|
+
},
|
|
231
|
+
setEnabled(value) {
|
|
232
|
+
enabled = value
|
|
233
|
+
if (!value) clear()
|
|
234
|
+
},
|
|
235
|
+
clear,
|
|
236
|
+
dispose() {
|
|
237
|
+
clear()
|
|
238
|
+
element.removeEventListener('pointerdown', pointerDown)
|
|
239
|
+
element.removeEventListener('pointermove', pointerMove)
|
|
240
|
+
element.removeEventListener('pointerup', pointerUp)
|
|
241
|
+
element.removeEventListener('pointercancel', clear)
|
|
242
|
+
target.removeEventListener('keydown', keyDown)
|
|
243
|
+
target.removeEventListener('keyup', keyUp)
|
|
244
|
+
target.removeEventListener('blur', clear)
|
|
245
|
+
doc?.removeEventListener('visibilitychange', clear)
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
}
|