@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,302 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { createCeramicReflection } from './ceramic-reflection.js';
|
|
3
|
+
import { createDoorHandle } from './door.js';
|
|
4
|
+
|
|
5
|
+
const SIZE = Object.freeze({ width: 8, depth: 6, height: 3.4 });
|
|
6
|
+
const DOOR = Object.freeze({ wall: 'north', centerX: 2.25, width: 1.4, height: 2.55 });
|
|
7
|
+
|
|
8
|
+
function dataTexture(data, width, height, name, color = false) {
|
|
9
|
+
const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
|
|
10
|
+
texture.name = name;
|
|
11
|
+
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
|
|
12
|
+
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
13
|
+
texture.magFilter = THREE.LinearFilter;
|
|
14
|
+
texture.generateMipmaps = true;
|
|
15
|
+
texture.colorSpace = color ? THREE.SRGBColorSpace : THREE.NoColorSpace;
|
|
16
|
+
texture.needsUpdate = true;
|
|
17
|
+
return texture;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makePorcelainTextures() {
|
|
21
|
+
const width = 1024;
|
|
22
|
+
const height = 768;
|
|
23
|
+
const colour = new Uint8Array(width * height * 4);
|
|
24
|
+
const roughness = new Uint8Array(width * height * 4);
|
|
25
|
+
const glaze = new Uint8Array(width * height * 4);
|
|
26
|
+
const normal = new Uint8Array(width * height * 4);
|
|
27
|
+
for (let y = 0; y < height; y += 1) {
|
|
28
|
+
for (let x = 0; x < width; x += 1) {
|
|
29
|
+
const px = (x + 0.5) / width * 8;
|
|
30
|
+
const py = (y + 0.5) / height * 6;
|
|
31
|
+
const tx = px % 1;
|
|
32
|
+
const ty = py % 1;
|
|
33
|
+
const edge = Math.min(tx, 1 - tx, ty, 1 - ty);
|
|
34
|
+
const joint = 1 - THREE.MathUtils.smoothstep(edge, 0.0035, 0.012);
|
|
35
|
+
const tileTone = Math.sin(Math.floor(px) * 19.3 + Math.floor(py) * 29.1) * 1.4;
|
|
36
|
+
const silk = Math.sin(px * 13 + Math.cos(py * 7) * 1.8) * Math.cos(py * 12) * 1.15;
|
|
37
|
+
const cloud = Math.sin(px * 4.3 + py * 1.2) * Math.sin(py * 5.8 - px * 0.9) * 1.6;
|
|
38
|
+
const grain = Math.sin(x * 1.71 + y * 2.31) * 0.32;
|
|
39
|
+
const value = 241 + tileTone + silk + cloud + grain;
|
|
40
|
+
const offset = (y * width + x) * 4;
|
|
41
|
+
colour[offset] = Math.round(THREE.MathUtils.lerp(value + 2, 181, joint));
|
|
42
|
+
colour[offset + 1] = Math.round(THREE.MathUtils.lerp(value + 1, 183, joint));
|
|
43
|
+
colour[offset + 2] = Math.round(THREE.MathUtils.lerp(value - 2, 178, joint));
|
|
44
|
+
colour[offset + 3] = 255;
|
|
45
|
+
const rough = Math.round(THREE.MathUtils.lerp(47 + cloud * 2.0, 176, joint));
|
|
46
|
+
roughness[offset] = roughness[offset + 1] = roughness[offset + 2] = rough;
|
|
47
|
+
roughness[offset + 3] = 255;
|
|
48
|
+
// 缓慢变化的波纹,而不是砂纸颗粒;细缝在反射掩码中单独变哑。
|
|
49
|
+
const dx = Math.sin(px * 17.3 + Math.cos(py * 9.7)) * 0.035 + Math.cos(py * 38 + px * 21) * 0.01;
|
|
50
|
+
const dy = Math.cos(py * 19.1 + Math.sin(px * 11.2)) * 0.035 + Math.sin(px * 27 - py * 31) * 0.01;
|
|
51
|
+
normal[offset] = glaze[offset] = Math.round(128 + dx * 127);
|
|
52
|
+
normal[offset + 1] = glaze[offset + 1] = Math.round(128 + dy * 127);
|
|
53
|
+
normal[offset + 2] = 255;
|
|
54
|
+
normal[offset + 3] = glaze[offset + 3] = 255;
|
|
55
|
+
glaze[offset + 2] = Math.round((1 - joint) * 255);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
map: dataTexture(colour, width, height, '珍珠陶瓷 / 一米砖与细灰缝', true),
|
|
60
|
+
roughnessMap: dataTexture(roughness, width, height, '珍珠陶瓷 / 釉面粗糙度'),
|
|
61
|
+
normalMap: dataTexture(normal, width, height, '珍珠陶瓷 / 轻微釉面起伏'),
|
|
62
|
+
glaze: dataTexture(glaze, width, height, '珍珠陶瓷 / 反射釉层与灰缝掩码'),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function makeWindowTexture() {
|
|
67
|
+
const width = 128;
|
|
68
|
+
const height = 128;
|
|
69
|
+
const data = new Uint8Array(width * height * 4);
|
|
70
|
+
for (let y = 0; y < height; y += 1) {
|
|
71
|
+
for (let x = 0; x < width; x += 1) {
|
|
72
|
+
const v = y / height;
|
|
73
|
+
const u = x / width;
|
|
74
|
+
const haze = 6 * Math.sin(u * 9 + v * 3) * Math.sin(v * 7);
|
|
75
|
+
const offset = (y * width + x) * 4;
|
|
76
|
+
data[offset] = Math.min(255, 238 + v * 16 + haze);
|
|
77
|
+
data[offset + 1] = Math.min(255, 241 + v * 12 + haze);
|
|
78
|
+
data[offset + 2] = Math.min(255, 232 + (1 - v) * 19 + haze);
|
|
79
|
+
data[offset + 3] = 255;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return dataTexture(data, width, height, '窗外 / 柔和天光', true);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createCeramicRoom({ renderer, scene, reducedMotion = false, doorOpen } = {}) {
|
|
86
|
+
if (doorOpen !== undefined && typeof doorOpen !== 'boolean') {
|
|
87
|
+
throw new Error(`createCeramicRoom 的 doorOpen 只接受 true、false 或不传,收到「${String(doorOpen)}」(${typeof doorOpen}),不能猜测门的初始状态。`);
|
|
88
|
+
}
|
|
89
|
+
const group = new THREE.Group();
|
|
90
|
+
group.name = 'room-ceramic / 珍珠白瓷';
|
|
91
|
+
const walls = [];
|
|
92
|
+
const geometries = new Set();
|
|
93
|
+
const materials = new Set();
|
|
94
|
+
const textures = new Set();
|
|
95
|
+
let disposed = false;
|
|
96
|
+
const ownMaterial = (material) => { materials.add(material); return material; };
|
|
97
|
+
const ownTexture = (texture) => { textures.add(texture); return texture; };
|
|
98
|
+
const porcelain = makePorcelainTextures();
|
|
99
|
+
Object.values(porcelain).forEach(ownTexture);
|
|
100
|
+
const floorMaterial = ownMaterial(new THREE.MeshPhysicalMaterial({
|
|
101
|
+
name: '珍珠白釉 / 镜面与细微起伏',
|
|
102
|
+
map: porcelain.map,
|
|
103
|
+
roughnessMap: porcelain.roughnessMap,
|
|
104
|
+
roughness: 1,
|
|
105
|
+
normalMap: porcelain.normalMap,
|
|
106
|
+
normalScale: new THREE.Vector2(0.85, 0.85),
|
|
107
|
+
metalness: 0,
|
|
108
|
+
clearcoat: 0.72,
|
|
109
|
+
clearcoatRoughness: 0.09,
|
|
110
|
+
envMapIntensity: 0.72,
|
|
111
|
+
ior: 1.48,
|
|
112
|
+
}));
|
|
113
|
+
const wallMaterial = ownMaterial(new THREE.MeshPhysicalMaterial({
|
|
114
|
+
name: '暖雾色陶板墙', color: '#dfdfd7', roughness: 0.48,
|
|
115
|
+
metalness: 0, clearcoat: 0.16, clearcoatRoughness: 0.35,
|
|
116
|
+
}));
|
|
117
|
+
wallMaterial.onBeforeCompile = (shader) => {
|
|
118
|
+
shader.vertexShader = shader.vertexShader.replace('#include <common>', `
|
|
119
|
+
#include <common>
|
|
120
|
+
varying vec3 vPorcelainPosition;
|
|
121
|
+
`).replace('#include <project_vertex>', `
|
|
122
|
+
#include <project_vertex>
|
|
123
|
+
vPorcelainPosition = (modelMatrix * vec4(transformed, 1.0)).xyz;
|
|
124
|
+
`);
|
|
125
|
+
shader.fragmentShader = shader.fragmentShader.replace('#include <common>', `
|
|
126
|
+
#include <common>
|
|
127
|
+
varying vec3 vPorcelainPosition;
|
|
128
|
+
`).replace('#include <color_fragment>', `
|
|
129
|
+
#include <color_fragment>
|
|
130
|
+
float row = vPorcelainPosition.y / 0.24;
|
|
131
|
+
vec2 tileUv = vec2((vPorcelainPosition.x + vPorcelainPosition.z) / 0.8 + mod(floor(row), 2.0) * 0.5, row);
|
|
132
|
+
vec2 seamDist = min(fract(tileUv), 1.0 - fract(tileUv));
|
|
133
|
+
vec2 seamAA = fwidth(tileUv) * 0.7;
|
|
134
|
+
vec2 seam = 1.0 - smoothstep(vec2(0.002), vec2(0.002) + seamAA, seamDist);
|
|
135
|
+
float tileShade = sin(floor(tileUv.x) * 7.3 + floor(tileUv.y) * 13.9) * 0.006;
|
|
136
|
+
diffuseColor.rgb *= 1.0 + tileShade - max(seam.x, seam.y) * 0.055;
|
|
137
|
+
`);
|
|
138
|
+
};
|
|
139
|
+
wallMaterial.customProgramCacheKey = () => 'room-ceramic-wall-grid-v1';
|
|
140
|
+
const trimMaterial = ownMaterial(new THREE.MeshStandardMaterial({ name: '亚光暖白边框', color: '#f2eee4', roughness: 0.29 }));
|
|
141
|
+
const baseMaterial = ownMaterial(new THREE.MeshStandardMaterial({ name: '石材地台侧沿', color: '#d6d3c9', roughness: 0.7 }));
|
|
142
|
+
const shadowMaterial = ownMaterial(new THREE.MeshStandardMaterial({ name: '细窄阴缝', color: '#858981', roughness: 0.8 }));
|
|
143
|
+
const metalMaterial = ownMaterial(new THREE.MeshStandardMaterial({ name: '香槟拉丝金属', color: '#aaa28b', metalness: 0.8, roughness: 0.26 }));
|
|
144
|
+
const doorMaterial = ownMaterial(new THREE.MeshStandardMaterial({ name: '浅米灰门扇', color: '#c9c5b7', roughness: 0.4 }));
|
|
145
|
+
const glassMaterial = ownMaterial(new THREE.MeshPhysicalMaterial({
|
|
146
|
+
name: '淡色磨砂窗玻璃', color: '#e5ece5', metalness: 0, roughness: 0.23,
|
|
147
|
+
transparent: true, opacity: 0.19, depthWrite: false, side: THREE.DoubleSide,
|
|
148
|
+
clearcoat: 1, clearcoatRoughness: 0.1,
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
function box(parent, name, width, height, depth, x, y, z, material = wallMaterial) {
|
|
152
|
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
|
153
|
+
geometries.add(geometry);
|
|
154
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
155
|
+
mesh.name = name;
|
|
156
|
+
mesh.position.set(x, y, z);
|
|
157
|
+
mesh.castShadow = true;
|
|
158
|
+
mesh.receiveShadow = true;
|
|
159
|
+
parent.add(mesh);
|
|
160
|
+
return mesh;
|
|
161
|
+
}
|
|
162
|
+
function wall(name, x, z) {
|
|
163
|
+
const wallGroup = new THREE.Group();
|
|
164
|
+
wallGroup.name = name;
|
|
165
|
+
group.add(wallGroup);
|
|
166
|
+
walls.push({ group: wallGroup, normal: new THREE.Vector3(x, 0, z) });
|
|
167
|
+
return wallGroup;
|
|
168
|
+
}
|
|
169
|
+
function ribbon(parent, name, length, x, z, alongZ = false) {
|
|
170
|
+
box(parent, `${name} / 踢脚`, alongZ ? 0.026 : length, 0.13, alongZ ? length : 0.026, x, 0.065, z, trimMaterial);
|
|
171
|
+
box(parent, `${name} / 顶边`, alongZ ? 0.065 : length, 0.065, alongZ ? length : 0.065, x, 3.365, z, trimMaterial);
|
|
172
|
+
box(parent, `${name} / 顶边阴缝`, alongZ ? 0.022 : length, 0.008, alongZ ? length : 0.022, x, 3.319, z, shadowMaterial);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
box(group, '整块室内地台', 8.4, 0.22, 6.4, 0, -0.135, 0, baseMaterial);
|
|
176
|
+
box(group, '地台下缘细线', 8.32, 0.025, 6.32, 0, -0.257, 0, shadowMaterial);
|
|
177
|
+
const floorGeometry = new THREE.PlaneGeometry(8, 6);
|
|
178
|
+
geometries.add(floorGeometry);
|
|
179
|
+
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
|
180
|
+
floor.name = '可摆放地面 / 珍珠陶瓷';
|
|
181
|
+
floor.rotation.x = -Math.PI / 2;
|
|
182
|
+
floor.position.y = 0;
|
|
183
|
+
floor.receiveShadow = true;
|
|
184
|
+
group.add(floor);
|
|
185
|
+
|
|
186
|
+
const north = wall('北墙 / 外开门', 0, -1);
|
|
187
|
+
const west = wall('西墙 / 天光高窗', -1, 0);
|
|
188
|
+
const east = wall('东墙', 1, 0);
|
|
189
|
+
const south = wall('南墙', 0, 1);
|
|
190
|
+
// 墙内侧严格落在 +/-4、+/-3;所有凹凸装饰置于边缘或室外。
|
|
191
|
+
box(north, '门左墙', 5.55, 3.4, 0.18, -1.225, 1.7, -3.09);
|
|
192
|
+
box(north, '门右墙', 1.05, 3.4, 0.18, 3.475, 1.7, -3.09);
|
|
193
|
+
box(north, '门上过梁', 1.4, 0.85, 0.18, 2.25, 2.975, -3.09);
|
|
194
|
+
ribbon(north, '门左', 5.55, -1.225, -2.988);
|
|
195
|
+
ribbon(north, '门右', 1.05, 3.475, -2.988);
|
|
196
|
+
box(north, '北墙连续顶线', 1.4, 0.065, 0.065, 2.25, 3.365, -2.99, trimMaterial);
|
|
197
|
+
box(east, '东墙墙体', 0.18, 3.4, 6, 4.09, 1.7, 0);
|
|
198
|
+
ribbon(east, '东墙', 6, 3.987, 0, true);
|
|
199
|
+
box(south, '南墙墙体', 8.36, 3.4, 0.18, 0, 1.7, 3.09);
|
|
200
|
+
ribbon(south, '南墙', 8, 0, 2.987);
|
|
201
|
+
|
|
202
|
+
// 左墙高窗:3.6 × 1.75 m,窗下仍有完整可用墙面。
|
|
203
|
+
const windowZ = -0.45;
|
|
204
|
+
const windowWidth = 3.6;
|
|
205
|
+
const windowBottom = 1.12;
|
|
206
|
+
const windowTop = 2.87;
|
|
207
|
+
box(west, '窗下实墙', 0.18, windowBottom, 6, -4.09, windowBottom / 2, 0);
|
|
208
|
+
box(west, '窗上实墙', 0.18, 3.4 - windowTop, 6, -4.09, (3.4 + windowTop) / 2, 0);
|
|
209
|
+
box(west, '窗北侧实墙', 0.18, windowTop - windowBottom, 0.75, -4.09, (windowTop + windowBottom) / 2, -2.625);
|
|
210
|
+
box(west, '窗南侧实墙', 0.18, windowTop - windowBottom, 1.65, -4.09, (windowTop + windowBottom) / 2, 2.175);
|
|
211
|
+
ribbon(west, '西墙', 6, -3.987, 0, true);
|
|
212
|
+
box(west, '窗台 / 白瓷倒角感', 0.31, 0.065, windowWidth + 0.18, -4.025, windowBottom, windowZ, trimMaterial);
|
|
213
|
+
box(west, '窗框上沿', 0.18, 0.065, windowWidth + 0.13, -4.075, windowTop, windowZ, trimMaterial);
|
|
214
|
+
for (const z of [windowZ - windowWidth / 2, windowZ + windowWidth / 2]) {
|
|
215
|
+
box(west, '窗框立边', 0.17, windowTop - windowBottom, 0.065, -4.075, (windowTop + windowBottom) / 2, z, trimMaterial);
|
|
216
|
+
}
|
|
217
|
+
for (const z of [windowZ - 0.6, windowZ + 0.6]) {
|
|
218
|
+
box(west, '纤细窗格', 0.095, windowTop - windowBottom, 0.04, -4.045, (windowTop + windowBottom) / 2, z, trimMaterial);
|
|
219
|
+
}
|
|
220
|
+
box(west, '窗格横梁', 0.08, 0.035, windowWidth, -4.055, 2.29, windowZ, trimMaterial);
|
|
221
|
+
const windowTexture = ownTexture(makeWindowTexture());
|
|
222
|
+
const windowLightMaterial = ownMaterial(new THREE.MeshBasicMaterial({
|
|
223
|
+
name: '窗外漫射天光', map: windowTexture, color: '#ffffff', side: THREE.DoubleSide, toneMapped: false,
|
|
224
|
+
}));
|
|
225
|
+
const windowGeometry = new THREE.PlaneGeometry(windowWidth, windowTop - windowBottom);
|
|
226
|
+
geometries.add(windowGeometry);
|
|
227
|
+
const luminousWindow = new THREE.Mesh(windowGeometry, windowLightMaterial);
|
|
228
|
+
luminousWindow.name = '可被地面真实反射的窗外天光';
|
|
229
|
+
luminousWindow.rotation.y = Math.PI / 2;
|
|
230
|
+
luminousWindow.position.set(-4.195, (windowTop + windowBottom) / 2, windowZ);
|
|
231
|
+
west.add(luminousWindow);
|
|
232
|
+
const glass = new THREE.Mesh(windowGeometry, glassMaterial);
|
|
233
|
+
glass.name = '高窗玻璃';
|
|
234
|
+
glass.rotation.y = Math.PI / 2;
|
|
235
|
+
glass.position.set(-4.075, (windowTop + windowBottom) / 2, windowZ);
|
|
236
|
+
west.add(glass);
|
|
237
|
+
|
|
238
|
+
// 门洞不是在完整墙面上贴一块门图;室内到室外有真正贯通的净空。
|
|
239
|
+
for (const z of [-2.976, -3.204]) {
|
|
240
|
+
for (const x of [1.51, 2.99]) box(north, '双面门框立边', 0.08, 2.59, 0.055, x, 1.295, z, trimMaterial);
|
|
241
|
+
box(north, '双面门框过梁', 1.56, 0.085, 0.055, 2.25, 2.59, z, trimMaterial);
|
|
242
|
+
}
|
|
243
|
+
box(north, '门洞右侧套口', 0.035, 2.55, 0.24, 2.933, 1.275, -3.09, trimMaterial);
|
|
244
|
+
box(north, '门洞左侧套口', 0.035, 2.55, 0.24, 1.567, 1.275, -3.09, trimMaterial);
|
|
245
|
+
box(north, '门槛', 1.4, 0.023, 0.38, 2.25, 0.006, -3.12, trimMaterial);
|
|
246
|
+
const hinge = new THREE.Group();
|
|
247
|
+
hinge.name = '固定向外开启的门扇';
|
|
248
|
+
hinge.position.set(1.59, 0, -3.17);
|
|
249
|
+
// doorOpen 不传时保留历史展示角 1.17(算 isOpen=true);显式 true/false 才落到统一端点。
|
|
250
|
+
hinge.rotation.y = doorOpen === undefined ? 1.17 : (doorOpen ? Math.PI / 2 : 0);
|
|
251
|
+
north.add(hinge);
|
|
252
|
+
box(hinge, '门扇', 1.32, 2.51, 0.065, 0.66, 1.27, 0, doorMaterial);
|
|
253
|
+
for (const z of [-0.035, 0.035]) {
|
|
254
|
+
box(hinge, '门板内嵌边左', 0.012, 2.17, 0.006, 0.13, 1.27, z, trimMaterial);
|
|
255
|
+
box(hinge, '门板内嵌边右', 0.012, 2.17, 0.006, 1.19, 1.27, z, trimMaterial);
|
|
256
|
+
for (const y of [0.185, 2.355]) box(hinge, '门板内嵌横边', 1.072, 0.012, 0.006, 0.66, y, z, trimMaterial);
|
|
257
|
+
box(hinge, '门把手底座', 0.038, 0.17, 0.02, 1.16, 1.12, z * 1.5, metalMaterial);
|
|
258
|
+
box(hinge, '门把手支柱', 0.035, 0.035, 0.085, 1.16, 1.14, z * 2.3, metalMaterial);
|
|
259
|
+
box(hinge, '门把手握柄', 0.15, 0.029, 0.027, 1.105, 1.14, z * 3.4, metalMaterial);
|
|
260
|
+
}
|
|
261
|
+
for (const y of [0.4, 1.25, 2.1]) box(hinge, '拉丝门铰', 0.035, 0.12, 0.082, 0, y, 0, metalMaterial);
|
|
262
|
+
|
|
263
|
+
// 窗光与窗属于同一个墙组,被剖开的墙不会留下悬空窗框或光源。
|
|
264
|
+
const windowFill = new THREE.SpotLight('#fff5df', 20, 13, Math.PI / 3.7, 0.72, 1.4);
|
|
265
|
+
windowFill.name = '柔和窗光';
|
|
266
|
+
windowFill.position.set(-3.88, 2.8, windowZ);
|
|
267
|
+
const lightTarget = new THREE.Object3D();
|
|
268
|
+
lightTarget.position.set(1.25, 0.0, 1.1);
|
|
269
|
+
windowFill.target = lightTarget;
|
|
270
|
+
windowFill.castShadow = false;
|
|
271
|
+
west.add(windowFill, lightTarget);
|
|
272
|
+
|
|
273
|
+
const reflection = createCeramicReflection({ surface: floor, material: floorMaterial, glazeTexture: porcelain.glaze, renderer, scene });
|
|
274
|
+
return {
|
|
275
|
+
group,
|
|
276
|
+
walls,
|
|
277
|
+
door: createDoorHandle(hinge),
|
|
278
|
+
update() { /* 静态陶瓷空间没有循环动画;反射在实际渲染时跟随相机。 */ },
|
|
279
|
+
dispose() {
|
|
280
|
+
if (disposed) return;
|
|
281
|
+
disposed = true;
|
|
282
|
+
reflection.dispose();
|
|
283
|
+
geometries.forEach((geometry) => geometry.dispose());
|
|
284
|
+
materials.forEach((material) => material.dispose());
|
|
285
|
+
textures.forEach((texture) => texture.dispose());
|
|
286
|
+
group.removeFromParent();
|
|
287
|
+
group.clear();
|
|
288
|
+
},
|
|
289
|
+
meta: {
|
|
290
|
+
id: 'room-ceramic-pearl-v1',
|
|
291
|
+
name: '珍珠白瓷',
|
|
292
|
+
dimensions: { ...SIZE },
|
|
293
|
+
door: { ...DOOR, position: [2.25, 0, -3], opens: 'outward' },
|
|
294
|
+
floorBounds: { minX: -4, maxX: 4, minZ: -3, maxZ: 3, y: 0 },
|
|
295
|
+
window: { wall: 'west', centerZ: windowZ, width: windowWidth, bottom: windowBottom, top: windowTop },
|
|
296
|
+
furniture: [],
|
|
297
|
+
reflection: '实时镜像相机 + 线性离屏渲染 + 釉面扰动 + 柔化取样',
|
|
298
|
+
reducedMotion,
|
|
299
|
+
description: '暖白陶瓷砖、细灰缝、微起伏釉面、高窗长反光与双面精细门框;室内净空保持规则长方体。',
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
// 门扇通用小接口(P1c-2a):门板、把手、门环等活动部件由各底座挂在 hinge 组下,
|
|
4
|
+
// 这里只统一开合端点与世界包围盒,不引入动画、物理或输入。门框、门檐、墙体留在
|
|
5
|
+
// 墙组,不进 door.group;活动网格仍由底座统一释放,本接口不新建动画循环或监听。
|
|
6
|
+
const OPEN_ROTATION_Y = Math.PI / 2;
|
|
7
|
+
|
|
8
|
+
/** 关闭端点 rotation.y=0;打开端点 rotation.y=π/2(两套底座均向北侧室外开启)。 */
|
|
9
|
+
export function createDoorHandle(hinge, id = 'entry-door') {
|
|
10
|
+
return {
|
|
11
|
+
id,
|
|
12
|
+
group: hinge,
|
|
13
|
+
setOpen(open) {
|
|
14
|
+
if (typeof open !== 'boolean') {
|
|
15
|
+
throw new Error(`房门 setOpen 只接受 true 或 false,收到「${String(open)}」(${typeof open}),不能猜测开合意图。`);
|
|
16
|
+
}
|
|
17
|
+
hinge.rotation.y = open ? OPEN_ROTATION_Y : 0;
|
|
18
|
+
},
|
|
19
|
+
isOpen() {
|
|
20
|
+
return hinge.rotation.y !== 0;
|
|
21
|
+
},
|
|
22
|
+
// 真实活动网格在当前姿态下的世界包围盒:先连祖先链一起刷新世界矩阵(父组平移
|
|
23
|
+
// 也算数),再用逐顶点精确模式累加。每次返回新的普通数值对象,调用方改不动
|
|
24
|
+
// Three.js 缓存;导航层负责把它变成 collider。
|
|
25
|
+
getBounds() {
|
|
26
|
+
hinge.updateWorldMatrix(true, true);
|
|
27
|
+
const box = new THREE.Box3().setFromObject(hinge, true);
|
|
28
|
+
return {
|
|
29
|
+
minX: box.min.x, maxX: box.max.x,
|
|
30
|
+
minY: box.min.y, maxY: box.max.y,
|
|
31
|
+
minZ: box.min.z, maxZ: box.max.z,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|