prts-terrarchive 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GAME_ASSETS.md +26 -0
- package/LICENSE +21 -0
- package/README.en.md +274 -0
- package/README.md +423 -0
- package/THIRD_PARTY_NOTICES.md +61 -0
- package/bin/bake-rhine-details.mjs +34 -0
- package/bin/install.js +273 -0
- package/bin/pack-map-assets.mjs +110 -0
- package/bin/preview-rhine.mjs +71 -0
- package/bin/rhine-detail-baker.ts +84 -0
- package/contracts/cloud_tools_v1.schema.json +243 -0
- package/contracts/corpus_i18n_v1.schema.json +179 -0
- package/contracts/corpus_tools_v1.schema.json +1801 -0
- package/cordis.patch.yml +32 -0
- package/docs/endfield-official-i18n.md +103 -0
- package/lib/client.js +3912 -0
- package/lib/endfield-map/map.js.br +0 -0
- package/lib/endfield-map/map.js.gz +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.br +0 -0
- package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.gz +0 -0
- package/lib/endfield-map/resources/map-01616f534aaaeca1.png +0 -0
- package/lib/endfield-map/resources/map-1562e0dff4be3d23.png +0 -0
- package/lib/endfield-map/resources/map-160fe040f080f6a3.png +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.br +0 -0
- package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.gz +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.br +0 -0
- package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.gz +0 -0
- package/lib/endfield-map/resources/map-1d30baaaef3e29cc.png +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.br +0 -0
- package/lib/endfield-map/resources/map-21a61386af79cd12.json.gz +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.br +0 -0
- package/lib/endfield-map/resources/map-24b920b8dab49b44.json.gz +0 -0
- package/lib/endfield-map/resources/map-26caf8d71247abb7.png +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.br +0 -0
- package/lib/endfield-map/resources/map-2729942c89545e73.json.gz +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.br +0 -0
- package/lib/endfield-map/resources/map-29063a9802f67014.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.br +0 -0
- package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.gz +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.br +0 -0
- package/lib/endfield-map/resources/map-2f98a873da81425e.json.gz +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.br +0 -0
- package/lib/endfield-map/resources/map-3ab978ae44190f80.json.gz +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.br +0 -0
- package/lib/endfield-map/resources/map-3b62c80d14208902.json.gz +0 -0
- package/lib/endfield-map/resources/map-3d486e887ae121ac.png +0 -0
- package/lib/endfield-map/resources/map-418dc4865608fce5.png +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.br +0 -0
- package/lib/endfield-map/resources/map-4366b84607335f76.json.gz +0 -0
- package/lib/endfield-map/resources/map-4371259d3ab7aac7.png +0 -0
- package/lib/endfield-map/resources/map-48dcc0fd55e13f7f.png +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.br +0 -0
- package/lib/endfield-map/resources/map-4aabda0e41197570.json.gz +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.br +0 -0
- package/lib/endfield-map/resources/map-505f5687b287043c.json.gz +0 -0
- package/lib/endfield-map/resources/map-5177e9d1d46f47e2.png +0 -0
- package/lib/endfield-map/resources/map-51baeb59b7f8c28d.png +0 -0
- package/lib/endfield-map/resources/map-549561b46e621874.png +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.br +0 -0
- package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.gz +0 -0
- package/lib/endfield-map/resources/map-5a6963909423b674.png +0 -0
- package/lib/endfield-map/resources/map-5a9c533c0552d7b7.png +0 -0
- package/lib/endfield-map/resources/map-5d20ae9ffa9a02b0.png +0 -0
- package/lib/endfield-map/resources/map-626c8847393f60bf.png +0 -0
- package/lib/endfield-map/resources/map-63d43cbec3bed3b8.png +0 -0
- package/lib/endfield-map/resources/map-661b6ec56e722574.png +0 -0
- package/lib/endfield-map/resources/map-6ddd384eca190828.png +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.br +0 -0
- package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.gz +0 -0
- package/lib/endfield-map/resources/map-738ebaa002c33cfc.png +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.br +0 -0
- package/lib/endfield-map/resources/map-758c13be6085ec07.json.gz +0 -0
- package/lib/endfield-map/resources/map-78929f26d6844509.png +0 -0
- package/lib/endfield-map/resources/map-7f09c142950a0f74.png +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.br +0 -0
- package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.gz +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.br +0 -0
- package/lib/endfield-map/resources/map-881343926a568f27.json.gz +0 -0
- package/lib/endfield-map/resources/map-8d1ade3f01d952b0.png +0 -0
- package/lib/endfield-map/resources/map-8fef3495bb7ed60f.png +0 -0
- package/lib/endfield-map/resources/map-907a7ecf8a0ce352.png +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.br +0 -0
- package/lib/endfield-map/resources/map-9113d5951c4db903.json.gz +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.br +0 -0
- package/lib/endfield-map/resources/map-94ad6634436224e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-99722e61da5f3e53.png +0 -0
- package/lib/endfield-map/resources/map-9be9596cc059abe3.png +0 -0
- package/lib/endfield-map/resources/map-a4db9a4763e47fbf.png +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.br +0 -0
- package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.gz +0 -0
- package/lib/endfield-map/resources/map-ab35d06875ee418f.png +0 -0
- package/lib/endfield-map/resources/map-ac51bb3e7623cd8b.png +0 -0
- package/lib/endfield-map/resources/map-ac830a66296bc83a.png +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.br +0 -0
- package/lib/endfield-map/resources/map-b3fd04209718e55e.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8164f1af7c5d895.png +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.br +0 -0
- package/lib/endfield-map/resources/map-b8cde93b965384d8.json.gz +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.br +0 -0
- package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.gz +0 -0
- package/lib/endfield-map/resources/map-bfc58277d5ba649d.png +0 -0
- package/lib/endfield-map/resources/map-c013f82048c6891a.png +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.br +0 -0
- package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.gz +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.br +0 -0
- package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.gz +0 -0
- package/lib/endfield-map/resources/map-c607f8b4c8ad8967.png +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.br +0 -0
- package/lib/endfield-map/resources/map-c6f20276b8781326.json.gz +0 -0
- package/lib/endfield-map/resources/map-d51adf5952af1e8e.png +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.br +0 -0
- package/lib/endfield-map/resources/map-d695eface22f8b74.json.gz +0 -0
- package/lib/endfield-map/resources/map-db11d394456a7465.png +0 -0
- package/lib/endfield-map/resources/map-dcc4d218581e3dc6.png +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.br +0 -0
- package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.gz +0 -0
- package/lib/endfield-map/resources/map-defe5db7dabf748a.png +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.br +0 -0
- package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.gz +0 -0
- package/lib/endfield-map/resources/map-e25ee9af6e493500.png +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.br +0 -0
- package/lib/endfield-map/resources/map-e3824082cd6f747d.json.gz +0 -0
- package/lib/endfield-map/resources/map-e694a91e630b1bd6.png +0 -0
- package/lib/endfield-map/resources/map-eaa93af79d12a87d.png +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.br +0 -0
- package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.gz +0 -0
- package/lib/endfield-map/resources/map-ede421a329dc78a4.png +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.br +0 -0
- package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.gz +0 -0
- package/lib/endfield-map/resources/map-fb5f4a59a181452c.png +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.br +0 -0
- package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.gz +0 -0
- package/lib/rhine/assets/archive-assembly.glb +0 -0
- package/lib/rhine/assets/archive-cassette.glb +0 -0
- package/lib/rhine/fonts/MiSans-Bold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Demibold.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Light.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-Regular.woff2 +0 -0
- package/lib/rhine/fonts/MiSans-license.pdf +0 -0
- package/lib/rhine/fonts/NOTICE.txt +6 -0
- package/lib/rhine/licenses/RhineLabUI-MIT.txt +21 -0
- package/lib/rhine/licenses/entities-BSD-2-Clause.txt +11 -0
- package/lib/rhine/licenses/linkify-it-MIT.txt +22 -0
- package/lib/rhine/licenses/markdown-it-MIT.txt +22 -0
- package/lib/rhine/licenses/mdurl-MIT.txt +45 -0
- package/lib/rhine/licenses/punycode.js-MIT.txt +20 -0
- package/lib/rhine/licenses/rolling-number-MIT.txt +21 -0
- package/lib/rhine/licenses/three-MIT.txt +21 -0
- package/lib/rhine/licenses/uc.micro-MIT.txt +20 -0
- package/lib/rhine/rhine.css +1 -0
- package/lib/rhine/rhine.js +5106 -0
- package/lib/skins/common.css +31 -0
- package/lib/skins/endfield-aic.css +141 -0
- package/lib/skins/prts-agent.css +136 -0
- package/lib/skins/rhine-lab.css +70 -0
- package/package.json +112 -0
- package/presets/definition.js +26 -0
- package/presets/prts/agent.cordis.yml +25 -0
- package/presets/prts/preset.yml +3 -0
- package/presets/register.js +86 -0
- package/resources/entity-routing.json +21 -0
- package/skills/prts-investigation/SKILL.md +121 -0
- package/skills/prts-retrieval/SKILL.md +35 -0
- package/skills/prts-retrieval/references/cloud-tools.md +13 -0
- package/skills/prts-retrieval/references/diagnostics.md +15 -0
- package/skills/prts-retrieval/references/local-tools.md +24 -0
- package/skills/prts-retrieval/references/module-arknights.md +23 -0
- package/skills/prts-retrieval/references/module-dual.md +15 -0
- package/skills/prts-retrieval/references/module-endfield.md +21 -0
- package/skills/prts-retrieval/references/retrieval-process.md +44 -0
- package/skills/prts-retrieval/references/retrieval-recipes-dual.md +34 -0
- package/skills/prts-retrieval/references/retrieval-recipes-endfield.md +37 -0
- package/skills/prts-retrieval/references/retrieval-recipes.md +39 -0
- package/skills/prts-retrieval/references/tools-runtime.md +48 -0
- package/skills/prts-retrieval/references/wiki-schema.md +38 -0
- package/src/archive-presentation.js +287 -0
- package/src/cloud-projection.js +124 -0
- package/src/cloud.js +498 -0
- package/src/entity-recognizer.js +311 -0
- package/src/entity-routing.js +82 -0
- package/src/evidence-state.js +332 -0
- package/src/i18n.js +365 -0
- package/src/index.js +1261 -0
- package/src/installer.js +1362 -0
- package/src/investigation-review.js +87 -0
- package/src/investigation-store.js +579 -0
- package/src/investigation-tools.js +113 -0
- package/src/localization-format.js +23 -0
- package/src/manual-search.js +103 -0
- package/src/read.js +1097 -0
- package/src/release-compatibility.js +73 -0
- package/src/release-watch.js +40 -0
- package/src/search-projection.js +139 -0
- package/src/search.js +1522 -0
- package/src/skill.js +98 -0
- package/src/source-map.js +348 -0
- package/src/state.js +314 -0
- package/src/store.js +1569 -0
- package/src/timeline.js +386 -0
- package/src/ui.js +1353 -0
- package/src/wiki.js +111 -0
- package/tsconfig.rhine.json +15 -0
- package/ui/rhine/archive-navigation-limit.ts +31 -0
- package/ui/rhine/archive-rack.ts +83 -0
- package/ui/rhine/assets/evidence-observatory.webp +0 -0
- package/ui/rhine/board-plane-transform.ts +24 -0
- package/ui/rhine/catalogue.ts +72 -0
- package/ui/rhine/deferred-preparation.ts +146 -0
- package/ui/rhine/evidence-board-controls.css +22 -0
- package/ui/rhine/evidence-board-history.ts +73 -0
- package/ui/rhine/evidence-board-model.ts +189 -0
- package/ui/rhine/evidence-board-panel.ts +762 -0
- package/ui/rhine/evidence-board-scene.ts +548 -0
- package/ui/rhine/evidence-board.css +191 -0
- package/ui/rhine/evidence-card-art.ts +470 -0
- package/ui/rhine/evidence-card-style.ts +17 -0
- package/ui/rhine/evidence-inbox-scene.ts +43 -0
- package/ui/rhine/evidence-inbox.css +29 -0
- package/ui/rhine/evidence-inbox.ts +104 -0
- package/ui/rhine/index.ts +12 -0
- package/ui/rhine/interaction.css +20 -0
- package/ui/rhine/investigation-board-plane.ts +5 -0
- package/ui/rhine/investigation-board.css +34 -0
- package/ui/rhine/investigation-board.ts +348 -0
- package/ui/rhine/investigation-edits.ts +46 -0
- package/ui/rhine/investigation-layout.ts +29 -0
- package/ui/rhine/label-prefetch.ts +34 -0
- package/ui/rhine/looping-title.ts +40 -0
- package/ui/rhine/manual-search.css +41 -0
- package/ui/rhine/manual-search.ts +89 -0
- package/ui/rhine/original/appearance.ts +180 -0
- package/ui/rhine/original/archive-label.ts +84 -0
- package/ui/rhine/original/archive-lighting.ts +39 -0
- package/ui/rhine/original/archive-loop.ts +82 -0
- package/ui/rhine/original/archive-refill.ts +64 -0
- package/ui/rhine/original/array-detail-data.ts +2 -0
- package/ui/rhine/original/array-detail.ts +41 -0
- package/ui/rhine/original/array-interior.ts +411 -0
- package/ui/rhine/original/array-occlusion.ts +166 -0
- package/ui/rhine/original/array-shell.ts +38 -0
- package/ui/rhine/original/array-visibility.ts +309 -0
- package/ui/rhine/original/asset-url.ts +4 -0
- package/ui/rhine/original/brand.ts +12 -0
- package/ui/rhine/original/cassette-lod.ts +81 -0
- package/ui/rhine/original/data.ts +21 -0
- package/ui/rhine/original/decryption.ts +185 -0
- package/ui/rhine/original/detail-signature.ts +18 -0
- package/ui/rhine/original/glass-reveal.ts +45 -0
- package/ui/rhine/original/internal-optics.ts +70 -0
- package/ui/rhine/original/label-renderer.ts +119 -0
- package/ui/rhine/original/label-textures.ts +117 -0
- package/ui/rhine/original/model-viewer.ts +658 -0
- package/ui/rhine/original/motion.ts +116 -0
- package/ui/rhine/original/opaque-backfaces.ts +64 -0
- package/ui/rhine/original/program-preparation.ts +111 -0
- package/ui/rhine/original/quality-renderer.ts +97 -0
- package/ui/rhine/original/quality-settings.ts +111 -0
- package/ui/rhine/original/render-quality.ts +163 -0
- package/ui/rhine/original/scene.ts +2051 -0
- package/ui/rhine/original/shelf-interior.ts +142 -0
- package/ui/rhine/original/ui-transitions.ts +111 -0
- package/ui/rhine/original/viewer-camera.ts +92 -0
- package/ui/rhine/original/viewport-layout.ts +41 -0
- package/ui/rhine/original-ui.css +2334 -0
- package/ui/rhine/readability.css +46 -0
- package/ui/rhine/reading-object.ts +96 -0
- package/ui/rhine/reading-origin.ts +28 -0
- package/ui/rhine/reading-room.css +55 -0
- package/ui/rhine/report-markdown.ts +98 -0
- package/ui/rhine/scene-edge-navigation.ts +84 -0
- package/ui/rhine/scene.ts +1726 -0
- package/ui/rhine/shelf-layout.ts +38 -0
- package/ui/rhine/snapshot-update-gate.ts +22 -0
- package/ui/rhine/source-reading.ts +15 -0
- package/ui/rhine/source-selection.css +164 -0
- package/ui/rhine/temporary-performance-observers.ts +163 -0
- package/ui/rhine/temporary-performance-panel.ts +124 -0
- package/ui/rhine/temporary-performance.css +19 -0
- package/ui/rhine/temporary-performance.ts +552 -0
- package/ui/rhine/temporary-render-diagnostics.ts +105 -0
- package/ui/rhine/tool-activity.ts +69 -0
- package/ui/rhine/types.ts +219 -0
- package/ui/rhine/visible-scene-hit.ts +13 -0
- package/ui/rhine/workbench.css +818 -0
- package/ui/rhine/workbench.ts +2245 -0
- package/ui/rhine/workspace-travel.ts +32 -0
- package/vite.rhine.config.ts +19 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// TEMPORARY RHINE PROFILER: hooks exist only while a capture is running.
|
|
2
|
+
import type { WebGLRenderer } from 'three';
|
|
3
|
+
import type { FrameToken } from './temporary-performance';
|
|
4
|
+
export type DiagnosticPass = { name: string; pass: { render: (...args: any[]) => any } };
|
|
5
|
+
|
|
6
|
+
/** Source payload accounting only. Never inspect or retain pixels, text or handles. */
|
|
7
|
+
export function uploadSize(method: string, args: any[]) {
|
|
8
|
+
let source: any, allocationBytes = 0;
|
|
9
|
+
if (method === 'bufferData') {
|
|
10
|
+
if (typeof args[1] === 'number') allocationBytes = args[1]; else source = args[1];
|
|
11
|
+
} else if (method === 'bufferSubData') source = args[2];
|
|
12
|
+
else if (method === 'texImage2D') source = args.length >= 9 ? args[8] : args[5];
|
|
13
|
+
else if (method === 'texSubImage2D') source = args.length >= 9 ? args[8] : args[6];
|
|
14
|
+
else if (method === 'texImage3D') source = args[9];
|
|
15
|
+
else if (method === 'texSubImage3D') source = args[10];
|
|
16
|
+
else if (method.startsWith('compressedTex')) source = args.slice().reverse().find(a => ArrayBuffer.isView(a));
|
|
17
|
+
let sourceBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source) ? source.byteLength : 0;
|
|
18
|
+
if (sourceBytes && method.startsWith('buffer') && ArrayBuffer.isView(source)) {
|
|
19
|
+
const unit = (source as { BYTES_PER_ELEMENT?: number }).BYTES_PER_ELEMENT || 1;
|
|
20
|
+
const offset = Number(args[3]) || 0, length = Number(args[4]) || 0;
|
|
21
|
+
sourceBytes = Math.min(Math.max(0, sourceBytes - offset * unit), length > 0 ? length * unit : Infinity);
|
|
22
|
+
}
|
|
23
|
+
const sourcePixels = source && !sourceBytes && typeof source !== 'number'
|
|
24
|
+
&& Number.isFinite(source.width) && Number.isFinite(source.height) ? source.width * source.height : 0;
|
|
25
|
+
return { sourceBytes, sourcePixels, allocationBytes };
|
|
26
|
+
}
|
|
27
|
+
const surfaceName = /^(?:Case_Engraving|Printed_Label|Frosted_Polymer|Ivory_Edges|Optical_|Subsurface_|Amber_|Champagne_|Moulded_|Titanium_|Index_|Internal_|Array_Interior|Shelf_Interior|Rhine_Archive_Label)[A-Za-z0-9_]*$/;
|
|
28
|
+
|
|
29
|
+
export function installRenderDiagnostics(renderer: WebGLRenderer, frame: () => FrameToken | undefined,
|
|
30
|
+
stage: (name: string) => void, passes: DiagnosticPass[], now: () => number,
|
|
31
|
+
outside: (detail: Record<string, unknown>) => void) {
|
|
32
|
+
const restore: (() => void)[] = [];
|
|
33
|
+
const replace = (owner: any, key: string, make: (original: (...args: any[]) => any) => (...args: any[]) => any) => {
|
|
34
|
+
const original = owner[key]; if (typeof original !== 'function') return;
|
|
35
|
+
const own = Object.getOwnPropertyDescriptor(owner, key), wrapper = make(original);
|
|
36
|
+
owner[key] = wrapper;
|
|
37
|
+
restore.push(() => { if (owner[key] !== wrapper) return; if (own) Object.defineProperty(owner, key, own); else delete owner[key]; });
|
|
38
|
+
};
|
|
39
|
+
for (const { name, pass } of passes) replace(pass, 'render', original => function(this: unknown, ...args: any[]) {
|
|
40
|
+
const token = frame(); if (!token?.detailed) return original.apply(this, args);
|
|
41
|
+
stage(name);
|
|
42
|
+
const start = now(), beforeCalls = renderer.info.render.calls, beforeTriangles = renderer.info.render.triangles;
|
|
43
|
+
try { return original.apply(this, args); }
|
|
44
|
+
finally {
|
|
45
|
+
const end = now(), row = token.sample.passWork![name] ||= { cpuMs: 0, calls: 0, triangles: 0 };
|
|
46
|
+
row.cpuMs += end - start; row.calls += renderer.info.render.calls - beforeCalls;
|
|
47
|
+
row.triangles += renderer.info.render.triangles - beforeTriangles;
|
|
48
|
+
token.sample.detailProbeMs! += now() - end;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
let lastToken: FrameToken | undefined;
|
|
52
|
+
let rows = new Map<string, NonNullable<FrameToken['sample']['drawFamilies']>[number]>();
|
|
53
|
+
replace(renderer, 'renderBufferDirect', original => function(this: unknown, camera: any, scene: any, geometry: any, material: any, object: any, group: any) {
|
|
54
|
+
const token = frame(); if (!token?.detailed) return original.call(this, camera, scene, geometry, material, object, group);
|
|
55
|
+
const beforeCalls = renderer.info.render.calls, beforeTriangles = renderer.info.render.triangles;
|
|
56
|
+
try { return original.call(this, camera, scene, geometry, material, object, group); }
|
|
57
|
+
finally {
|
|
58
|
+
const start = now();
|
|
59
|
+
if (lastToken !== token) { rows = new Map(); lastToken = token; }
|
|
60
|
+
let ancestor = object, family = 'other';
|
|
61
|
+
if (family === 'other') while (ancestor) {
|
|
62
|
+
const tag = ancestor.userData?.performanceFamily;
|
|
63
|
+
if (['hero', 'shelf', 'moving', 'rack', 'board', 'array'].includes(tag)) { family = tag; break; }
|
|
64
|
+
ancestor = ancestor.parent;
|
|
65
|
+
}
|
|
66
|
+
if (family === 'other' && object?.isInstancedMesh) family = 'array';
|
|
67
|
+
const candidate = object?.userData?.surface || object?.name || '';
|
|
68
|
+
const part = typeof candidate === 'string' && candidate.length < 100 && surfaceName.test(candidate) ? candidate : 'other';
|
|
69
|
+
const phase = material?.isMeshDepthMaterial || material?.isMeshDistanceMaterial ? 'shadow' : token.gpuStage || 'unknown';
|
|
70
|
+
const materialKind = material?.isMeshPhysicalMaterial ? 'physical' : material?.isMeshStandardMaterial ? 'standard'
|
|
71
|
+
: material?.isMeshBasicMaterial ? 'basic' : material?.isShaderMaterial ? 'shader' : 'other';
|
|
72
|
+
const key = [family, part, phase, materialKind].join(':');
|
|
73
|
+
let row = rows.get(key);
|
|
74
|
+
if (!row && rows.size < 96) {
|
|
75
|
+
row = { family, part, material: materialKind, phase, calls: 0, triangles: 0 };
|
|
76
|
+
rows.set(key, row); token.sample.drawFamilies!.push(row);
|
|
77
|
+
}
|
|
78
|
+
if (row) { row.calls += renderer.info.render.calls - beforeCalls; row.triangles += renderer.info.render.triangles - beforeTriangles; }
|
|
79
|
+
else token.sample.counters.detailDrawGroupsDropped = (Number(token.sample.counters.detailDrawGroupsDropped) || 0) + 1;
|
|
80
|
+
token.sample.detailProbeMs! += now() - start;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const gl = renderer.getContext();
|
|
84
|
+
for (const method of ['texImage2D', 'texSubImage2D', 'texImage3D', 'texSubImage3D', 'compressedTexImage2D',
|
|
85
|
+
'compressedTexSubImage2D', 'generateMipmap', 'bufferData', 'bufferSubData']) {
|
|
86
|
+
replace(gl, method, original => function(this: unknown, ...args: any[]) {
|
|
87
|
+
const token = frame();
|
|
88
|
+
if (method.startsWith('buffer') && !token?.detailed) return original.apply(this, args);
|
|
89
|
+
const start = now();
|
|
90
|
+
try { return original.apply(this, args); }
|
|
91
|
+
finally {
|
|
92
|
+
const end = now(), sizes = uploadSize(method, args);
|
|
93
|
+
if (token) {
|
|
94
|
+
token.sample.glWork ||= {};
|
|
95
|
+
const key = (token.gpuStage || 'whole-frame') + ':' + method;
|
|
96
|
+
const row = token.sample.glWork[key] ||= { calls: 0, cpuMs: 0, sourceBytes: 0, sourcePixels: 0, allocationBytes: 0 };
|
|
97
|
+
row.calls++; row.cpuMs += end - start;
|
|
98
|
+
row.sourceBytes += sizes.sourceBytes; row.sourcePixels += sizes.sourcePixels; row.allocationBytes += sizes.allocationBytes;
|
|
99
|
+
token.sample.detailProbeMs = (token.sample.detailProbeMs || 0) + now() - end;
|
|
100
|
+
} else outside({ kind: 'gl-upload-outside-frame', method, cpuMs: end - start, ...sizes });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return () => { for (const fn of restore.reverse()) fn(); rows.clear(); lastToken = undefined; };
|
|
105
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ArchiveOperation, ArchiveSource, InvestigationSnapshot, InvestigationToolCall } from './types';
|
|
2
|
+
|
|
3
|
+
/** Tool receipts drive the HUD; an earlier assistant paragraph is not completion. */
|
|
4
|
+
export function investigationActivity(snapshot: InvestigationSnapshot) {
|
|
5
|
+
const calls: InvestigationToolCall[] = snapshot.toolCalls ?? snapshot.operations ?? [];
|
|
6
|
+
const active = snapshot.running ? calls.filter(call => call.state === 'active'
|
|
7
|
+
&& !calls.some(child => child.state === 'active' && child.id.startsWith(`${call.id}:`))) : [];
|
|
8
|
+
const current = active.at(-1);
|
|
9
|
+
const latest = calls.at(-1);
|
|
10
|
+
return {
|
|
11
|
+
calls, active, current, recent: calls.slice(-3),
|
|
12
|
+
label: current ? `正在调用 ${current.tool}` : snapshot.running ? '等待下一步'
|
|
13
|
+
: snapshot.outcome === 'completed' ? '本轮调查已完成'
|
|
14
|
+
: snapshot.outcome === 'interrupted' ? '调查已停止' : snapshot.error ? '调查未完成'
|
|
15
|
+
: snapshot.answer ? '已返回内容' : '等待开始调查',
|
|
16
|
+
detail: current ? [current.query, active.length > 1 ? `另有 ${active.length - 1} 个调用进行中` : ''].filter(Boolean).join(' · ')
|
|
17
|
+
: snapshot.running ? latest ? `${latest.tool} ${latest.state === 'error' ? '调用失败' : '已返回'} · 等待 Agent 继续`
|
|
18
|
+
: '等待首次工具调用' : snapshot.error || '',
|
|
19
|
+
showReport: Boolean(snapshot.answer) && !snapshot.running && !active.length,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function webUrl(value?: string) {
|
|
24
|
+
try { const url = new URL(value || ''); if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return ''; url.hash = ''; return url.href; } catch { return ''; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function operationSource(operation: ArchiveOperation, sources: ArchiveSource[]) {
|
|
28
|
+
const candidates = sources.filter(source => !operation.dataVersion || source.dataVersion === operation.dataVersion);
|
|
29
|
+
const returned = candidates.find(source => operation.sourceIds.includes(source.id));
|
|
30
|
+
if (returned) return returned;
|
|
31
|
+
if (operation.tool === 'web_fetch' && operation.url) {
|
|
32
|
+
const url = webUrl(operation.url);
|
|
33
|
+
if (!url) return undefined;
|
|
34
|
+
const source = candidates.find(item => item.id === `web:${url}` || item.origin === 'web' && webUrl(item.url) === url);
|
|
35
|
+
if (source) return source;
|
|
36
|
+
// A live URL is a reading target, not a returned source: keep this card out
|
|
37
|
+
// of the material rack and counters until an actual receipt arrives.
|
|
38
|
+
if (operation.state !== 'complete') return { id: `web:${url}`, url, title: url, kind: 'web_page',
|
|
39
|
+
origin: 'web' as const, state: 'found' as const, excerpt: operation.state === 'active' ? '正在获取网页内容,返回后可阅读。' : '网页读取未完成。' };
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const matches = candidates.filter(source => operation.documentUid ? source.documentUid === operation.documentUid
|
|
43
|
+
: operation.documentId ? source.documentId === operation.documentId
|
|
44
|
+
: Boolean(operation.sourceRef && source.sourceRef === operation.sourceRef));
|
|
45
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** One card per real read call, including a repeat read of the same document. */
|
|
49
|
+
export function latestReadFocus(snapshot: InvestigationSnapshot, sources: ArchiveSource[]) {
|
|
50
|
+
const reads = (snapshot.operations || []).filter(call => call.kind === 'read');
|
|
51
|
+
const operation = reads.at(-1);
|
|
52
|
+
if (!operation) return undefined;
|
|
53
|
+
const source = operationSource(operation, sources);
|
|
54
|
+
if (!source) return undefined;
|
|
55
|
+
return { key: `${snapshot.sessionId}:${snapshot.investigationId || ''}:${operation.id}`, source, operation };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** New calls move the whole card downward once; streamed snapshots stay still. */
|
|
59
|
+
export class ReadCardMotion {
|
|
60
|
+
private animation?: Animation;
|
|
61
|
+
reveal(element: HTMLElement, reduced: boolean) {
|
|
62
|
+
this.dispose();
|
|
63
|
+
if (!reduced) this.animation = element.animate([
|
|
64
|
+
{ opacity: 0, transform: 'translateY(-20px)' },
|
|
65
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
66
|
+
], { duration: 340, easing: 'cubic-bezier(.22,1,.36,1)' });
|
|
67
|
+
}
|
|
68
|
+
dispose() { this.animation?.cancel(); this.animation = undefined; }
|
|
69
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { RenderQuality } from './original/render-quality';
|
|
2
|
+
import type { EvidenceCard, EvidenceBoardTool, EvidenceTemplate } from './evidence-board-model';
|
|
3
|
+
|
|
4
|
+
export interface BoardAnchorPoint { x: number; y: number; visible: boolean }
|
|
5
|
+
export interface BoardAnchorFrame {
|
|
6
|
+
corner: BoardAnchorPoint;
|
|
7
|
+
chalk: BoardAnchorPoint;
|
|
8
|
+
/** Clockwise corners of the view label on the board plane, in stage pixels. */
|
|
9
|
+
controls: [BoardAnchorPoint, BoardAnchorPoint, BoardAnchorPoint, BoardAnchorPoint];
|
|
10
|
+
/** Board-local writing surface, clockwise. Offscreen corners remain valid projection anchors. */
|
|
11
|
+
surface: [BoardAnchorPoint, BoardAnchorPoint, BoardAnchorPoint, BoardAnchorPoint];
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
zoom: number;
|
|
15
|
+
visible: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type RhineLocation = 'board' | 'archive' | 'desk';
|
|
19
|
+
|
|
20
|
+
export type ArchiveOrigin = 'local' | 'cloud' | 'web';
|
|
21
|
+
export type ArchiveState = 'found' | 'read' | 'cited';
|
|
22
|
+
export interface ArchiveSource {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
origin: ArchiveOrigin;
|
|
27
|
+
state: ArchiveState;
|
|
28
|
+
agentRead?: boolean;
|
|
29
|
+
excerpt: string;
|
|
30
|
+
/** The exact web_fetch text received by the Agent, separate from the preview excerpt. */
|
|
31
|
+
content?: string;
|
|
32
|
+
contentTruncated?: boolean;
|
|
33
|
+
documentId?: string;
|
|
34
|
+
documentUid?: string;
|
|
35
|
+
sourceRef?: string;
|
|
36
|
+
dataVersion?: string;
|
|
37
|
+
lineStart?: number;
|
|
38
|
+
lineEnd?: number;
|
|
39
|
+
url?: string;
|
|
40
|
+
callId?: string;
|
|
41
|
+
ranges?: { start: number; end: number }[];
|
|
42
|
+
readRanges?: { start: number; end: number }[];
|
|
43
|
+
/** Position explicitly requested by a clue/report citation, not an Agent read receipt. */
|
|
44
|
+
readingRange?: { start: number; end: number };
|
|
45
|
+
saved?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** One actual tool invocation, retained after completion for visual playback. */
|
|
48
|
+
export interface ArchiveOperation {
|
|
49
|
+
id: string;
|
|
50
|
+
tool: string;
|
|
51
|
+
kind: 'search' | 'read';
|
|
52
|
+
state: 'active' | 'complete' | 'error';
|
|
53
|
+
/** Final merged source IDs delivered by this invocation; reads exclude unread pointers. */
|
|
54
|
+
sourceIds: string[];
|
|
55
|
+
/** Requested HTTP(S) page, available before a web_fetch result exists. */
|
|
56
|
+
url?: string;
|
|
57
|
+
documentId?: string;
|
|
58
|
+
documentUid?: string;
|
|
59
|
+
sourceRef?: string;
|
|
60
|
+
dataVersion?: string;
|
|
61
|
+
query?: string;
|
|
62
|
+
/** Actual Host event times, absent when the loaded history lacks that boundary. */
|
|
63
|
+
startedAt?: number;
|
|
64
|
+
completedAt?: number;
|
|
65
|
+
}
|
|
66
|
+
export interface InvestigationToolCall {
|
|
67
|
+
id: string;
|
|
68
|
+
tool: string;
|
|
69
|
+
state: 'active' | 'complete' | 'error';
|
|
70
|
+
query?: string;
|
|
71
|
+
startedAt?: number;
|
|
72
|
+
completedAt?: number;
|
|
73
|
+
}
|
|
74
|
+
export interface InvestigationSnapshot {
|
|
75
|
+
sessionId: string;
|
|
76
|
+
running: boolean;
|
|
77
|
+
searching: boolean;
|
|
78
|
+
query: string;
|
|
79
|
+
tool: string;
|
|
80
|
+
sources: ArchiveSource[];
|
|
81
|
+
answer: string;
|
|
82
|
+
/** Stable identity of the current durable turn, or its pending user message. */
|
|
83
|
+
investigationId?: string;
|
|
84
|
+
/** Only a durable successful turn/end confirms completed. */
|
|
85
|
+
outcome?: 'running' | 'completed' | 'interrupted' | 'error' | 'unknown';
|
|
86
|
+
/** Sources received in this turn; the main sources array remains cumulative. */
|
|
87
|
+
turnSources?: ArchiveSource[];
|
|
88
|
+
/** Sources explicitly cited by the current answer, including reused older sources. */
|
|
89
|
+
reportSources?: ArchiveSource[];
|
|
90
|
+
/** Host event time for a confirmed successful turn/end. */
|
|
91
|
+
completedAt?: number;
|
|
92
|
+
question?: string;
|
|
93
|
+
phase?: 'idle' | 'searching' | 'reading' | 'synthesizing' | 'complete' | 'error';
|
|
94
|
+
activeDocumentId?: string;
|
|
95
|
+
activeSourceRef?: string;
|
|
96
|
+
activeDataVersion?: string;
|
|
97
|
+
error?: string;
|
|
98
|
+
hasMoreHistory?: boolean;
|
|
99
|
+
loadingHistory?: boolean;
|
|
100
|
+
records?: { id: string; tool: string; state: 'active' | 'complete' | 'error'; query: string; text: string }[];
|
|
101
|
+
/** Current-turn tool activities; parallel and already completed calls remain distinct. */
|
|
102
|
+
operations?: ArchiveOperation[];
|
|
103
|
+
/** All current-turn Host tools, including nested and non-corpus calls. */
|
|
104
|
+
toolCalls?: InvestigationToolCall[];
|
|
105
|
+
}
|
|
106
|
+
export interface RhineScene {
|
|
107
|
+
/** Resolves after queued scene preparation and a complete rendered frame. */
|
|
108
|
+
whenReady(): Promise<void>;
|
|
109
|
+
setLocation(location: RhineLocation): void;
|
|
110
|
+
setBoardFullscreen(fullscreen: boolean): void;
|
|
111
|
+
setBoardEditorInset(bottomFraction: number): void;
|
|
112
|
+
cancelBoardGesture(): boolean;
|
|
113
|
+
resetBoardView(): void;
|
|
114
|
+
zoomBoard(direction: -1 | 1): void;
|
|
115
|
+
setBoardTool(tool: EvidenceBoardTool): void;
|
|
116
|
+
setEvidenceInbox(value:import('./evidence-inbox').EvidenceInboxView,open:boolean):void;
|
|
117
|
+
getBoardCardBounds(id: string): { left: number; right: number; top: number; bottom: number } | null;
|
|
118
|
+
setBoardCards(cards: EvidenceCard[]): void;
|
|
119
|
+
selectBoardCard(id: string | null): void;
|
|
120
|
+
setSearching(searching: boolean): void;
|
|
121
|
+
setFollowAgent(value: boolean): void;
|
|
122
|
+
setSources(sources: ArchiveSource[], animate?: boolean): void;
|
|
123
|
+
setArchiveSources(sources: ArchiveSource[]): void;
|
|
124
|
+
selectArchiveSource(id: string, userInitiated?: boolean): void;
|
|
125
|
+
browseArchiveSource(id: string): boolean;
|
|
126
|
+
setInvestigation(snapshot: InvestigationSnapshot): void;
|
|
127
|
+
setShelfPage(page: number): void;
|
|
128
|
+
browseShelf(direction: number): void;
|
|
129
|
+
select(id: string | null): void;
|
|
130
|
+
navigate(axis: 'row' | 'lane', direction: number): boolean;
|
|
131
|
+
selectArchive(index: number): void;
|
|
132
|
+
setDetail(open: boolean): void;
|
|
133
|
+
setReadingObject(value: import('./reading-object').ReadingObject | null): void;
|
|
134
|
+
createAssemblyModel(): Promise<{ model: import('three').Group; dispose: () => void; setClarity?: (value: number) => void }>;
|
|
135
|
+
finishDecryption(): void;
|
|
136
|
+
setActive(active: boolean): void;
|
|
137
|
+
setReducedMotion(reduced: boolean): void;
|
|
138
|
+
setQuality(quality: RenderQuality): void;
|
|
139
|
+
resize(): void;
|
|
140
|
+
stats(): Record<string, unknown>;
|
|
141
|
+
dispose(): void;
|
|
142
|
+
}
|
|
143
|
+
export interface RhineSceneOptions {
|
|
144
|
+
assetBase: string;
|
|
145
|
+
navigationLimiter?: import('./archive-navigation-limit').ArchiveNavigationLimiter;
|
|
146
|
+
/** TEMPORARY RHINE PROFILER */
|
|
147
|
+
performanceProbe?: import('./temporary-performance').RenderPerformanceCapture;
|
|
148
|
+
/** Opening boundary used to distinguish late history from live tool receipts. */
|
|
149
|
+
activitySince?: number;
|
|
150
|
+
reducedMotion?: boolean;
|
|
151
|
+
onSelect?: (id: string) => void;
|
|
152
|
+
onArchiveSelect?: (index: number) => void;
|
|
153
|
+
onArchiveOpen?: (index: number) => void;
|
|
154
|
+
onArchiveSourceSelect?: (id: string | null, lane: number, userInitiated?: boolean) => void;
|
|
155
|
+
onArchiveSourceOpen?: (id: string) => void;
|
|
156
|
+
onShelfSelect?: (id: string | null, page: number) => void;
|
|
157
|
+
onLocationRequest?: (location: RhineLocation) => void;
|
|
158
|
+
onInboxOpen?:()=>void;
|
|
159
|
+
onInboxAnchor?: (value:import('./evidence-inbox').EvidenceInboxAnchor)=>void;
|
|
160
|
+
onBoardSelect?: (id: string | null, openEditor?: boolean) => void;
|
|
161
|
+
onBoardMove?: (id: string, x: number, y: number) => void;
|
|
162
|
+
onBoardResize?: (id: string, scale: number, x: number, y: number) => void;
|
|
163
|
+
onBoardAnchors?: (frame: BoardAnchorFrame) => void;
|
|
164
|
+
onBoardToolRequest?: () => void;
|
|
165
|
+
onBoardToolChange?: (tool: EvidenceBoardTool) => void;
|
|
166
|
+
onBoardPlace?: (template: EvidenceTemplate, position: { x: number; y: number }) => void;
|
|
167
|
+
onBoardConnect?: (fromId: string, toId: string) => void;
|
|
168
|
+
onDetailFrame?: (visibility: number) => void;
|
|
169
|
+
onError?: (message: string) => void;
|
|
170
|
+
}
|
|
171
|
+
export interface RhineOptions {
|
|
172
|
+
assetBase: string;
|
|
173
|
+
snapshot: InvestigationSnapshot;
|
|
174
|
+
agentAvailable?: boolean;
|
|
175
|
+
api: (endpoint: string, payload?: unknown, signal?: AbortSignal) => Promise<any>;
|
|
176
|
+
askAgent: (text: string) => Promise<void>;
|
|
177
|
+
loadHistory?: () => Promise<void>;
|
|
178
|
+
host?: RhineHostControls;
|
|
179
|
+
/** Per-controller upstream snapshot diagnostics, without snapshot contents. */
|
|
180
|
+
snapshotDiagnostics?: Pick<RhineHostControls, 'setPerformanceMonitor' | 'performanceStats'>;
|
|
181
|
+
close: () => void;
|
|
182
|
+
openSettings?: () => boolean;
|
|
183
|
+
isSettingsOpen?: () => boolean;
|
|
184
|
+
}
|
|
185
|
+
export interface RhineHostState {
|
|
186
|
+
sessionId: string;
|
|
187
|
+
title: string;
|
|
188
|
+
workspace: string;
|
|
189
|
+
blank: boolean;
|
|
190
|
+
running: boolean;
|
|
191
|
+
busy: boolean;
|
|
192
|
+
loading: boolean;
|
|
193
|
+
canSubmit: boolean;
|
|
194
|
+
error: string;
|
|
195
|
+
mode: string;
|
|
196
|
+
modes: { id: string; name: string; description: string }[];
|
|
197
|
+
sessions: { id: string; title: string; workspace: string; running: boolean; updatedAt: number }[];
|
|
198
|
+
model: string;
|
|
199
|
+
models: { id: string; name: string }[];
|
|
200
|
+
}
|
|
201
|
+
export interface RhineHostControls {
|
|
202
|
+
/** Optional bounded diagnostics for the host-to-workbench notification bridge. */
|
|
203
|
+
setPerformanceMonitor?(monitor?: (kind: string) => (() => void) | undefined): void;
|
|
204
|
+
performanceStats?(): Record<string, unknown>;
|
|
205
|
+
getState(): RhineHostState;
|
|
206
|
+
subscribe(listener: (state: RhineHostState) => void): () => void;
|
|
207
|
+
refresh(): Promise<void>;
|
|
208
|
+
createSession(mode?: string): Promise<void>;
|
|
209
|
+
openSession(id: string): Promise<void>;
|
|
210
|
+
selectMode(id: string): Promise<void>;
|
|
211
|
+
selectModel(id: string): Promise<void>;
|
|
212
|
+
cancel(): Promise<void>;
|
|
213
|
+
}
|
|
214
|
+
export interface RhineWorkbench {
|
|
215
|
+
update(snapshot: InvestigationSnapshot): void;
|
|
216
|
+
setActive(active: boolean): void;
|
|
217
|
+
dispose(): void;
|
|
218
|
+
stats(): Record<string, unknown>;
|
|
219
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
/** Raycaster visits hidden descendants too; navigation must respect what is actually shown. */
|
|
4
|
+
export function visibleSceneHit(hits: THREE.Intersection[]): THREE.Intersection | undefined {
|
|
5
|
+
return hits.find(hit => {
|
|
6
|
+
for (let object: THREE.Object3D | null = hit.object; object; object = object.parent) {
|
|
7
|
+
if (!object.visible) return false;
|
|
8
|
+
}
|
|
9
|
+
const material = (hit.object as THREE.Mesh).material;
|
|
10
|
+
const materials = Array.isArray(material) ? material : [material];
|
|
11
|
+
return materials.some(value => !value || value.visible && value.opacity > 0);
|
|
12
|
+
});
|
|
13
|
+
}
|