@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,67 @@
|
|
|
1
|
+
// 自制阅读休憩家具。米制、+Y向上、+Z正面,底面中心为原点。
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
// 圆角实体沿Z挤出,边缘真实倒角;归一到指定尺寸,顶面与包络不靠声明替身。
|
|
5
|
+
function rounded(w, h, d, radius = 0.025) {
|
|
6
|
+
const r = Math.min(radius, w / 4, h / 4), x = -w / 2, y = -h / 2;
|
|
7
|
+
const shape = new THREE.Shape();
|
|
8
|
+
shape.moveTo(x + r, y); shape.lineTo(x + w - r, y);
|
|
9
|
+
shape.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
10
|
+
shape.lineTo(x + w, y + h - r); shape.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
11
|
+
shape.lineTo(x + r, y + h); shape.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
12
|
+
shape.lineTo(x, y + r); shape.quadraticCurveTo(x, y, x + r, y);
|
|
13
|
+
const g = new THREE.ExtrudeGeometry(shape, { depth: d, steps: 1, curveSegments: 4,
|
|
14
|
+
bevelEnabled: true, bevelSegments: 2, bevelSize: Math.min(0.006, d / 5), bevelThickness: Math.min(0.006, d / 5) });
|
|
15
|
+
g.center(); g.computeBoundingBox();
|
|
16
|
+
const size = g.boundingBox.getSize(new THREE.Vector3());
|
|
17
|
+
g.scale(w / size.x, h / size.y, d / size.z); g.computeVertexNormals();
|
|
18
|
+
return g;
|
|
19
|
+
}
|
|
20
|
+
function woodTexture() {
|
|
21
|
+
const size = 128, data = new Uint8Array(size * size * 4); let seed = 20260930;
|
|
22
|
+
for (let v = 0; v < size; v++) for (let u = 0; u < size; u++) {
|
|
23
|
+
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0;
|
|
24
|
+
const tone = 210 + 4 * Math.sin(v * 0.6 + Math.sin(u * 0.04)) + (seed / 4294967296 - 0.5) * 2;
|
|
25
|
+
const i = (v * size + u) * 4; data[i] = tone; data[i + 1] = tone - 26; data[i + 2] = tone - 61; data[i + 3] = 255;
|
|
26
|
+
}
|
|
27
|
+
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
28
|
+
texture.colorSpace = THREE.SRGBColorSpace; texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
|
29
|
+
texture.magFilter = THREE.LinearFilter; texture.needsUpdate = true; return texture;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createCeramicLowCabinet() {
|
|
33
|
+
const group = new THREE.Group(); group.name = 'ceramic-low-cabinet-01';
|
|
34
|
+
const owned = new Set(), keep = value => { owned.add(value); return value; }; let disposed = false;
|
|
35
|
+
const dispose = () => {
|
|
36
|
+
if (disposed) return; disposed = true; group.removeFromParent();
|
|
37
|
+
for (const resource of owned) resource.dispose(); owned.clear(); group.clear();
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const material = options => keep(new THREE.MeshStandardMaterial(options));
|
|
41
|
+
const wood = material({ map: keep(woodTexture()), roughness: 0.7 });
|
|
42
|
+
const glaze = material({ color: '#eee9dd', roughness: 0.43 });
|
|
43
|
+
const brass = material({ color: '#b69557', metalness: 0.72, roughness: 0.38 });
|
|
44
|
+
const add = (g, m, x, y, z, name) => {
|
|
45
|
+
const mesh = new THREE.Mesh(keep(g), m); mesh.name = name; mesh.position.set(x, y, z);
|
|
46
|
+
mesh.castShadow = mesh.receiveShadow = true; group.add(mesh); return mesh;
|
|
47
|
+
};
|
|
48
|
+
const box = (w, h, d, m, x, y, z, name) => add(new THREE.BoxGeometry(w, h, d), m, x, y, z, name);
|
|
49
|
+
const soft = (w, h, d, m, x, y, z, name, radius) => add(rounded(w, h, d, radius), m, x, y, z, name);
|
|
50
|
+
for (const x of [-0.44, 0.44]) for (const z of [-0.18, 0.18])
|
|
51
|
+
soft(0.075, 0.14, 0.075, wood, x, 0.07, z, '矮柜短脚');
|
|
52
|
+
for (const x of [-0.52, 0.52]) box(0.045, 0.65, 0.50, glaze, x, 0.455, 0, '完整柜侧板');
|
|
53
|
+
box(1.04, 0.05, 0.50, glaze, 0, 0.15, 0, '完整柜底板');
|
|
54
|
+
box(1.04, 0.64, 0.035, glaze, 0, 0.46, -0.2325, '完整柜背板');
|
|
55
|
+
box(0.03, 0.64, 0.46, glaze, 0, 0.46, 0, '柜内中撑');
|
|
56
|
+
for (const x of [-0.252, 0.252]) {
|
|
57
|
+
soft(0.494, 0.61, 0.035, glaze, x, 0.465, 0.235, '关闭柜门', 0.015);
|
|
58
|
+
const k = x < 0 ? -0.055 : 0.055;
|
|
59
|
+
const post = add(new THREE.CylinderGeometry(0.008, 0.008, 0.017, 12), brass, k, 0.52, 0.259, '把手连接座');
|
|
60
|
+
post.rotation.x = Math.PI / 2;
|
|
61
|
+
add(new THREE.SphereGeometry(0.011, 12, 8), brass, k, 0.52, 0.267, '黄铜门把手');
|
|
62
|
+
}
|
|
63
|
+
// 顶板用完整实体盒确保冻结rect九点都处于真正水平最高面。
|
|
64
|
+
box(1.10, 0.07, 0.56, wood, 0, 0.815, 0, '平整浅木厚顶板');
|
|
65
|
+
return { group, dispose };
|
|
66
|
+
} catch (error) { dispose(); throw error; }
|
|
67
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"assetVersion": 1,
|
|
4
|
+
"status": "internal",
|
|
5
|
+
"assetId": "ceramic-vase-01",
|
|
6
|
+
"name": "暖釉细颈花瓶",
|
|
7
|
+
"category": "vase",
|
|
8
|
+
"description": "暖白象牙釉面细颈花瓶:圆润瓶腹向下收出稳固底足,向上经溜肩收到短细颈,瓶口微微外翻成真实口沿;瓶身有外壁与内壁的真实壁厚,顶部中央是开口,内壁下行接封闭的内瓶底,是空瓶、不插花。宽深各 0.16 米、高 0.30 米,本地 +Z 正面为无文字的釉面,仅有轻微釉色变化。仅作陈列摆件,无任何资料动作;仅可依附接受 vase 类别的承载面(如陶瓷展示台台面),当前书桌桌面只接受台灯类别,不能因为“桌面摆件”的称呼承诺能放上书桌。",
|
|
9
|
+
"styleIds": ["ceramic"],
|
|
10
|
+
"modelRef": "ceramic-vase-01",
|
|
11
|
+
"previewRef": "assets/ceramic-vase-01/preview.webp",
|
|
12
|
+
"placeholder": false,
|
|
13
|
+
"bounds": { "minX": -0.08, "maxX": 0.08, "minY": 0, "maxY": 0.3, "minZ": -0.08, "maxZ": 0.08 },
|
|
14
|
+
"placement": { "allowedKinds": ["surface"], "allowedYawDeg": [0, 90, 180, 270] },
|
|
15
|
+
"clearanceBoxes": [],
|
|
16
|
+
"approachPoint": null,
|
|
17
|
+
"surfaces": [],
|
|
18
|
+
"capabilities": [],
|
|
19
|
+
"recommendations": { "note": "推荐写实项目展示区,与暖釉陶瓷展示台同系列搭配;也可混搭童话阅读房等其他风格。风格只是软推荐,不构成摆放准入;能否放置始终以承载面的类别与空间校验为准。" },
|
|
20
|
+
"source": { "origin": "自制", "license": "MIT", "note": "本包 model.js 自制 Three.js 程序化模型与固定种子 DataTexture 釉面粗糙度纹理,无外部模型或贴图。" }
|
|
21
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// 暖釉细颈花瓶:自制程序化候选模型,不依赖 DOM、网络或实验文件。
|
|
2
|
+
// 米制、+Y 向上、+Z 正面;底面中心原点。冻结包络 X、Z 各 ±0.08、Y[0,0.30]。
|
|
3
|
+
// 设计常数(检查按此独立手算):外壳 lathe 剖面外底近轴 (0.001,0)→底足缘 (0.053,0.006)
|
|
4
|
+
// →足上收腰 (0.046,0.022)→瓶腹最宽 (0.074,0.105)→肩 (0.049,0.212)→颈 (0.0265,0.262)
|
|
5
|
+
// →外翻口沿 (0.041,0.294)→口沿顶外缘 (0.041,0.299);口沿顶环外 0.041/内 0.033 @Y0.299;
|
|
6
|
+
// 内壁剖面自口内缘 (0.033,0.299) 下行至内底缘 (0.045,0.030);内底圆盘 r0.045 @Y0.030。
|
|
7
|
+
// 腹部壁厚 0.074−0.066=0.008;底部实心厚 0.030。空瓶不插花,顶部中央为真实开口。
|
|
8
|
+
import * as THREE from 'three';
|
|
9
|
+
|
|
10
|
+
// 釉面粗糙度微变:NoColorSpace 数据纹理,数值接近 255,只轻微扰动基础粗糙度。
|
|
11
|
+
function glazeRoughness() {
|
|
12
|
+
const size = 256;
|
|
13
|
+
const data = new Uint8Array(size * size * 4);
|
|
14
|
+
let seed = 20260951;
|
|
15
|
+
const random = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed / 4294967296; };
|
|
16
|
+
for (let v = 0; v < size; v++) for (let u = 0; u < size; u++) {
|
|
17
|
+
const wave = 240 + 3 * Math.sin((u + 1.5 * Math.sin(v * 0.04)) * 0.05) + 2 * Math.sin(v * 0.021) + (random() - 0.5) * 2;
|
|
18
|
+
const i = (v * size + u) * 4;
|
|
19
|
+
data[i] = data[i + 1] = data[i + 2] = Math.max(0, Math.min(255, Math.round(wave)));
|
|
20
|
+
data[i + 3] = 255;
|
|
21
|
+
}
|
|
22
|
+
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
23
|
+
texture.colorSpace = THREE.NoColorSpace;
|
|
24
|
+
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
|
25
|
+
texture.magFilter = THREE.LinearFilter;
|
|
26
|
+
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
27
|
+
texture.generateMipmaps = true;
|
|
28
|
+
texture.needsUpdate = true;
|
|
29
|
+
return texture;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 椭圆扁平环(口沿顶面):外缘到内缘的真实窄环,法线朝上。
|
|
33
|
+
function annulus(y, rOuter, rInner, segments) {
|
|
34
|
+
const positions = [], indices = [];
|
|
35
|
+
const ringOf = (r) => { const ids = []; for (let i = 0; i < segments; i++) {
|
|
36
|
+
const t = i / segments * Math.PI * 2;
|
|
37
|
+
const id = positions.length / 3;
|
|
38
|
+
positions.push(r * Math.cos(t), y, r * Math.sin(t));
|
|
39
|
+
ids.push(id);
|
|
40
|
+
} return ids; };
|
|
41
|
+
const outer = ringOf(rOuter), inner = ringOf(rInner);
|
|
42
|
+
for (let i = 0; i < segments; i++) {
|
|
43
|
+
const j = (i + 1) % segments;
|
|
44
|
+
indices.push(outer[i], inner[i], outer[j], inner[i], inner[j], outer[j]);
|
|
45
|
+
}
|
|
46
|
+
const geometry = new THREE.BufferGeometry();
|
|
47
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
48
|
+
geometry.setIndex(indices);
|
|
49
|
+
geometry.computeVertexNormals();
|
|
50
|
+
return geometry;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 圆盘(内瓶底):真实封底,法线朝上(腔内可见)。
|
|
54
|
+
function disc(y, r, segments) {
|
|
55
|
+
const positions = [], indices = [];
|
|
56
|
+
const center = positions.length / 3;
|
|
57
|
+
positions.push(0, y, 0);
|
|
58
|
+
const ring = [];
|
|
59
|
+
for (let i = 0; i < segments; i++) {
|
|
60
|
+
const t = i / segments * Math.PI * 2;
|
|
61
|
+
const id = positions.length / 3;
|
|
62
|
+
positions.push(r * Math.cos(t), y, r * Math.sin(t));
|
|
63
|
+
ring.push(id);
|
|
64
|
+
}
|
|
65
|
+
for (let i = 0; i < segments; i++) {
|
|
66
|
+
const j = (i + 1) % segments;
|
|
67
|
+
indices.push(center, ring[j], ring[i]);
|
|
68
|
+
}
|
|
69
|
+
const geometry = new THREE.BufferGeometry();
|
|
70
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
71
|
+
geometry.setIndex(indices);
|
|
72
|
+
geometry.computeVertexNormals();
|
|
73
|
+
return geometry;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createCeramicVase() {
|
|
77
|
+
const group = new THREE.Group();
|
|
78
|
+
group.name = 'ceramic-vase-01';
|
|
79
|
+
const owned = new Set();
|
|
80
|
+
const keep = (resource) => { owned.add(resource); return resource; };
|
|
81
|
+
let disposed = false;
|
|
82
|
+
const dispose = () => {
|
|
83
|
+
if (disposed) return;
|
|
84
|
+
disposed = true;
|
|
85
|
+
group.removeFromParent();
|
|
86
|
+
for (const resource of owned) resource.dispose();
|
|
87
|
+
owned.clear(); group.clear();
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
const rough = keep(glazeRoughness());
|
|
91
|
+
const glaze = keep(new THREE.MeshPhysicalMaterial({ color: '#e8e2d3', roughness: 0.16, roughnessMap: rough, clearcoat: 0.6, clearcoatRoughness: 0.25 }));
|
|
92
|
+
const innerGlaze = keep(new THREE.MeshStandardMaterial({ color: '#cfc8b8', roughness: 0.55 }));
|
|
93
|
+
const add = (geometry, material, name) => {
|
|
94
|
+
const mesh = new THREE.Mesh(keep(geometry), material);
|
|
95
|
+
mesh.name = name; mesh.castShadow = mesh.receiveShadow = true;
|
|
96
|
+
group.add(mesh); return mesh;
|
|
97
|
+
};
|
|
98
|
+
// 瓶身外壳:真实旋转曲面,含外底封底、底足、收腰、瓶腹、肩、细颈与外翻口沿;
|
|
99
|
+
// 剖面自下而上,法线朝外,顶部止于口沿顶外缘。
|
|
100
|
+
add(new THREE.LatheGeometry([
|
|
101
|
+
new THREE.Vector2(0.001, 0), new THREE.Vector2(0.045, 0), new THREE.Vector2(0.053, 0.006),
|
|
102
|
+
new THREE.Vector2(0.053, 0.016), new THREE.Vector2(0.046, 0.022), new THREE.Vector2(0.064, 0.055),
|
|
103
|
+
new THREE.Vector2(0.074, 0.105), new THREE.Vector2(0.071, 0.160), new THREE.Vector2(0.049, 0.212),
|
|
104
|
+
new THREE.Vector2(0.030, 0.238), new THREE.Vector2(0.0265, 0.262), new THREE.Vector2(0.029, 0.278),
|
|
105
|
+
new THREE.Vector2(0.041, 0.294), new THREE.Vector2(0.041, 0.299),
|
|
106
|
+
], 36), glaze, '瓶身外壳');
|
|
107
|
+
// 口沿顶面:外缘 0.041 到内缘 0.033 的真实窄环,顶部中央由此保持开口。
|
|
108
|
+
add(annulus(0.299, 0.041, 0.033, 36), glaze, '瓶口沿面');
|
|
109
|
+
// 瓶内壁:自口沿内缘下行到内底缘的旋转曲面,法线朝腔内;与外壳之间是真实壁厚。
|
|
110
|
+
add(new THREE.LatheGeometry([
|
|
111
|
+
new THREE.Vector2(0.033, 0.299), new THREE.Vector2(0.031, 0.288), new THREE.Vector2(0.023, 0.262),
|
|
112
|
+
new THREE.Vector2(0.027, 0.238), new THREE.Vector2(0.041, 0.212), new THREE.Vector2(0.063, 0.155),
|
|
113
|
+
new THREE.Vector2(0.066, 0.105), new THREE.Vector2(0.062, 0.055), new THREE.Vector2(0.045, 0.030),
|
|
114
|
+
], 36), innerGlaze, '瓶内壁');
|
|
115
|
+
// 内瓶底:封闭的瓶内底面,空瓶有底。
|
|
116
|
+
add(disc(0.030, 0.045, 36), innerGlaze, '瓶内底');
|
|
117
|
+
return { group, dispose };
|
|
118
|
+
} catch (error) { dispose(); throw error; }
|
|
119
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"assetVersion": 1,
|
|
4
|
+
"status": "internal",
|
|
5
|
+
"assetId": "fairy-bed-01",
|
|
6
|
+
"name": "林间蜂蜜木床",
|
|
7
|
+
"category": "bed",
|
|
8
|
+
"description": "蜂蜜木圆润床头与较低床尾,四脚支承床框及承托板,奶油厚床垫、双枕与低饱和青绿折被。宽1.50、深2.20、高不超过1.10米;+Z为床尾,接近位在床尾。仅地面静态休憩家具,无坐卧动作,不提供承载面。",
|
|
9
|
+
"styleIds": [
|
|
10
|
+
"fairy"
|
|
11
|
+
],
|
|
12
|
+
"modelRef": "fairy-bed-01",
|
|
13
|
+
"previewRef": "assets/fairy-bed-01/preview.webp",
|
|
14
|
+
"placeholder": false,
|
|
15
|
+
"bounds": {
|
|
16
|
+
"minX": -0.75,
|
|
17
|
+
"maxX": 0.75,
|
|
18
|
+
"minY": 0,
|
|
19
|
+
"maxY": 1.1,
|
|
20
|
+
"minZ": -1.1,
|
|
21
|
+
"maxZ": 1.1
|
|
22
|
+
},
|
|
23
|
+
"placement": {
|
|
24
|
+
"allowedKinds": [
|
|
25
|
+
"floor"
|
|
26
|
+
],
|
|
27
|
+
"allowedYawDeg": [
|
|
28
|
+
0,
|
|
29
|
+
90,
|
|
30
|
+
180,
|
|
31
|
+
270
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"clearanceBoxes": [
|
|
35
|
+
{
|
|
36
|
+
"name": "正面接近操作区",
|
|
37
|
+
"bounds": {
|
|
38
|
+
"minX": -0.8,
|
|
39
|
+
"maxX": 0.8,
|
|
40
|
+
"minY": 0,
|
|
41
|
+
"maxY": 1.9,
|
|
42
|
+
"minZ": 1.1,
|
|
43
|
+
"maxZ": 2.1
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"approachPoint": {
|
|
48
|
+
"x": 0,
|
|
49
|
+
"z": 1.65
|
|
50
|
+
},
|
|
51
|
+
"surfaces": [],
|
|
52
|
+
"capabilities": [],
|
|
53
|
+
"recommendations": {
|
|
54
|
+
"note": "适合温暖阅读休憩房,与蜂蜜木书桌、书架、奶油布料及暖白陶瓷搭配;风格只是软推荐,混搭允许,摆放以空间和类别校验为准。"
|
|
55
|
+
},
|
|
56
|
+
"source": {
|
|
57
|
+
"origin": "自制",
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"note": "自制Three.js程序化实体几何,无外部模型或贴图;有木面者使用固定种子DataTexture。"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// 自制阅读休憩家具。米制、+Y向上、+Z正面,底面中心为原点。
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
// 圆角实体沿Z挤出,边缘真实倒角;归一到指定尺寸,顶面与包络不靠声明替身。
|
|
5
|
+
function rounded(w, h, d, radius = 0.025) {
|
|
6
|
+
const r = Math.min(radius, w / 4, h / 4), x = -w / 2, y = -h / 2;
|
|
7
|
+
const shape = new THREE.Shape();
|
|
8
|
+
shape.moveTo(x + r, y); shape.lineTo(x + w - r, y);
|
|
9
|
+
shape.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
10
|
+
shape.lineTo(x + w, y + h - r); shape.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
11
|
+
shape.lineTo(x + r, y + h); shape.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
12
|
+
shape.lineTo(x, y + r); shape.quadraticCurveTo(x, y, x + r, y);
|
|
13
|
+
const g = new THREE.ExtrudeGeometry(shape, { depth: d, steps: 1, curveSegments: 4,
|
|
14
|
+
bevelEnabled: true, bevelSegments: 2, bevelSize: Math.min(0.006, d / 5), bevelThickness: Math.min(0.006, d / 5) });
|
|
15
|
+
g.center(); g.computeBoundingBox();
|
|
16
|
+
const size = g.boundingBox.getSize(new THREE.Vector3());
|
|
17
|
+
g.scale(w / size.x, h / size.y, d / size.z); g.computeVertexNormals();
|
|
18
|
+
return g;
|
|
19
|
+
}
|
|
20
|
+
function woodTexture() {
|
|
21
|
+
const size = 128, data = new Uint8Array(size * size * 4); let seed = 20260930;
|
|
22
|
+
for (let v = 0; v < size; v++) for (let u = 0; u < size; u++) {
|
|
23
|
+
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0;
|
|
24
|
+
const tone = 210 + 4 * Math.sin(v * 0.6 + Math.sin(u * 0.04)) + (seed / 4294967296 - 0.5) * 2;
|
|
25
|
+
const i = (v * size + u) * 4; data[i] = tone; data[i + 1] = tone - 26; data[i + 2] = tone - 61; data[i + 3] = 255;
|
|
26
|
+
}
|
|
27
|
+
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
28
|
+
texture.colorSpace = THREE.SRGBColorSpace; texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
|
29
|
+
texture.magFilter = THREE.LinearFilter; texture.needsUpdate = true; return texture;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createFairyBed() {
|
|
33
|
+
const group = new THREE.Group(); group.name = 'fairy-bed-01';
|
|
34
|
+
const owned = new Set(), keep = value => { owned.add(value); return value; }; let disposed = false;
|
|
35
|
+
const dispose = () => {
|
|
36
|
+
if (disposed) return; disposed = true; group.removeFromParent();
|
|
37
|
+
for (const resource of owned) resource.dispose(); owned.clear(); group.clear();
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const material = options => keep(new THREE.MeshStandardMaterial(options));
|
|
41
|
+
const wood = material({ map: keep(woodTexture()), roughness: 0.7 });
|
|
42
|
+
const cream = material({ color: '#eee4cf', roughness: 0.92 });
|
|
43
|
+
const green = material({ color: '#86a69d', roughness: 0.94 });
|
|
44
|
+
const add = (g, m, x, y, z, name) => {
|
|
45
|
+
const mesh = new THREE.Mesh(keep(g), m); mesh.name = name; mesh.position.set(x, y, z);
|
|
46
|
+
mesh.castShadow = mesh.receiveShadow = true; group.add(mesh); return mesh;
|
|
47
|
+
};
|
|
48
|
+
const box = (w, h, d, m, x, y, z, name) => add(new THREE.BoxGeometry(w, h, d), m, x, y, z, name);
|
|
49
|
+
const soft = (w, h, d, m, x, y, z, name, radius) => add(rounded(w, h, d, radius), m, x, y, z, name);
|
|
50
|
+
for (const x of [-0.66, 0.66]) for (const z of [-0.95, 0.95])
|
|
51
|
+
soft(0.11, 0.38, 0.11, wood, x, 0.19, z, '落地床脚');
|
|
52
|
+
for (const x of [-0.69, 0.69]) soft(0.10, 0.18, 2.12, wood, x, 0.38, 0, '侧床框');
|
|
53
|
+
for (const z of [-1.02, 1.02]) soft(1.46, 0.18, 0.10, wood, 0, 0.38, z, '端床框');
|
|
54
|
+
box(1.34, 0.04, 2.02, wood, 0, 0.43, 0, '床垫承托板');
|
|
55
|
+
soft(1.48, 0.76, 0.09, wood, 0, 0.72, -1.05, '圆润床头板', 0.12);
|
|
56
|
+
soft(1.48, 0.29, 0.09, wood, 0, 0.485, 1.05, '低床尾板', 0.06);
|
|
57
|
+
soft(1.34, 0.18, 1.98, cream, 0, 0.54, 0, '奶油厚床垫', 0.055);
|
|
58
|
+
for (const x of [-0.34, 0.34]) soft(0.54, 0.12, 0.35, cream, x, 0.685, -0.69, '厚枕头', 0.06);
|
|
59
|
+
soft(1.30, 0.07, 0.96, green, 0, 0.665, 0.40, '青绿折被', 0.045);
|
|
60
|
+
soft(1.27, 0.022, 0.13, green, 0, 0.708, -0.01, '折被厚折边', 0.01);
|
|
61
|
+
return { group, dispose };
|
|
62
|
+
} catch (error) { dispose(); throw error; }
|
|
63
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"assetVersion": 1,
|
|
4
|
+
"status": "internal",
|
|
5
|
+
"assetId": "fairy-book-stack-01",
|
|
6
|
+
"name": "林间双册书摆",
|
|
7
|
+
"category": "ornament",
|
|
8
|
+
"description": "童话风双册精装书摆件:下册蜂蜜棕平放,上册低饱和青绿在其上轻微错开并小角度斜叠;两册各有完整的硬封面顶板、封底板、书脊板与三面内缩的书页层,书脊上缀两条细凸饰线,封面无文字。宽 0.22 米、深 0.14 米、叠高 0.075 米,本地 +Z 正面为书页侧面。是一个整体装饰摆件,不是可点击的业务文档,无任何资料动作,也不提供承载面、不允许把新物品继续放到书上;仅可依附接受 ornament 类别的承载面(如陶瓷展示台台面),当前书桌桌面只接受台灯类别,不能因为“桌面摆件”的称呼承诺能放上书桌。",
|
|
9
|
+
"styleIds": ["fairy"],
|
|
10
|
+
"modelRef": "fairy-book-stack-01",
|
|
11
|
+
"previewRef": "assets/fairy-book-stack-01/preview.webp",
|
|
12
|
+
"placeholder": false,
|
|
13
|
+
"bounds": { "minX": -0.11, "maxX": 0.11, "minY": 0, "maxY": 0.075, "minZ": -0.07, "maxZ": 0.07 },
|
|
14
|
+
"placement": { "allowedKinds": ["surface"], "allowedYawDeg": [0, 90, 180, 270] },
|
|
15
|
+
"clearanceBoxes": [],
|
|
16
|
+
"approachPoint": null,
|
|
17
|
+
"surfaces": [],
|
|
18
|
+
"capabilities": [],
|
|
19
|
+
"recommendations": { "note": "推荐童话阅读房、书桌旁与展示台,与林间蜂蜜木书架、书桌同系列搭配;也可混搭写实房间。风格只是软推荐,不构成摆放准入;能否放置始终以承载面的类别与空间校验为准。" },
|
|
20
|
+
"source": { "origin": "自制", "license": "MIT", "note": "本包 model.js 自制 Three.js 程序化模型,无纹理、无外部模型或贴图。" }
|
|
21
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// 林间双册书摆:自制程序化候选模型,不依赖 DOM、网络或实验文件。
|
|
2
|
+
// 米制、+Y 向上、+Z 正面;底面中心原点。冻结包络 X ±0.11、Y[0,0.075]、Z ±0.07。
|
|
3
|
+
// 设计常数(检查按此独立手算):下册 0.208×0.041×0.132(蜂蜜棕),中心 (0,0.0205,0),
|
|
4
|
+
// 封面顶/封底厚 0.005、书脊宽 0.014 居 X[−0.104,−0.090]、页块 0.188×0.030×0.120 内缩
|
|
5
|
+
// 且右缘 0.098<封面 0.104;上册 0.176×0.032×0.106(低饱和青绿),子组中心
|
|
6
|
+
// (0.012,0.057,−0.002) 绕 Y 转 7°,底面与下册顶面相接(Y0.041)不悬空;上册同构四件。
|
|
7
|
+
// 书脊饰线各两条(0.0016×0.0016×页深−0.07),凸出书脊外缘 0.0008,不生成文字贴图。
|
|
8
|
+
// 整体一件摆件,无承载面,不允许把新物品继续挂到书上。
|
|
9
|
+
import * as THREE from 'three';
|
|
10
|
+
|
|
11
|
+
// 一册合上的精装书:封面顶板、封底板、书脊板与内缩页块,四件都有真实厚度;
|
|
12
|
+
// 以册中心为原点,书脊居 X− 侧,页块贴书脊内缘、右侧与前后内缩。
|
|
13
|
+
function addBook(parent, keep, size, materials, prefix) {
|
|
14
|
+
const [width, height, depth] = size;
|
|
15
|
+
const { cover, pages, trim } = materials;
|
|
16
|
+
const add = (geometry, material, name, x, y, z) => {
|
|
17
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
18
|
+
mesh.name = name; mesh.position.set(x, y, z);
|
|
19
|
+
mesh.castShadow = mesh.receiveShadow = true;
|
|
20
|
+
parent.add(mesh); return mesh;
|
|
21
|
+
};
|
|
22
|
+
const board = 0.005; // 硬封面厚度
|
|
23
|
+
const spine = 0.014; // 书脊板宽
|
|
24
|
+
const pageInset = 0.006; // 页块右侧与前后内缩
|
|
25
|
+
const pageWidth = width - spine - pageInset;
|
|
26
|
+
const pageHeight = height - board * 2 - 0.001; // 与封面顶底各留 0.0005,不共面
|
|
27
|
+
add(keep(new THREE.BoxGeometry(width, board, depth)), cover, `${prefix}封面顶`, 0, height / 2 - board / 2, 0);
|
|
28
|
+
add(keep(new THREE.BoxGeometry(width, board, depth)), cover, `${prefix}封底`, 0, -height / 2 + board / 2, 0);
|
|
29
|
+
add(keep(new THREE.BoxGeometry(spine, pageHeight + board * 2, depth)), cover, `${prefix}书脊`, -width / 2 + spine / 2, 0, 0);
|
|
30
|
+
add(keep(new THREE.BoxGeometry(pageWidth, pageHeight, depth - pageInset * 2)), pages, `${prefix}书页`, (spine - pageInset) / 2, 0, 0);
|
|
31
|
+
// 书脊饰线:两条细凸线,比书深短 0.07,沿书脊长度方向错开且不出封面。
|
|
32
|
+
for (const [index, offset] of [-0.03, 0.03].entries()) {
|
|
33
|
+
add(keep(new THREE.BoxGeometry(0.0016, 0.0016, depth - 0.07)), trim, `${prefix}书脊饰线${index + 1}`,
|
|
34
|
+
-width / 2 - 0.0008, (index === 0 ? 0.008 : -0.008), offset);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createFairyBookStack() {
|
|
39
|
+
const group = new THREE.Group();
|
|
40
|
+
group.name = 'fairy-book-stack-01';
|
|
41
|
+
const owned = new Set();
|
|
42
|
+
const keep = (resource) => { owned.add(resource); return resource; };
|
|
43
|
+
let disposed = false;
|
|
44
|
+
const dispose = () => {
|
|
45
|
+
if (disposed) return;
|
|
46
|
+
disposed = true;
|
|
47
|
+
group.removeFromParent();
|
|
48
|
+
for (const resource of owned) resource.dispose();
|
|
49
|
+
owned.clear(); group.clear();
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
const honeyCover = keep(new THREE.MeshPhysicalMaterial({ color: '#a8793f', roughness: 0.52, clearcoat: 0.3, clearcoatRoughness: 0.4 }));
|
|
53
|
+
const sageCover = keep(new THREE.MeshPhysicalMaterial({ color: '#6f8d7d', roughness: 0.5, clearcoat: 0.3, clearcoatRoughness: 0.4 }));
|
|
54
|
+
const pages = keep(new THREE.MeshStandardMaterial({ color: '#ece2c8', roughness: 0.85 }));
|
|
55
|
+
const trim = keep(new THREE.MeshStandardMaterial({ color: '#e6d6a8', roughness: 0.4, metalness: 0.35 }));
|
|
56
|
+
|
|
57
|
+
// 下册平放:底面贴地 Y=0,页块三面内缩于封面。
|
|
58
|
+
const lower = new THREE.Group();
|
|
59
|
+
group.add(lower);
|
|
60
|
+
addBook(lower, keep, [0.208, 0.041, 0.132], { cover: honeyCover, pages, trim }, '下册');
|
|
61
|
+
lower.position.set(0, 0.0205, 0);
|
|
62
|
+
|
|
63
|
+
// 上册轻微错开+小角度斜叠:子组整体变换,底面与下册顶面相接。
|
|
64
|
+
const upper = new THREE.Group();
|
|
65
|
+
group.add(upper);
|
|
66
|
+
addBook(upper, keep, [0.176, 0.032, 0.106], { cover: sageCover, pages, trim }, '上册');
|
|
67
|
+
upper.position.set(0.012, 0.057, -0.002);
|
|
68
|
+
upper.rotation.y = 7 * Math.PI / 180;
|
|
69
|
+
return { group, dispose };
|
|
70
|
+
} catch (error) { dispose(); throw error; }
|
|
71
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"assetVersion": 1,
|
|
4
|
+
"status": "internal",
|
|
5
|
+
"assetId": "fairy-bookshelf-01",
|
|
6
|
+
"name": "林间蜂蜜木书架",
|
|
7
|
+
"category": "shelf",
|
|
8
|
+
"description": "童话风格的落地开放木书架:蜂蜜色暖木、边角圆润、顶部藤叶点缀,层内放少量苔绿、褪色蓝与暖米色的装饰书;正面为本地 +Z,正面开放、背面有背板可分辨;整体点击后显示该实例绑定的资料集合。宽 1.2 米、高 1.8 米、深 0.5 米。",
|
|
9
|
+
"styleIds": ["fairy"],
|
|
10
|
+
"modelRef": "fairy-bookshelf-01",
|
|
11
|
+
"previewRef": "assets/fairy-bookshelf-01/preview.webp",
|
|
12
|
+
"placeholder": false,
|
|
13
|
+
"bounds": { "minX": -0.6, "maxX": 0.6, "minY": 0, "maxY": 1.8, "minZ": -0.25, "maxZ": 0.25 },
|
|
14
|
+
"placement": { "allowedKinds": ["floor"], "allowedYawDeg": [0, 90, 180, 270] },
|
|
15
|
+
"clearanceBoxes": [
|
|
16
|
+
{
|
|
17
|
+
"name": "书架前阅读取书区",
|
|
18
|
+
"bounds": { "minX": -0.65, "maxX": 0.65, "minY": 0, "maxY": 1.9, "minZ": 0.25, "maxZ": 1.3 }
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"approachPoint": { "x": 0, "z": 0.9 },
|
|
22
|
+
"surfaces": [],
|
|
23
|
+
"capabilities": ["open-document-collection"],
|
|
24
|
+
"recommendations": {
|
|
25
|
+
"note": "适合自然主题阅读区和温暖的项目介绍室,推荐童话房间,也可在写实房间混搭;靠墙、正面朝已保留的通路。"
|
|
26
|
+
},
|
|
27
|
+
"source": {
|
|
28
|
+
"origin": "自制",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"note": "Three.js 程序化建模与自制材质(固定种子 DataTexture 木纹),生成文件为本包 model.js;自 E4a 样件(实验提交 f824ce3)原样迁入,造型与材质未改动。"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// 林间蜂蜜木书架(fairy-bookshelf-01)· 首款正式家具模型。
|
|
2
|
+
// 自 E4a 实验样件原样迁入(样件提交 f824ce3,2026-09-28;E4b-1 入库,仅改本头部说明),
|
|
3
|
+
// 造型、几何与材质未改动;经 web/assets/furniture/index.json 登记,由同目录 registry.js
|
|
4
|
+
// 按固定字面量动态导入创建。程序化建模+自制材质:Three.js 随包副本,木纹用固定种子
|
|
5
|
+
// DataTexture 生成,不依赖 canvas/DOM,可在无 DOM 的 Node 中真实构造
|
|
6
|
+
// (tests/furniture-library.test.mjs 依赖这一点)。
|
|
7
|
+
// 约定:米制、+Y 向上、正面本地 +Z、底面中心为原点;根组 position/rotation 为零、
|
|
8
|
+
// scale 为 1;全部实体几何落在声明盒 X[-0.6,0.6]×Y[0,1.8]×Z[-0.25,0.25] 内。
|
|
9
|
+
// 构造不接业务数据、不内置房间坐标、不写实例 ID;dispose 幂等,只释放本次工厂
|
|
10
|
+
// 拥有的几何/材质/纹理(共享几何只登记一次),两实例互不释放对方资源。
|
|
11
|
+
import * as THREE from 'three';
|
|
12
|
+
|
|
13
|
+
/** 与底座同型的固定种子随机(LCG 家族),保证纹理可复现。 */
|
|
14
|
+
function seededRandom(seed) {
|
|
15
|
+
return () => {
|
|
16
|
+
seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
|
|
17
|
+
let n = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
18
|
+
n = (n + Math.imul(n ^ (n >>> 7), 61 | n)) ^ n;
|
|
19
|
+
return ((n ^ (n >>> 14)) >>> 0) / 4294967296;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 256×256 蜂蜜木纹:低对比条纹沿纹理 U 方向变化(贴到板上即沿板长方向展开),
|
|
25
|
+
* 叠加细噪点与少量节疤环。同一张纹理同时作 map(sRGB 颜色纹理)与 bumpMap。
|
|
26
|
+
*/
|
|
27
|
+
function makeWoodTexture(keep) {
|
|
28
|
+
const size = 256;
|
|
29
|
+
const data = new Uint8Array(size * size * 4);
|
|
30
|
+
const rng = seededRandom(20260928);
|
|
31
|
+
const knots = [];
|
|
32
|
+
for (let i = 0; i < 3; i++) knots.push({ u: 24 + rng() * (size - 48), v: 24 + rng() * (size - 48), r: 5 + rng() * 7 });
|
|
33
|
+
for (let v = 0; v < size; v += 1) {
|
|
34
|
+
const wobble = Math.sin(v * 0.055) * 3.2 + Math.sin(v * 0.017) * 6.5;
|
|
35
|
+
for (let u = 0; u < size; u += 1) {
|
|
36
|
+
// 主纹:沿 U 的密条纹,相位被低频扰动推弯;行间再叠一层行噪声
|
|
37
|
+
const stripe = Math.sin((u + wobble) * 0.62 + Math.sin((u + wobble) * 0.11) * 2.4);
|
|
38
|
+
const grain = stripe * 0.5 + 0.5;
|
|
39
|
+
let tone = 213 + grain * 24 - 12 + (rng() - 0.5) * 9;
|
|
40
|
+
for (const knot of knots) {
|
|
41
|
+
const du = u - knot.u; const dv = v - knot.v;
|
|
42
|
+
const ring = Math.sqrt(du * du + dv * dv);
|
|
43
|
+
if (ring < knot.r) tone -= 14 + Math.sin(ring * 2.6) * 9; // 节疤略深
|
|
44
|
+
}
|
|
45
|
+
tone = Math.max(150, Math.min(245, tone));
|
|
46
|
+
const index = (v * size + u) * 4;
|
|
47
|
+
data[index] = tone; data[index + 1] = tone - 26; data[index + 2] = tone - 62; data[index + 3] = 255;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
51
|
+
texture.colorSpace = THREE.SRGBColorSpace; // 当颜色纹理使用,按颜色空间标记
|
|
52
|
+
texture.wrapS = THREE.RepeatWrapping;
|
|
53
|
+
texture.wrapT = THREE.RepeatWrapping;
|
|
54
|
+
texture.magFilter = THREE.LinearFilter;
|
|
55
|
+
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
56
|
+
texture.generateMipmaps = true;
|
|
57
|
+
texture.needsUpdate = true;
|
|
58
|
+
return keep(texture);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createFairyBookshelf() {
|
|
62
|
+
const group = new THREE.Group();
|
|
63
|
+
group.name = 'fairy-bookshelf-01';
|
|
64
|
+
const owned = new Set();
|
|
65
|
+
const keep = (resource) => { owned.add(resource); return resource; };
|
|
66
|
+
|
|
67
|
+
const woodMap = makeWoodTexture(keep);
|
|
68
|
+
// 材质预算 8 个:蜜木主色(略深用于背板/底座)、边角轻蜡质、三色装饰书、叶、藤枝
|
|
69
|
+
const woodMain = keep(new THREE.MeshStandardMaterial({ map: woodMap, bumpMap: woodMap, bumpScale: 0.014, roughness: 0.62, metalness: 0.02 }));
|
|
70
|
+
const woodDeep = keep(new THREE.MeshStandardMaterial({ map: woodMap, bumpMap: woodMap, bumpScale: 0.012, color: '#cdb28c', roughness: 0.7, metalness: 0.02 }));
|
|
71
|
+
const woodEdge = keep(new THREE.MeshStandardMaterial({ map: woodMap, color: '#efdcbb', roughness: 0.48, metalness: 0.03 })); // 端部压边:轻微蜡质反光
|
|
72
|
+
const bookMoss = keep(new THREE.MeshStandardMaterial({ color: '#6b7d4f', roughness: 0.82 }));
|
|
73
|
+
const bookBlue = keep(new THREE.MeshStandardMaterial({ color: '#7d93a8', roughness: 0.8 }));
|
|
74
|
+
const bookCream = keep(new THREE.MeshStandardMaterial({ color: '#d8c39a', roughness: 0.86 }));
|
|
75
|
+
const leafMat = keep(new THREE.MeshStandardMaterial({ color: '#5f7d43', roughness: 0.9, side: THREE.DoubleSide }));
|
|
76
|
+
const vineMat = keep(new THREE.MeshStandardMaterial({ color: '#6a5236', roughness: 0.95 }));
|
|
77
|
+
|
|
78
|
+
const boxGeometry = (w, h, d) => keep(new THREE.BoxGeometry(w, h, d));
|
|
79
|
+
const addBox = (w, h, d, material, x, y, z) => {
|
|
80
|
+
const item = new THREE.Mesh(boxGeometry(w, h, d), material);
|
|
81
|
+
item.position.set(x, y, z);
|
|
82
|
+
item.castShadow = true;
|
|
83
|
+
item.receiveShadow = true;
|
|
84
|
+
group.add(item);
|
|
85
|
+
return item;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// ── 结构:底座(两层)→ 侧立板 → 背板 → 三块层板 → 顶板 → 圆润压边 ──
|
|
89
|
+
addBox(1.14, 0.08, 0.42, woodDeep, 0, 0.04, 0); // 底座主体 Y[0,0.08]
|
|
90
|
+
addBox(1.18, 0.03, 0.46, woodDeep, 0, 0.095, 0); // 底座顶沿 Y[0.08,0.11]
|
|
91
|
+
addBox(0.045, 1.61, 0.44, woodMain, -0.575, 0.915, 0); // 左立板 X[-0.5975,-0.5525] Y[0.11,1.72]
|
|
92
|
+
addBox(0.045, 1.61, 0.44, woodMain, 0.575, 0.915, 0); // 右立板
|
|
93
|
+
addBox(1.06, 1.55, 0.018, woodDeep, 0, 0.885, -0.19); // 背板 Z[-0.199,-0.181] Y[0.11,1.66]
|
|
94
|
+
for (const y of [0.52, 0.94, 1.36]) addBox(1.085, 0.035, 0.42, woodMain, 0, y, 0.01); // 三块层板
|
|
95
|
+
addBox(1.18, 0.05, 0.46, woodMain, 0, 1.745, 0); // 顶板 Y[1.72,1.77]
|
|
96
|
+
|
|
97
|
+
// 顶前圆润压边(轴沿 X 的圆柱)与两侧前棱竖圆角柱(轴沿 Y)
|
|
98
|
+
const capBeam = new THREE.Mesh(keep(new THREE.CylinderGeometry(0.026, 0.026, 1.18, 12)), woodEdge);
|
|
99
|
+
capBeam.rotation.z = Math.PI / 2;
|
|
100
|
+
capBeam.position.set(0, 1.772, 0.214); // 最前 0.24、最高 1.798
|
|
101
|
+
capBeam.castShadow = true;
|
|
102
|
+
group.add(capBeam);
|
|
103
|
+
for (const x of [-0.575, 0.575]) {
|
|
104
|
+
const roundEdge = new THREE.Mesh(keep(new THREE.CylinderGeometry(0.018, 0.018, 1.55, 10)), woodEdge);
|
|
105
|
+
roundEdge.position.set(x, 0.935, 0.201); // X ±0.593、Z 0.219
|
|
106
|
+
roundEdge.castShadow = true;
|
|
107
|
+
group.add(roundEdge);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── 装饰书:苔绿/褪色蓝/暖米,贴背板直立,少量斜靠与平放,不铺满每层 ──
|
|
111
|
+
// 层净空:A Y[0.11,0.5025]、B Y[0.5375,0.9225]、C Y[0.9575,1.3425];顶层留给藤叶
|
|
112
|
+
const standing = (x, baseY, h, material, lean = 0) => {
|
|
113
|
+
const book = addBox(0.05, h, 0.17, material, x, baseY + h / 2, -0.09);
|
|
114
|
+
if (lean) { book.rotation.z = lean; book.position.x += (Math.sin(lean) * h) / 2; book.position.y = baseY + (Math.cos(lean) * h) / 2 + 0.012; }
|
|
115
|
+
return book;
|
|
116
|
+
};
|
|
117
|
+
standing(-0.44, 0.11, 0.30, bookMoss);
|
|
118
|
+
standing(-0.37, 0.11, 0.26, bookBlue);
|
|
119
|
+
standing(-0.30, 0.11, 0.32, bookCream);
|
|
120
|
+
standing(-0.17, 0.11, 0.30, bookBlue, -0.21); // 斜靠 12°
|
|
121
|
+
standing(-0.44, 0.5375, 0.26, bookBlue);
|
|
122
|
+
standing(-0.37, 0.5375, 0.30, bookCream);
|
|
123
|
+
standing(-0.30, 0.5375, 0.24, bookMoss);
|
|
124
|
+
standing(-0.44, 0.9575, 0.28, bookCream);
|
|
125
|
+
standing(-0.37, 0.9575, 0.25, bookMoss);
|
|
126
|
+
addBox(0.30, 0.05, 0.22, bookBlue, -0.05, 0.9825, -0.09); // C 层平放一册
|
|
127
|
+
|
|
128
|
+
// ── 上角藤叶:贴背板顶沿与两角,不遮开放层与正面;细藤枝沿背板下垂 ──
|
|
129
|
+
// 叶片是压扁球体,rotY 后的世界包络半径按对角 √(sx²+sz²) 校核,保证整叶留在盒内
|
|
130
|
+
const leafGeometry = keep(new THREE.SphereGeometry(1, 8, 5)); // 所有叶片共享同一几何
|
|
131
|
+
const leaves = [
|
|
132
|
+
[-0.40, 1.772, -0.135, 0.06, 0.014, 0.09, 0.35],
|
|
133
|
+
[-0.15, 1.768, -0.138, 0.058, 0.014, 0.088, -0.2],
|
|
134
|
+
[0.12, 1.774, -0.132, 0.06, 0.014, 0.09, 0.55],
|
|
135
|
+
[0.38, 1.77, -0.135, 0.058, 0.014, 0.088, -0.4],
|
|
136
|
+
[-0.47, 1.70, -0.13, 0.055, 0.016, 0.085, 0.8],
|
|
137
|
+
[0.47, 1.70, -0.13, 0.055, 0.016, 0.085, -0.7],
|
|
138
|
+
[0.44, 1.32, -0.135, 0.06, 0.015, 0.088, 0.15],
|
|
139
|
+
];
|
|
140
|
+
for (const [x, y, z, sx, sy, sz, rotY] of leaves) {
|
|
141
|
+
const leaf = new THREE.Mesh(leafGeometry, leafMat);
|
|
142
|
+
leaf.scale.set(sx, sy, sz);
|
|
143
|
+
leaf.rotation.y = rotY;
|
|
144
|
+
leaf.position.set(x, y, z);
|
|
145
|
+
leaf.castShadow = true;
|
|
146
|
+
group.add(leaf);
|
|
147
|
+
}
|
|
148
|
+
for (const side of [-1, 1]) {
|
|
149
|
+
const vine = new THREE.Mesh(keep(new THREE.CylinderGeometry(0.007, 0.011, 0.55, 8)), vineMat);
|
|
150
|
+
vine.position.set(side * 0.47, 1.50, -0.165);
|
|
151
|
+
vine.rotation.x = 0.12;
|
|
152
|
+
vine.rotation.z = side * 0.06;
|
|
153
|
+
vine.castShadow = true;
|
|
154
|
+
group.add(vine);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let disposed = false;
|
|
158
|
+
function dispose() {
|
|
159
|
+
if (disposed) return;
|
|
160
|
+
disposed = true;
|
|
161
|
+
group.removeFromParent();
|
|
162
|
+
for (const resource of owned) resource.dispose();
|
|
163
|
+
owned.clear();
|
|
164
|
+
group.clear();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { group, dispose };
|
|
168
|
+
}
|
|
Binary file
|