@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.
Files changed (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/README.zh.md +100 -0
  4. package/client.js +122 -0
  5. package/cordis.patch.yml +8 -0
  6. package/dist/check-building.js +1669 -0
  7. package/dist/check-room.js +980 -0
  8. package/dist/index.js +2873 -0
  9. package/package.json +60 -0
  10. package/skills/specdev-building/SKILL.md +85 -0
  11. package/skills/specdev-building/assets/layouts/court.json +53 -0
  12. package/skills/specdev-building/assets/layouts/row.json +48 -0
  13. package/skills/specdev-building/assets/layouts/single.json +27 -0
  14. package/skills/specdev-building/assets/layouts/tower-9.json +71 -0
  15. package/skills/specdev-building/assets/layouts/tower.json +59 -0
  16. package/skills/specdev-building/references/blueprint-contract.md +126 -0
  17. package/skills/specdev-building/references/diagnostics.md +101 -0
  18. package/skills/specdev-building/references/layouts.md +112 -0
  19. package/skills/specdev-business/SKILL.md +91 -0
  20. package/skills/specdev-business/assets/plan-template.md +66 -0
  21. package/skills/specdev-business/assets/spec-template.md +111 -0
  22. package/skills/specdev-business/assets/tasks-template.md +36 -0
  23. package/skills/specdev-business/references/change.md +117 -0
  24. package/skills/specdev-business/references/evidence-review.md +125 -0
  25. package/skills/specdev-business/references/implementation.md +67 -0
  26. package/skills/specdev-business/references/project-contract.md +211 -0
  27. package/skills/specdev-business/references/source-map.md +110 -0
  28. package/skills/specdev-business/references/specification.md +93 -0
  29. package/skills/specdev-business/references/workflow.md +126 -0
  30. package/skills/specdev-room/SKILL.md +77 -0
  31. package/skills/specdev-room/assets/intro-room.json +14 -0
  32. package/skills/specdev-room/references/diagnostics.md +21 -0
  33. package/skills/specdev-room/references/room-contract.md +59 -0
  34. package/vendor/archify-renderer/VERSION.md +52 -0
  35. package/vendor/archify-renderer/archify/LICENSE +22 -0
  36. package/vendor/archify-renderer/archify/THIRD_PARTY_NOTICES.md +56 -0
  37. package/vendor/archify-renderer/archify/assets/template.html +14787 -0
  38. package/vendor/archify-renderer/archify/bin/archify.mjs +1990 -0
  39. package/vendor/archify-renderer/archify/renderers/shared/brand-marks.mjs +563 -0
  40. package/vendor/archify-renderer/archify/renderers/shared/cli.mjs +218 -0
  41. package/vendor/archify-renderer/archify/renderers/shared/desktop-readability.mjs +26 -0
  42. package/vendor/archify-renderer/archify/renderers/shared/diagnostics.mjs +127 -0
  43. package/vendor/archify-renderer/archify/renderers/shared/engineering-profiles.mjs +157 -0
  44. package/vendor/archify-renderer/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  45. package/vendor/archify-renderer/archify/renderers/shared/generated-validators.mjs +13 -0
  46. package/vendor/archify-renderer/archify/renderers/shared/geometry.mjs +1423 -0
  47. package/vendor/archify-renderer/archify/renderers/shared/i18n.mjs +594 -0
  48. package/vendor/archify-renderer/archify/renderers/shared/legend.mjs +217 -0
  49. package/vendor/archify-renderer/archify/renderers/shared/output-path.mjs +321 -0
  50. package/vendor/archify-renderer/archify/renderers/shared/repository-evidence.mjs +235 -0
  51. package/vendor/archify-renderer/archify/renderers/shared/text-fit.mjs +49 -0
  52. package/vendor/archify-renderer/archify/renderers/shared/utils.mjs +232 -0
  53. package/vendor/archify-renderer/archify/renderers/shared/validator.mjs +86 -0
  54. package/vendor/archify-renderer/archify/renderers/workflow/render-workflow.mjs +35 -0
  55. package/vendor/archify-renderer/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  56. package/vendor/archify-renderer/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  57. package/vendor/archify-renderer/archify/schemas/common.schema.json +115 -0
  58. package/vendor/archify-renderer/archify/schemas/workflow.schema.json +428 -0
  59. package/vendor/archify-renderer/archify/scripts/check-render-output.mjs +835 -0
  60. package/vendor/archify-renderer/shims/process.mjs +18 -0
  61. package/vendor/archify-renderer/stubs/child_process.mjs +2 -0
  62. package/vendor/archify-renderer/stubs/crypto.mjs +3 -0
  63. package/vendor/archify-renderer/stubs/dns-promises.mjs +2 -0
  64. package/vendor/archify-renderer/stubs/fs.mjs +3 -0
  65. package/vendor/archify-renderer/stubs/http.mjs +3 -0
  66. package/vendor/archify-renderer/stubs/https.mjs +3 -0
  67. package/vendor/archify-renderer/stubs/net.mjs +3 -0
  68. package/vendor/archify-renderer/stubs/path.mjs +3 -0
  69. package/vendor/archify-renderer/stubs/url.mjs +3 -0
  70. package/web/assets/building-kit.js +1157 -0
  71. package/web/assets/building-look.js +185 -0
  72. package/web/assets/building-plan.js +495 -0
  73. package/web/assets/building-scene.js +529 -0
  74. package/web/assets/building-walk.js +248 -0
  75. package/web/assets/building.js +859 -0
  76. package/web/assets/business.js +124 -0
  77. package/web/assets/chart-link.js +165 -0
  78. package/web/assets/common.js +302 -0
  79. package/web/assets/details.js +197 -0
  80. package/web/assets/evidence.js +30 -0
  81. package/web/assets/furniture/SOURCES.md +24 -0
  82. package/web/assets/furniture/assets/ceramic-display-stand-01/asset.json +25 -0
  83. package/web/assets/furniture/assets/ceramic-display-stand-01/model.js +116 -0
  84. package/web/assets/furniture/assets/ceramic-display-stand-01/preview.webp +0 -0
  85. package/web/assets/furniture/assets/ceramic-floor-lamp-01/asset.json +61 -0
  86. package/web/assets/furniture/assets/ceramic-floor-lamp-01/model.js +56 -0
  87. package/web/assets/furniture/assets/ceramic-floor-lamp-01/preview.webp +0 -0
  88. package/web/assets/furniture/assets/ceramic-low-cabinet-01/asset.json +81 -0
  89. package/web/assets/furniture/assets/ceramic-low-cabinet-01/model.js +67 -0
  90. package/web/assets/furniture/assets/ceramic-low-cabinet-01/preview.webp +0 -0
  91. package/web/assets/furniture/assets/ceramic-vase-01/asset.json +21 -0
  92. package/web/assets/furniture/assets/ceramic-vase-01/model.js +119 -0
  93. package/web/assets/furniture/assets/ceramic-vase-01/preview.webp +0 -0
  94. package/web/assets/furniture/assets/fairy-bed-01/asset.json +61 -0
  95. package/web/assets/furniture/assets/fairy-bed-01/model.js +63 -0
  96. package/web/assets/furniture/assets/fairy-bed-01/preview.webp +0 -0
  97. package/web/assets/furniture/assets/fairy-book-stack-01/asset.json +21 -0
  98. package/web/assets/furniture/assets/fairy-book-stack-01/model.js +71 -0
  99. package/web/assets/furniture/assets/fairy-book-stack-01/preview.webp +0 -0
  100. package/web/assets/furniture/assets/fairy-bookshelf-01/asset.json +32 -0
  101. package/web/assets/furniture/assets/fairy-bookshelf-01/model.js +168 -0
  102. package/web/assets/furniture/assets/fairy-bookshelf-01/preview.webp +0 -0
  103. package/web/assets/furniture/assets/fairy-desk-01/asset.json +25 -0
  104. package/web/assets/furniture/assets/fairy-desk-01/model.js +132 -0
  105. package/web/assets/furniture/assets/fairy-desk-01/preview.webp +0 -0
  106. package/web/assets/furniture/assets/fairy-notice-board-01/asset.json +23 -0
  107. package/web/assets/furniture/assets/fairy-notice-board-01/model.js +125 -0
  108. package/web/assets/furniture/assets/fairy-notice-board-01/preview.webp +0 -0
  109. package/web/assets/furniture/assets/fairy-potted-plant-01/asset.json +21 -0
  110. package/web/assets/furniture/assets/fairy-potted-plant-01/model.js +128 -0
  111. package/web/assets/furniture/assets/fairy-potted-plant-01/preview.webp +0 -0
  112. package/web/assets/furniture/assets/fairy-reading-chair-01/asset.json +61 -0
  113. package/web/assets/furniture/assets/fairy-reading-chair-01/model.js +66 -0
  114. package/web/assets/furniture/assets/fairy-reading-chair-01/preview.webp +0 -0
  115. package/web/assets/furniture/assets/fairy-table-lamp-01/asset.json +21 -0
  116. package/web/assets/furniture/assets/fairy-table-lamp-01/model.js +182 -0
  117. package/web/assets/furniture/assets/fairy-table-lamp-01/preview.webp +0 -0
  118. package/web/assets/furniture/catalog.js +137 -0
  119. package/web/assets/furniture/index.json +110 -0
  120. package/web/assets/furniture/registry.js +72 -0
  121. package/web/assets/furniture-page.css +68 -0
  122. package/web/assets/furniture-page.js +302 -0
  123. package/web/assets/home.js +65 -0
  124. package/web/assets/node-detail.js +148 -0
  125. package/web/assets/panel-link.js +47 -0
  126. package/web/assets/read.js +746 -0
  127. package/web/assets/room-scene.js +386 -0
  128. package/web/assets/room.css +287 -0
  129. package/web/assets/room.js +437 -0
  130. package/web/assets/rooms/bindings.js +449 -0
  131. package/web/assets/rooms/ceramic-reflection.js +193 -0
  132. package/web/assets/rooms/ceramic-room.js +302 -0
  133. package/web/assets/rooms/door.js +35 -0
  134. package/web/assets/rooms/fairy-room.js +492 -0
  135. package/web/assets/rooms/furniture-assembly.js +113 -0
  136. package/web/assets/rooms/navigation.js +262 -0
  137. package/web/assets/rooms/placeholder-catalog.json +71 -0
  138. package/web/assets/rooms/placeholder-furniture.js +76 -0
  139. package/web/assets/rooms/placement.js +588 -0
  140. package/web/assets/rooms/reader.js +441 -0
  141. package/web/assets/rooms/reading-gate.js +157 -0
  142. package/web/assets/rooms/room-lighting.js +50 -0
  143. package/web/assets/rooms/templates.json +50 -0
  144. package/web/assets/rooms/walk-frame.js +64 -0
  145. package/web/assets/rooms/walk-session.js +138 -0
  146. package/web/assets/save-result.js +89 -0
  147. package/web/assets/screenshots/business.png +0 -0
  148. package/web/assets/screenshots/node-detail.png +0 -0
  149. package/web/assets/screenshots/project-home.png +0 -0
  150. package/web/assets/screenshots/workflow.png +0 -0
  151. package/web/assets/showcase.js +232 -0
  152. package/web/assets/style.css +836 -0
  153. package/web/assets/template.js +7 -0
  154. package/web/assets/theme.js +29 -0
  155. package/web/assets/three/OrbitControls.js +1972 -0
  156. package/web/assets/three/VERSION.md +12 -0
  157. package/web/assets/three/three.core.js +60586 -0
  158. package/web/assets/three/three.module.js +19719 -0
  159. package/web/assets/workspaces.css +21 -0
  160. package/web/assets/workspaces.js +81 -0
  161. package/web/building.html +131 -0
  162. package/web/business.html +43 -0
  163. package/web/furniture.html +47 -0
  164. package/web/index.html +57 -0
  165. package/web/read.html +107 -0
  166. package/web/room.html +146 -0
  167. package/web/showcase.html +50 -0
  168. package/web/workspaces.html +32 -0
@@ -0,0 +1,25 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "assetVersion": 1,
4
+ "status": "internal",
5
+ "assetId": "fairy-desk-01",
6
+ "name": "林间蜂蜜木书桌",
7
+ "category": "desk",
8
+ "description": "蜂蜜色暖木书桌,连续水平厚桌面带柔和倒角,四条收脚木腿、桌裙与侧横枨,桌下留空;本地 +Z 正面有静态浅抽屉、细缝和小黄铜拉手,叶形浅装饰与同系列书架呼应。宽 1.6 米、高 0.75 米、深 0.8 米。桌面只承载台灯类物品,不提供抽屉开合或资料动作。",
9
+ "styleIds": ["fairy"],
10
+ "modelRef": "fairy-desk-01",
11
+ "previewRef": "assets/fairy-desk-01/preview.webp",
12
+ "placeholder": false,
13
+ "bounds": { "minX": -0.8, "maxX": 0.8, "minY": 0, "maxY": 0.75, "minZ": -0.4, "maxZ": 0.4 },
14
+ "placement": { "allowedKinds": ["floor"], "allowedYawDeg": [0, 90, 180, 270] },
15
+ "clearanceBoxes": [
16
+ { "name": "桌前操作留空", "bounds": { "minX": -0.8, "maxX": 0.8, "minY": 0, "maxY": 1.9, "minZ": 0.4, "maxZ": 1.3 } }
17
+ ],
18
+ "approachPoint": { "x": 0, "z": 1 },
19
+ "surfaces": [
20
+ { "surfaceId": "desktop", "name": "桌面可摆放区", "rect": { "minX": -0.7, "maxX": 0.7, "minZ": -0.3, "maxZ": 0.3 }, "y": 0.75, "allowedCategories": ["lamp"] }
21
+ ],
22
+ "capabilities": [],
23
+ "recommendations": { "note": "推荐童话阅读区与温暖的项目介绍区,和林间蜂蜜木书架搭配;也可混搭写实房。桌面只接受 lamp 类别,正面保留操作空间。" },
24
+ "source": { "origin": "自制", "license": "MIT", "note": "当前资产包 model.js 自制 Three.js 程序化模型与固定种子 DataTexture 木纹,无外部模型或贴图。" }
25
+ }
@@ -0,0 +1,132 @@
1
+ // 林间蜂蜜木书桌:自制程序化候选模型,不依赖 DOM、网络或实验文件。
2
+ // 米制、+Y 向上、+Z 正面;底面中心原点。四层圆角环形成真实倒角和连续水平桌面。
3
+ import * as THREE from 'three';
4
+
5
+ function woodTexture() {
6
+ const size = 256;
7
+ const data = new Uint8Array(size * size * 4);
8
+ let seed = 20260929;
9
+ const random = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed / 4294967296; };
10
+ for (let v = 0; v < size; v++) for (let u = 0; u < size; u++) {
11
+ // 木纹沿 U 延伸,横向 V 仅轻微起伏;不使用高对比节疤或随机大条纹。
12
+ const wave = v + 2.2 * Math.sin(u * 0.025) + 0.8 * Math.sin(u * 0.071);
13
+ const tone = 211 + 5 * Math.sin(wave * 0.48) + 2 * Math.sin(wave * 1.7) + (random() - 0.5) * 3;
14
+ const i = (v * size + u) * 4;
15
+ data[i] = tone; data[i + 1] = tone - 28; data[i + 2] = tone - 65; data[i + 3] = 255;
16
+ }
17
+ const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
18
+ texture.colorSpace = THREE.SRGBColorSpace;
19
+ texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
20
+ texture.magFilter = THREE.LinearFilter;
21
+ texture.minFilter = THREE.LinearMipmapLinearFilter;
22
+ texture.generateMipmaps = true;
23
+ texture.needsUpdate = true;
24
+ return texture;
25
+ }
26
+
27
+ // 水平板:上下平面、圆润角点与上下斜边均为真实实体几何,顶面没有支撑替身。
28
+ function roundedBoard(width, height, depth, bevel) {
29
+ const positions = [], uvs = [], indices = [];
30
+ const ring = (y, inset) => {
31
+ const radius = 0.018 - inset;
32
+ const hx = width / 2 - inset, hz = depth / 2 - inset;
33
+ const points = [];
34
+ for (let corner = 0; corner < 4; corner++) {
35
+ const angle = corner * Math.PI / 2;
36
+ const cx = (corner === 0 || corner === 3 ? 1 : -1) * (hx - radius);
37
+ const cz = (corner < 2 ? 1 : -1) * (hz - radius);
38
+ for (let step = 0; step <= 4; step++) {
39
+ const a = angle + step * Math.PI / 8;
40
+ points.push([cx + Math.cos(a) * radius, y, cz + Math.sin(a) * radius]);
41
+ }
42
+ }
43
+ return points;
44
+ };
45
+ const rings = [ring(-height / 2, bevel), ring(-height / 2 + bevel, 0), ring(height / 2 - bevel, 0), ring(height / 2, bevel)];
46
+ const n = rings[0].length;
47
+ const vertex = (p, uv) => { const id = positions.length / 3; positions.push(...p); uvs.push(...uv); return id; };
48
+ // 环沿 XZ 逆时针;顶部反序得到 +Y 法线,侧面单独顶点保持柔和斜边与平面分界。
49
+ for (const [level, top] of [[0, false], [3, true]]) {
50
+ const center = vertex([0, rings[level][0][1], 0], [0.5, 0.5]);
51
+ const ids = rings[level].map(p => vertex(p, [p[0] / width + 0.5, p[2] / depth + 0.5]));
52
+ for (let i = 0; i < n; i++) indices.push(center, ids[top ? (i + 1) % n : i], ids[top ? i : (i + 1) % n]);
53
+ }
54
+ for (let level = 0; level < 3; level++) for (let i = 0; i < n; i++) {
55
+ const j = (i + 1) % n;
56
+ const ids = [rings[level][i], rings[level][j], rings[level + 1][j], rings[level + 1][i]]
57
+ .map((p, k) => vertex(p, [((k === 1 || k === 2) ? i + 1 : i) / n * 3, p[1] / height]));
58
+ indices.push(ids[0], ids[2], ids[1], ids[0], ids[3], ids[2]);
59
+ }
60
+ const geometry = new THREE.BufferGeometry();
61
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
62
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
63
+ geometry.setIndex(indices);
64
+ geometry.computeVertexNormals();
65
+ return geometry;
66
+ }
67
+
68
+ export function createFairyDesk() {
69
+ const group = new THREE.Group();
70
+ group.name = 'fairy-desk-01';
71
+ const owned = new Set();
72
+ const keep = resource => { owned.add(resource); return resource; };
73
+ let disposed = false;
74
+ const dispose = () => {
75
+ if (disposed) return;
76
+ disposed = true;
77
+ group.removeFromParent();
78
+ for (const resource of owned) resource.dispose();
79
+ owned.clear(); group.clear();
80
+ };
81
+ try {
82
+ const map = keep(woodTexture());
83
+ const wood = keep(new THREE.MeshStandardMaterial({ map, bumpMap: map, bumpScale: 0.002, roughness: 0.68, metalness: 0.01 }));
84
+ const deep = keep(new THREE.MeshStandardMaterial({ map, color: '#d5bd99', roughness: 0.74 }));
85
+ const seam = keep(new THREE.MeshStandardMaterial({ color: '#62492e', roughness: 0.9 }));
86
+ const brass = keep(new THREE.MeshStandardMaterial({ color: '#b99955', metalness: 0.65, roughness: 0.4 }));
87
+ const leaf = keep(new THREE.MeshStandardMaterial({ color: '#b69b60', roughness: 0.8 }));
88
+ const add = (geometry, material, x, y, z, name) => {
89
+ const mesh = new THREE.Mesh(keep(geometry), material);
90
+ mesh.name = name; mesh.position.set(x, y, z); mesh.castShadow = mesh.receiveShadow = true;
91
+ group.add(mesh); return mesh;
92
+ };
93
+ const box = (w, h, d, mat, x, y, z, name, grain = 'x') => {
94
+ const geometry = new THREE.BoxGeometry(w, h, d);
95
+ const p = geometry.attributes.position, uv = geometry.attributes.uv;
96
+ for (let i = 0; i < p.count; i++) uv.setXY(i, grain === 'y' ? p.getY(i) / h + 0.5 : grain === 'z' ? p.getZ(i) / d + 0.5 : p.getX(i) / w + 0.5,
97
+ grain === 'y' ? (p.getX(i) / w + p.getZ(i) / d) / 2 + 0.5 : p.getY(i) / h + 0.5);
98
+ return add(geometry, mat, x, y, z, name);
99
+ };
100
+ add(roundedBoard(1.6, 0.06, 0.8, 0.008), wood, 0, 0.72, 0, '连续蜂蜜木桌面');
101
+ // 四腿从地面到桌板底部;脚端收细,纹理沿腿长,桌下保持空隙。
102
+ for (const x of [-0.675, 0.675]) for (const z of [-0.28, 0.28]) {
103
+ const leg = box(0.092, 0.695, 0.092, wood, x, 0.3475, z, '收脚木腿', 'y');
104
+ const p = leg.geometry.attributes.position;
105
+ for (let i = 0; i < p.count; i++) if (p.getY(i) < 0) p.setXYZ(i, p.getX(i) * 0.60, p.getY(i), p.getZ(i) * 0.60);
106
+ p.needsUpdate = true; leg.geometry.computeVertexNormals();
107
+ }
108
+ box(1.28, 0.10, 0.038, deep, 0, 0.64, -0.28, '后桌裙');
109
+ for (const x of [-0.675, 0.675]) {
110
+ box(0.038, 0.10, 0.56, deep, x, 0.64, 0, '侧桌裙', 'z');
111
+ box(0.044, 0.045, 0.52, deep, x, 0.24, 0, '侧横枨', 'z');
112
+ }
113
+ for (const x of [-0.465, 0.465]) box(0.39, 0.10, 0.038, deep, x, 0.64, 0.28, '前桌裙');
114
+ box(0.52, 0.088, 0.026, seam, 0, 0.633, 0.301, '抽屉细缝');
115
+ box(0.496, 0.070, 0.029, wood, 0, 0.633, 0.320, '静态浅抽屉面板');
116
+ // 静态抽屉具有实际盒体:底板与侧/后板相接,低视角不会穿透到桌板。
117
+ box(0.496, 0.012, 0.554, wood, 0, 0.604, 0.037, '抽屉底板');
118
+ for (const x of [-0.240, 0.240]) box(0.016, 0.070, 0.540, deep, x, 0.643, 0.038, '抽屉侧板', 'z');
119
+ box(0.496, 0.070, 0.016, deep, 0, 0.643, -0.232, '抽屉后板');
120
+ for (const x of [-0.055, 0.055]) {
121
+ const post = add(new THREE.CylinderGeometry(0.009, 0.009, 0.034, 10), brass, x, 0.633, 0.35, '黄铜拉手脚');
122
+ post.rotation.x = Math.PI / 2;
123
+ }
124
+ const handle = add(new THREE.CylinderGeometry(0.009, 0.009, 0.128, 12), brass, 0, 0.633, 0.367, '黄铜拉手');
125
+ handle.rotation.z = Math.PI / 2;
126
+ for (const x of [-0.46, 0.46]) {
127
+ const relief = add(new THREE.SphereGeometry(1, 12, 6), leaf, x, 0.647, 0.302, '叶形浅浮雕');
128
+ relief.scale.set(0.045, 0.016, 0.004); relief.rotation.z = x > 0 ? -0.25 : 0.25;
129
+ }
130
+ return { group, dispose };
131
+ } catch (error) { dispose(); throw error; }
132
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "assetVersion": 1,
4
+ "status": "internal",
5
+ "assetId": "fairy-notice-board-01",
6
+ "name": "林间蜂蜜木告示牌",
7
+ "category": "notice-board",
8
+ "description": "童话风蜂蜜木落地告示牌:两根立柱下接向前后延伸的短脚,柱顶缀黄铜小球;蜂蜜色木框上沿微拱、边缘倒角收边,框内嵌暖米色软木正面板与木质背板,正面主要区域留白,框角与拱肩缀少量黄铜铆钉。宽 1 米、高 1.7 米、深 0.44 米,本地 +Z 为软木正面板方向。落地摆放,为项目介绍、流程说明等业务资料提供直观的资料集合入口:登记后点击可经现有 open-document-collection 能力在阅读面板查看绑定文档集合;板面只表现软木材质,不直接把业务文档正文、流程图或截图渲染到三维板面上。",
9
+ "styleIds": ["fairy"],
10
+ "modelRef": "fairy-notice-board-01",
11
+ "previewRef": "assets/fairy-notice-board-01/preview.webp",
12
+ "placeholder": false,
13
+ "bounds": { "minX": -0.5, "maxX": 0.5, "minY": 0, "maxY": 1.7, "minZ": -0.22, "maxZ": 0.22 },
14
+ "placement": { "allowedKinds": ["floor"], "allowedYawDeg": [0, 90, 180, 270] },
15
+ "clearanceBoxes": [
16
+ { "name": "告示牌前阅读区", "bounds": { "minX": -0.55, "maxX": 0.55, "minY": 0, "maxY": 1.9, "minZ": 0.22, "maxZ": 1.25 } }
17
+ ],
18
+ "approachPoint": { "x": 0, "z": 0.9 },
19
+ "surfaces": [],
20
+ "capabilities": ["open-document-collection"],
21
+ "recommendations": { "note": "推荐项目入口介绍区、流程说明区与童话房,与林间蜂蜜木书架、书桌、台灯同系列搭配;也允许混搭写实房间。风格只是软推荐,不构成摆放准入。" },
22
+ "source": { "origin": "自制", "license": "MIT", "note": "本包 model.js 自制 Three.js 程序化模型与固定种子 DataTexture 木纹、软木纹,无外部模型或贴图。" }
23
+ }
@@ -0,0 +1,125 @@
1
+ // 林间蜂蜜木告示牌:自制程序化候选模型,不依赖 DOM、网络或实验文件。
2
+ // 米制、+Y 向上、+Z 正面(软木面板朝向);底面中心原点。冻结包络 X±0.5、Y[0,1.7]、Z±0.22。
3
+ // 设计常数(检查按此独立手算):木框外半宽 0.46、底 0.80、拱脚 1.30、拱顶 1.60、
4
+ // 圆角 0.03;框带宽 0.055;框体挤出 Z[-0.045,0.045]+倒角 0.008;软木板 Z[-0.005,0.035];
5
+ // 背板 Z[-0.045,-0.005];立柱 x=±0.40(截面 0.09×0.074,Y[0.04,1.56],前面 z=0.037
6
+ // 略凸出于软木板前面 0.035,避免共面闪纹);短脚 X[±0.35,±0.45]、
7
+ // Y[0,0.045]、Z±0.19;横撑 Y[0.48,0.52];柱顶黄铜球 r0.032@Y1.578;铆钉 r0.014@Z0.053。
8
+ import * as THREE from 'three';
9
+
10
+ function woodTexture() {
11
+ const size = 256;
12
+ const data = new Uint8Array(size * size * 4);
13
+ let seed = 20260932;
14
+ const random = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed / 4294967296; };
15
+ for (let v = 0; v < size; v++) for (let u = 0; u < size; u++) {
16
+ // 低对比细木纹沿 U 延伸,与书桌、书架同系列;不做节疤。
17
+ const wave = v + 1.4 * Math.sin(u * 0.031) + 0.5 * Math.sin(u * 0.083);
18
+ const tone = 208 + 4 * Math.sin(wave * 0.55) + 2 * Math.sin(wave * 1.9) + (random() - 0.5) * 2.5;
19
+ const i = (v * size + u) * 4;
20
+ data[i] = tone; data[i + 1] = tone - 24; data[i + 2] = tone - 58; data[i + 3] = 255;
21
+ }
22
+ const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
23
+ texture.colorSpace = THREE.SRGBColorSpace;
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
+ function corkTexture() {
33
+ const size = 256;
34
+ const data = new Uint8Array(size * size * 4);
35
+ let seed = 20260933;
36
+ const random = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed / 4294967296; };
37
+ for (let v = 0; v < size; v++) for (let u = 0; u < size; u++) {
38
+ // 暖米色软木:细颗粒噪点叠加少量深色碎屑,不出现条纹或图案。
39
+ const grain = (random() - 0.5) * 9 + 2.2 * Math.sin(u * 0.71) * Math.sin(v * 0.63);
40
+ const speck = random() < 0.012 ? -26 : 0;
41
+ const tone = 226 + grain + speck;
42
+ const i = (v * size + u) * 4;
43
+ data[i] = tone + 8; data[i + 1] = tone - 6; data[i + 2] = tone - 34; data[i + 3] = 255;
44
+ }
45
+ const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
46
+ texture.colorSpace = THREE.SRGBColorSpace;
47
+ texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
48
+ texture.magFilter = THREE.LinearFilter;
49
+ texture.minFilter = THREE.LinearMipmapLinearFilter;
50
+ texture.generateMipmaps = true;
51
+ texture.needsUpdate = true;
52
+ return texture;
53
+ }
54
+
55
+ // 拱顶外轮廓:底边圆角 r,两侧直边到拱脚,两条二次曲线汇于拱顶;按 inset 收缩得框内孔。
56
+ function boardOutline(halfWidth, bottom, spring, peak, radius) {
57
+ const path = new THREE.Path();
58
+ path.moveTo(-halfWidth + radius, bottom);
59
+ path.lineTo(halfWidth - radius, bottom);
60
+ path.quadraticCurveTo(halfWidth, bottom, halfWidth, bottom + radius);
61
+ path.lineTo(halfWidth, spring);
62
+ path.quadraticCurveTo(halfWidth, peak, 0, peak);
63
+ path.quadraticCurveTo(-halfWidth, peak, -halfWidth, spring);
64
+ path.lineTo(-halfWidth, bottom + radius);
65
+ path.quadraticCurveTo(-halfWidth, bottom, -halfWidth + radius, bottom);
66
+ path.closePath();
67
+ return path;
68
+ }
69
+
70
+ export function createFairyNoticeBoard() {
71
+ const group = new THREE.Group();
72
+ group.name = 'fairy-notice-board-01';
73
+ const owned = new Set();
74
+ const keep = (resource) => { owned.add(resource); return resource; };
75
+ let disposed = false;
76
+ const dispose = () => {
77
+ if (disposed) return;
78
+ disposed = true;
79
+ group.removeFromParent();
80
+ for (const resource of owned) resource.dispose();
81
+ owned.clear(); group.clear();
82
+ };
83
+ try {
84
+ const wood = keep(woodTexture());
85
+ const cork = keep(corkTexture());
86
+ const honey = keep(new THREE.MeshStandardMaterial({ map: wood, bumpMap: wood, bumpScale: 0.0015, roughness: 0.66, metalness: 0.01 }));
87
+ const backWood = keep(new THREE.MeshStandardMaterial({ color: '#dcc39a', map: wood, bumpMap: wood, bumpScale: 0.0012, roughness: 0.78 }));
88
+ const corkMat = keep(new THREE.MeshStandardMaterial({ map: cork, bumpMap: cork, bumpScale: 0.004, roughness: 0.95 }));
89
+ const brass = keep(new THREE.MeshStandardMaterial({ color: '#c19a5e', metalness: 0.7, roughness: 0.34 }));
90
+ const add = (geometry, material, x, y, z, name) => {
91
+ const mesh = new THREE.Mesh(keep(geometry), material);
92
+ mesh.name = name; mesh.position.set(x, y, z); mesh.castShadow = mesh.receiveShadow = true;
93
+ group.add(mesh); return mesh;
94
+ };
95
+
96
+ // 面板三件:木框(真实倒角挤出,孔内嵌板)、软木正面板、木质背板;三者都有实际厚度。
97
+ const OUTER = { halfWidth: 0.46, bottom: 0.80, spring: 1.30, peak: 1.60, radius: 0.03 };
98
+ const inset = 0.055;
99
+ const frameShape = new THREE.Shape(boardOutline(OUTER.halfWidth, OUTER.bottom, OUTER.spring, OUTER.peak, OUTER.radius).getPoints(16));
100
+ frameShape.holes.push(boardOutline(
101
+ OUTER.halfWidth - inset, OUTER.bottom + inset, OUTER.spring - inset, OUTER.peak - inset, OUTER.radius,
102
+ ));
103
+ add(new THREE.ExtrudeGeometry(frameShape, { depth: 0.09, bevelEnabled: true, bevelThickness: 0.008, bevelSize: 0.008, bevelSegments: 2, curveSegments: 16 }), honey, 0, 0, -0.045, '木框');
104
+ const panelShape = new THREE.Shape(boardOutline(
105
+ OUTER.halfWidth - inset, OUTER.bottom + inset, OUTER.spring - inset, OUTER.peak - inset, OUTER.radius,
106
+ ).getPoints(16));
107
+ add(new THREE.ExtrudeGeometry(panelShape, { depth: 0.04, bevelEnabled: false, curveSegments: 16 }), corkMat, 0, 0, -0.005, '软木正面板');
108
+ add(new THREE.ExtrudeGeometry(panelShape, { depth: 0.04, bevelEnabled: false, curveSegments: 16 }), backWood, 0, 0, -0.045, '木质背板');
109
+
110
+ // 两根立柱与向前后延伸的短脚:脚底到 Y=0,柱、脚、面板真实嵌接,不悬空。
111
+ for (const side of [-1, 1]) {
112
+ const label = side < 0 ? '左' : '右';
113
+ add(new THREE.BoxGeometry(0.09, 1.52, 0.074), honey, side * 0.40, 0.80, 0, `${label}立柱`);
114
+ add(new THREE.BoxGeometry(0.10, 0.045, 0.38), honey, side * 0.40, 0.0225, 0, `${label}短脚`);
115
+ add(new THREE.SphereGeometry(0.032, 14, 10), brass, side * 0.40, 1.578, 0, '柱顶黄铜球');
116
+ }
117
+ add(new THREE.BoxGeometry(0.72, 0.04, 0.06), honey, 0, 0.50, 0, '背侧横撑');
118
+
119
+ // 少量黄铜铆钉:底角、拱肩与拱冠各就位在框带中线上,正面主要区域保持留白。
120
+ for (const [x, y] of [[-0.4325, 0.8275], [0.4325, 0.8275], [-0.4325, 1.3275], [0.4325, 1.3275], [0, 1.5725]]) {
121
+ add(new THREE.SphereGeometry(0.014, 10, 6), brass, x, y, 0.053, '黄铜铆钉');
122
+ }
123
+ return { group, dispose };
124
+ } catch (error) { dispose(); throw error; }
125
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "assetVersion": 1,
4
+ "status": "internal",
5
+ "assetId": "fairy-potted-plant-01",
6
+ "name": "林间小叶盆栽",
7
+ "category": "plant",
8
+ "description": "童话风小叶盆栽:暖陶小盆有厚边盆口与真实内外壁,盆内可见土面;短枝从土中长出并向两侧分枝,托起八片带薄厚度与弧度的小叶,绿色分三档、轮廓紧凑,叶柄接枝、枝接土。宽深各 0.22 米、高 0.36 米,本地 +Z 正面为叶簇朝向之一。静态陈列摆件,无生长动画、光点或实际灯光,无任何资料动作;仅可依附接受 plant 类别的承载面(如陶瓷展示台台面),当前书桌桌面只接受台灯类别,不能因为“桌面摆件”的称呼承诺能放上书桌。",
9
+ "styleIds": ["fairy"],
10
+ "modelRef": "fairy-potted-plant-01",
11
+ "previewRef": "assets/fairy-potted-plant-01/preview.webp",
12
+ "placeholder": false,
13
+ "bounds": { "minX": -0.11, "maxX": 0.11, "minY": 0, "maxY": 0.36, "minZ": -0.11, "maxZ": 0.11 },
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,128 @@
1
+ // 林间小叶盆栽:自制程序化候选模型,不依赖 DOM、网络或实验文件。
2
+ // 米制、+Y 向上、+Z 正面;底面中心原点。冻结包络 X、Z 各 ±0.11、Y[0,0.36]。
3
+ // 设计常数(检查按此独立手算):盆外壳 lathe 外底近轴 (0.001,0)→外底缘 (0.058,0)→
4
+ // 底缘圆角 (0.064,0.008)→盆壁 (0.072,0.080)→口外缘 (0.076,0.092)→厚边顶 (0.070,0.098);
5
+ // 盆口沿面环外 0.070/内 0.0665 @Y0.098;盆内壁 lathe 自口内缘 (0.0665,0.098) 下行至
6
+ // 内底缘 (0.052,0.034)→内底近轴 (0.001,0.032);土面圆盘 r0.058 @Y0.078;
7
+ // 主枝自土内 (0.008,0.070,0.004) 至 (0.020,0.178,0.010)(r0.005),
8
+ // 两分枝自 (0.015,0.150,0.008) 分别至 (−0.030,0.210,−0.020) 与 (0.032,0.222,0.018)(r0.0035);
9
+ // 八片薄弧叶(球体压扁 0.036×0.005×0.015)叶柄端接枝:叶心自所接枝端沿朝向偏移 0.024,
10
+ // 小于长半轴 0.036,枝端点始终嵌在叶体内;随机翻转只绕长轴(rotateX),实测叶簇最高约 Y0.27。
11
+ // 无动画、光点或实际灯光;每叶有薄厚度,枝入土、叶接枝,不出现漂浮叶。
12
+ import * as THREE from 'three';
13
+
14
+ // 圆盘(土面):法线朝上的真实面。
15
+ // 椭圆扁平环(盆口沿面):厚边顶面的真实窄环。
16
+ function disc(y, r, segments) {
17
+ const positions = [], indices = [];
18
+ const center = positions.length / 3;
19
+ positions.push(0, y, 0);
20
+ const ring = [];
21
+ for (let i = 0; i < segments; i++) {
22
+ const t = i / segments * Math.PI * 2;
23
+ const id = positions.length / 3;
24
+ positions.push(r * Math.cos(t), y, r * Math.sin(t));
25
+ ring.push(id);
26
+ }
27
+ for (let i = 0; i < segments; i++) {
28
+ const j = (i + 1) % segments;
29
+ indices.push(center, ring[j], ring[i]);
30
+ }
31
+ const geometry = new THREE.BufferGeometry();
32
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
33
+ geometry.setIndex(indices);
34
+ geometry.computeVertexNormals();
35
+ return geometry;
36
+ }
37
+
38
+ function annulus(y, rOuter, rInner, segments) {
39
+ const positions = [], indices = [];
40
+ const ringOf = (r) => { const ids = []; for (let i = 0; i < segments; i++) {
41
+ const t = i / segments * Math.PI * 2;
42
+ const id = positions.length / 3;
43
+ positions.push(r * Math.cos(t), y, r * Math.sin(t));
44
+ ids.push(id);
45
+ } return ids; };
46
+ const outer = ringOf(rOuter), inner = ringOf(rInner);
47
+ for (let i = 0; i < segments; i++) {
48
+ const j = (i + 1) % segments;
49
+ indices.push(outer[i], inner[i], outer[j], inner[i], inner[j], outer[j]);
50
+ }
51
+ const geometry = new THREE.BufferGeometry();
52
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
53
+ geometry.setIndex(indices);
54
+ geometry.computeVertexNormals();
55
+ return geometry;
56
+ }
57
+
58
+ export function createFairyPottedPlant() {
59
+ const group = new THREE.Group();
60
+ group.name = 'fairy-potted-plant-01';
61
+ const owned = new Set();
62
+ const keep = (resource) => { owned.add(resource); return resource; };
63
+ let disposed = false;
64
+ const dispose = () => {
65
+ if (disposed) return;
66
+ disposed = true;
67
+ group.removeFromParent();
68
+ for (const resource of owned) resource.dispose();
69
+ owned.clear(); group.clear();
70
+ };
71
+ try {
72
+ const terra = keep(new THREE.MeshStandardMaterial({ color: '#b06f4e', roughness: 0.85 }));
73
+ const soil = keep(new THREE.MeshStandardMaterial({ color: '#4a3626', roughness: 0.97 }));
74
+ const stem = keep(new THREE.MeshStandardMaterial({ color: '#5a4a33', roughness: 0.8 }));
75
+ const greenDeep = keep(new THREE.MeshStandardMaterial({ color: '#3c5232', roughness: 0.7 }));
76
+ const greenMid = keep(new THREE.MeshStandardMaterial({ color: '#4f6b3c', roughness: 0.68 }));
77
+ const greenLight = keep(new THREE.MeshStandardMaterial({ color: '#68884a', roughness: 0.65 }));
78
+ const add = (geometry, material, name) => {
79
+ const mesh = new THREE.Mesh(keep(geometry), material);
80
+ mesh.name = name; mesh.castShadow = mesh.receiveShadow = true;
81
+ group.add(mesh); return mesh;
82
+ };
83
+ // 暖陶小盆:外壳含外底与厚边口沿(法线朝外),内壁与内底朝腔内,盆口有真实厚边。
84
+ add(new THREE.LatheGeometry([
85
+ new THREE.Vector2(0.001, 0), new THREE.Vector2(0.058, 0), new THREE.Vector2(0.064, 0.008),
86
+ new THREE.Vector2(0.072, 0.080), new THREE.Vector2(0.076, 0.092), new THREE.Vector2(0.070, 0.098),
87
+ ], 32), terra, '盆外壳');
88
+ add(annulus(0.098, 0.070, 0.0665, 32), terra, '盆口沿面');
89
+ add(new THREE.LatheGeometry([
90
+ new THREE.Vector2(0.0665, 0.098), new THREE.Vector2(0.060, 0.040), new THREE.Vector2(0.052, 0.034),
91
+ new THREE.Vector2(0.001, 0.032),
92
+ ], 32), soil, '盆内壁');
93
+ add(disc(0.078, 0.058, 32), soil, '土面');
94
+ // 枝:两点定长的圆柱(同台灯支架技巧),自土中向上,分枝外倾。
95
+ const grow = (from, to, radius, name) => {
96
+ const fromV = new THREE.Vector3(...from), toV = new THREE.Vector3(...to);
97
+ const branch = add(new THREE.CylinderGeometry(radius, radius * 1.15, fromV.distanceTo(toV), 8), stem, name);
98
+ branch.position.copy(fromV).add(toV).multiplyScalar(0.5);
99
+ branch.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), toV.clone().sub(fromV).normalize());
100
+ return toV;
101
+ };
102
+ const soilTop = new THREE.Vector3(0.008, 0.070, 0.004);
103
+ const mainTip = new THREE.Vector3(0.020, 0.178, 0.010);
104
+ const forkBase = new THREE.Vector3(0.015, 0.150, 0.008);
105
+ grow([soilTop.x, soilTop.y, soilTop.z], [mainTip.x, mainTip.y, mainTip.z], 0.005, '主枝');
106
+ const tipA = grow([forkBase.x, forkBase.y, forkBase.z], [-0.030, 0.210, -0.020], 0.0035, '左分枝');
107
+ const tipB = grow([forkBase.x, forkBase.y, forkBase.z], [0.032, 0.222, 0.018], 0.0035, '右分枝');
108
+ // 八片薄弧叶:叶心自所接枝端沿 outward 偏移 0.024(<长半轴 0.036),朝向固定种子伪随机,三档绿色。
109
+ let seed = 20260961;
110
+ const random = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed / 4294967296; };
111
+ const anchors = [mainTip, tipA, tipB, tipA, tipB, mainTip, tipA, tipB];
112
+ const greens = [greenDeep, greenMid, greenLight];
113
+ for (let i = 0; i < anchors.length; i++) {
114
+ const anchor = anchors[i];
115
+ const yaw = random() * Math.PI * 2;
116
+ const tilt = 0.35 + random() * 0.5;
117
+ const outward = new THREE.Vector3(Math.cos(yaw) * Math.cos(tilt), Math.sin(tilt), Math.sin(yaw) * Math.cos(tilt)).normalize();
118
+ const leaf = add(new THREE.SphereGeometry(1, 10, 8), greens[i % 3], `薄叶${i + 1}`);
119
+ leaf.scale.set(0.036, 0.005, 0.015);
120
+ leaf.position.copy(anchor).add(outward.clone().multiplyScalar(0.024));
121
+ leaf.quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), outward);
122
+ // 随机翻转只能绕自身长轴(rotateX):长轴保持穿过所接枝端,叶柄端始终嵌入枝内;
123
+ // 绕局部 Y 随机转会把短轴转向枝端(偏移 0.024 超过短半轴 0.015),叶与枝分离成漂浮叶。
124
+ leaf.rotateX(random() * Math.PI);
125
+ }
126
+ return { group, dispose };
127
+ } catch (error) { dispose(); throw error; }
128
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "assetVersion": 1,
4
+ "status": "internal",
5
+ "assetId": "fairy-reading-chair-01",
6
+ "name": "林间软垫阅读椅",
7
+ "category": "chair",
8
+ "description": "紧凑蜂蜜木扶手椅,四脚、座框和背支承连接厚奶油坐垫与靠垫,两侧圆润扶手。宽0.72、深0.60、高不超过0.95米;+Z为座前。仅地面静态家具,不支持坐下,不提供承载面。",
9
+ "styleIds": [
10
+ "fairy"
11
+ ],
12
+ "modelRef": "fairy-reading-chair-01",
13
+ "previewRef": "assets/fairy-reading-chair-01/preview.webp",
14
+ "placeholder": false,
15
+ "bounds": {
16
+ "minX": -0.36,
17
+ "maxX": 0.36,
18
+ "minY": 0,
19
+ "maxY": 0.95,
20
+ "minZ": -0.3,
21
+ "maxZ": 0.3
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.45,
39
+ "maxX": 0.45,
40
+ "minY": 0,
41
+ "maxY": 1.9,
42
+ "minZ": 0.3,
43
+ "maxZ": 1.3
44
+ }
45
+ }
46
+ ],
47
+ "approachPoint": {
48
+ "x": 0,
49
+ "z": 0.9
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,66 @@
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 createFairyReadingChair() {
33
+ const group = new THREE.Group(); group.name = 'fairy-reading-chair-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.28, 0.28]) for (const z of [-0.21, 0.21])
51
+ soft(0.065, 0.40, 0.065, wood, x, 0.20, z, '落地椅脚');
52
+ box(0.62, 0.065, 0.47, wood, 0, 0.39, 0.015, '座承托板');
53
+ for (const x of [-0.29, 0.29]) soft(0.065, 0.105, 0.52, wood, x, 0.38, 0, '侧座框');
54
+ for (const z of [-0.23, 0.23]) soft(0.62, 0.105, 0.055, wood, 0, 0.38, z, '端座框');
55
+ for (const x of [-0.275, 0.275]) {
56
+ soft(0.055, 0.57, 0.065, wood, x, 0.635, -0.235, '靠背支承');
57
+ soft(0.055, 0.24, 0.055, wood, x, 0.505, 0.21, '前扶手支承');
58
+ soft(0.09, 0.065, 0.52, wood, x, 0.635, 0, '圆润扶手');
59
+ }
60
+ soft(0.56, 0.12, 0.46, cream, 0, 0.48, 0.015, '厚坐垫', 0.035);
61
+ soft(0.56, 0.47, 0.06, wood, 0, 0.70, -0.25, '实木背板', 0.04).rotation.x = -0.05;
62
+ soft(0.53, 0.43, 0.095, cream, 0, 0.705, -0.18, '厚靠垫', 0.05).rotation.x = -0.05;
63
+ soft(0.53, 0.018, 0.035, green, 0, 0.543, 0.205, '坐垫青绿缝边', 0.005);
64
+ return { group, dispose };
65
+ } catch (error) { dispose(); throw error; }
66
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "assetVersion": 1,
4
+ "status": "internal",
5
+ "assetId": "fairy-table-lamp-01",
6
+ "name": "林间暖光台灯",
7
+ "category": "lamp",
8
+ "description": "童话风暖光小台灯:低矮圆润蜂蜜木底座带收边,黄铜灯杆经套环连接底座与灯头,暖米色微锥布灯罩有外壁、内衬与上下薄包边,罩内小灯泡呈固定暖亮外观;宽 0.24 米、高 0.42 米、深 0.30 米,本地 +Z 正面为底座小黄铜叶纹嵌片。仅可依附接受 lamp 类别的承载面(如书桌桌面),无独立地面站位;灯泡为固定暖亮材质,无实际照明、开关灯或昼夜动作。",
9
+ "styleIds": ["fairy"],
10
+ "modelRef": "fairy-table-lamp-01",
11
+ "previewRef": "assets/fairy-table-lamp-01/preview.webp",
12
+ "placeholder": false,
13
+ "bounds": { "minX": -0.12, "maxX": 0.12, "minY": 0, "maxY": 0.42, "minZ": -0.15, "maxZ": 0.15 },
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
+ }