@forgeax/engine-picking 0.1.4 → 0.1.6
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/README.md +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/helpers/mock-shader-registry.d.ts +3 -0
- package/dist/__tests__/helpers/mock-shader-registry.d.ts.map +1 -0
- package/dist/index.mjs +4 -8
- package/dist/index.mjs.map +1 -1
- package/dist/pick-vertex.d.ts.map +1 -1
- package/dist/pick.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/__tests__/glyph-text-pick.test.ts +8 -7
- package/src/__tests__/helpers/mock-shader-registry.ts +118 -0
- package/src/__tests__/pick-vertex.unit.test.ts +80 -61
- package/src/__tests__/pick.test.ts +26 -13
- package/src/__tests__/visibility-regression.integration.test.ts +3 -4
- package/src/pick-vertex.ts +4 -4
- package/src/pick.ts +3 -3
- package/dist/asset-port.d.ts +0 -6
- package/dist/asset-port.d.ts.map +0 -1
- package/src/asset-port.ts +0 -13
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/asset-port.ts","../src/pick-errors.ts","../src/pick-core.ts","../src/pick.ts","../src/pick-tile.ts","../src/pick-vertex.ts","../src/viewport-to-world.ts"],"names":["Transform","ray","mat4","vec3","MeshFilter","toShared","MeshRenderer"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { World } from '@forgeax/engine-ecs';\nimport type { Asset, Handle, Result } from '@forgeax/engine-types';\n\nexport type PickingAssetResolutionError =\n ReturnType<World['sharedRefs']['resolve']> extends Result<unknown, infer E> ? E : never;\n\n/** Resolve a picking payload through the World shared-ref owner. */\nexport function resolvePickingAsset<T extends Asset>(\n world: World,\n handle: Handle<string, 'shared'>,\n): Result<T, PickingAssetResolutionError> {\n return world.sharedRefs.resolve<string, T>(handle) as Result<T, PickingAssetResolutionError>;\n}\n","// @forgeax/engine-picking — pick error model (feat-20260529-picking-raycasting-screen-to-entity M3 / w9).\n//\n// PickError code owner + derived PickErrorCode projection. The screen-to-entity\n// `pick(...)` free function returns `undefined` for the recoverable miss path (no ray\n// hit) and surfaces a structured PickError ONLY for the one unrecoverable precondition\n// failure: the supplied cameraEntity does not hold a `Camera` component, so no view /\n// projection matrix can be built. Separating the error channel (PickError) from the\n// normal miss channel (undefined) lets AI users branch with `if (hit)` for the common\n// case and inspect `.code` for the precondition failure (charter P3).\n//\n// D-1: a NEW closed union rather than reusing RuntimeErrorCode. RuntimeErrorCode's 8\n// members are all render / skin / shadow domain; folding a picking precondition into it\n// would dilute its semantic focus. A dedicated single-member union keeps the picking\n// error surface cohesive and additively evolvable (AGENTS.md Error model minor add-only).\n//\n// Related: requirements AC-11 (structured error signal) + AC-13 (closed-union SSOT);\n// plan-strategy D-1; plan-tasks.json w9 acceptanceCheck; charter P3.\n\n/**\n * Detail for the `PickError` camera-component-missing code.\n *\n * Carries the offending camera entity (packed `Entity` u32) so AI consumers can read\n * `.detail.cameraEntity` by property access (charter P4) — no string parsing of the\n * human-facing message.\n */\nexport interface PickCameraMissingDetail {\n readonly cameraEntity: number;\n}\n\n/**\n * Structured error for the picking precondition failure: the supplied `cameraEntity`\n * does not hold a `Camera` component, so `pick()` cannot build the view / projection\n * matrices needed to unproject the screen coordinate.\n *\n * Three-field structured surface per the AGENTS.md error model + `.detail`:\n * - `.code = 'camera-component-missing'` (closed `PickErrorCode`)\n * - `.expected` — the expected precondition (camera entity carries a `Camera`)\n * - `.hint` — an actionable `world.set` recovery directive\n * - `.detail = { cameraEntity }` — the offending entity (charter P4)\n *\n * Surfaced (not the normal miss path): a no-hit ray returns `undefined` from `pick()`;\n * this error is reserved for the unrecoverable precondition (charter P3 explicit failure,\n * separate channel from the recoverable miss).\n */\nexport class PickError extends Error {\n readonly code = 'camera-component-missing';\n readonly expected: string;\n readonly hint: string;\n readonly detail: PickCameraMissingDetail;\n\n constructor(cameraEntity: number) {\n const expected = 'cameraEntity holds a Camera component';\n const hint =\n `cameraEntity ${cameraEntity} has no Camera component; spawn or attach one before ` +\n 'picking, e.g. world.set(cameraEntity, Camera, { fov: Math.PI / 4, aspect, near, far })';\n super(`pick: cameraEntity ${cameraEntity} has no Camera component`);\n this.name = 'PickError';\n this.expected = expected;\n this.hint = hint;\n this.detail = { cameraEntity };\n }\n}\n\n/**\n * Closed union of picking precondition error codes, derived from the PickError owner.\n *\n * AI users perform exhaustive `switch (err.code)` without a default; TS guards completeness.\n */\nexport type PickErrorCode = PickError['code'];\n","// pick-core.ts — shared picking skeleton for pick() / pickVertex*() (feat-20260705 M2 M0).\n//\n// `pick.ts` (screen-to-entity ray-AABB) and `pick-vertex.ts` (vertex-level) share a\n// verbatim skeleton (F11): the `Transform.world` row type, the row reader, and the\n// camera-validation → view=invert(worldMatrix) → projection-branch → screenToRay\n// sequence. This module is the single source of truth for that skeleton\n// (architecture-principles §2 Derive, Don't Duplicate; AC-201). pick.ts and\n// pick-vertex.ts import from here rather than each maintaining their own copy.\n//\n// Error channel (charter P3): a `cameraEntity` that carries no `Camera` is the one\n// unrecoverable precondition — `computeScreenRay` throws a structured `PickError`\n// (`code: 'camera-component-missing'`). A camera entity that carries no resolvable\n// `Transform.world` is a degenerate miss (no view matrix can be built) — signalled by\n// a `undefined` return, which callers translate to their own miss shape\n// (`undefined` for pick, `[]`/`undefined` for the vertex queries).\n\nimport type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport { mat4, ray } from '@forgeax/engine-math';\nimport { Camera, type CameraProjection, cameraProjectionFromF32 } from '@forgeax/engine-render';\nimport { Transform } from '@forgeax/engine-scene';\nimport { PickError } from './pick-errors';\n\n/**\n * Read an entity's resolved world mat4 (16 column-major floats) from the\n * `Transform.world` column array view (feat-20260601 D-3). Returns a fresh\n * copy (the view aliases live slot bytes); `undefined` when the entity has no\n * Transform / world column.\n */\nexport function readWorldMatrix(world: World, entity: EntityHandle): Float32Array | undefined {\n const result = world.get(entity, Transform);\n return result.ok ? new Float32Array(result.value.world) : undefined;\n}\n\n/**\n * The screen-to-world ray plus the camera matrices used to build it.\n *\n * - `ray` — the unprojected world-space pick ray (origin + direction).\n * - `view` — `invert(camera Transform.world)`.\n * - `proj` — the camera projection matrix (perspective / orthographic).\n * - `projectionKind` — the resolved camera projection discriminant.\n *\n * `view` and `proj` are returned separately so vertex picking can build its own\n * `viewProj = proj * view` for `worldToScreen` without recomputing the branch.\n */\nexport interface ScreenRay {\n readonly ray: ray.Ray;\n readonly view: mat4.Mat4;\n readonly proj: mat4.Mat4;\n readonly projectionKind: CameraProjection;\n}\n\n/**\n * Build the screen-to-world ray for `cameraEntity` at the viewport-relative\n * `(screenX, screenY)` coordinate: validate the camera component, read its world\n * transform, invert it to a view matrix, branch the projection on the camera\n * discriminant, and unproject the coordinate into a world-space ray.\n *\n * @returns The `ScreenRay`, or `undefined` when the camera entity carries no\n * resolvable `Transform.world` (a degenerate miss — no view matrix can be built).\n * @throws {PickError} `code: 'camera-component-missing'` when `cameraEntity` has no `Camera`.\n */\nexport function computeScreenRay(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): ScreenRay | undefined {\n // An invalid viewport cannot define a screen ray; refuse it before the lower\n // math layer's safe fallback can become a fabricated origin hit.\n if (\n !Number.isFinite(viewportWidth) ||\n !Number.isFinite(viewportHeight) ||\n viewportWidth <= 0 ||\n viewportHeight <= 0\n ) {\n return undefined;\n }\n\n // --- precondition: camera component present (structured error, charter P3) ---\n const camRes = world.get(cameraEntity, Camera);\n if (!camRes.ok) {\n throw new PickError(cameraEntity as unknown as number);\n }\n const cam = camRes.value;\n\n // --- camera world transform (feat-20260601 D-3: read Transform.world mat4) ---\n const camWorld = readWorldMatrix(world, cameraEntity);\n if (camWorld === undefined) {\n // A camera entity without a Transform cannot define a view matrix; treat the\n // degenerate case as a miss rather than fabricating an identity view (no spurious hit).\n return undefined;\n }\n\n // --- view = invert(camera world mat4) ---\n const view = mat4.create();\n mat4.invert(view, camWorld as unknown as mat4.Mat4Like);\n\n // --- projection: branch on the camera discriminant (research Finding 5a) ---\n const projectionKind = cameraProjectionFromF32(cam.projection);\n const proj = mat4.create();\n if (projectionKind === 'orthographic') {\n mat4.orthographic(proj, cam.left, cam.right, cam.bottom, cam.top, cam.near, cam.far);\n } else {\n mat4.perspective(proj, cam.fov, cam.aspect, cam.near, cam.far);\n }\n\n // --- screen -> world ray (two-point unproject; clamp + NaN/Inf sanitized inside) ---\n const r = ray.create();\n ray.screenToRay(r, screenX, screenY, viewportWidth, viewportHeight, view, proj, projectionKind);\n\n return { ray: r, view, proj, projectionKind };\n}\n","// pick.ts — screen-to-entity raycast (feat-20260529-picking-raycasting-screen-to-entity M3 / w13).\n//\n// `pick(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight)`\n// is a free function (NOT `world.pick()`; requirements hard constraint): it unprojects\n// a viewport-relative screen coordinate into a world-space ray through the supplied\n// camera, walks every renderable archetype, ray-AABB tests each pickable mesh's\n// world-space bounding box, and returns the NEAREST `PickHit` (or `undefined` on miss).\n//\n// Mesh AABB source (feat-20260614 M8, D-15/D-18): the ray-AABB test needs each\n// mesh's local-space `MeshAsset.aabb`, resolved from the entity's `MeshFilter`\n// handle via `world.sharedRefs.resolve<MeshAsset>(handle)` (two-tier builtin /\n// world.sharedRefs dispatch). The registry no longer holds handles, so `pick`\n// takes no `AssetRegistry` -- it resolves entirely World-side. Keeping `pick` a\n// free function (rather than a `World` method) preserves the layering: `World`\n// (engine-ecs) stays asset-free; the picking glue lives in the runtime package\n// alongside the renderer.\n//\n// Error channel split (charter P3): the single unrecoverable precondition —\n// `cameraEntity` carries no `Camera` — throws a structured `PickError`\n// (`code: 'camera-component-missing'`); the ordinary \"ray hit nothing\" outcome returns\n// `undefined`. AI users branch with `if (hit)` for the common case and only handle\n// `PickError` where they cannot guarantee the camera entity is well-formed.\n//\n// Transform source (feat-20260601 D-3): per entity (camera + candidates) read the\n// single resolved `Transform.world` mat4 written by `propagateTransforms` -- the\n// GlobalTransform/Transform fallback double-track is retired (the world column\n// always exists on a Transform-bearing entity). The camera view is\n// `mat4.invert(Transform.world)`; the candidate AABB is the local AABB\n// transformed by `Transform.world` directly. The world mat4 is read through the\n// M1 row-level access, zero `{}` materialization.\n//\n// Related: requirements in-scope #5/#6/#7 + AC-05..AC-11; plan-strategy D-3 / D-6 / 5.3;\n// research Finding 4 (local->world AABB) + Finding 5 (entity-id via query rows).\n\nimport type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport { box3, ray, type Vec3Like, vec3 } from '@forgeax/engine-math';\nimport { MeshFilter, MeshRenderer } from '@forgeax/engine-render';\nimport { Transform } from '@forgeax/engine-scene';\nimport type { MeshAsset } from '@forgeax/engine-types';\nimport { toShared } from '@forgeax/engine-types';\nimport { resolvePickingAsset } from './asset-port';\nimport { computeScreenRay, readWorldMatrix } from './pick-core';\n\n/**\n * Result of a successful screen-to-entity pick.\n *\n * Minimal three-field surface (D-6):\n * - `entity` — the picked `Entity` (packed u32 handle, ready for `world.get` / `world.set`)\n * - `point` — the world-space ray/AABB entry point (`Vec3Like`; a 3-element array)\n * - `distance` — the entry distance along the ray from the camera (>= 0)\n *\n * No `face` / `uv` / `normal` fields: AABB picking has no triangle resolution, so those\n * would be a lie. A future mesh-precise pick spin-off owns them (requirements OOS).\n */\nexport interface PickHit {\n readonly entity: EntityHandle;\n readonly point: Vec3Like;\n readonly distance: number;\n}\n\n/**\n * Raycast from a viewport-relative screen coordinate into the world and return the\n * nearest pickable mesh entity whose world-space AABB the ray enters.\n *\n * @param world The ECS world holding the camera + candidate mesh entities (and the\n * per-World SharedRefStore that owns each `MeshAsset` and its local-space `aabb`).\n * @param cameraEntity The entity carrying the `Camera` component (and a Transform).\n * @param screenX Horizontal pixel coordinate relative to the viewport top-left (y-down).\n * @param screenY Vertical pixel coordinate.\n * @param viewportWidth Viewport width in pixels.\n * @param viewportHeight Viewport height in pixels.\n * @returns The nearest `PickHit`, or `undefined` when the ray hits nothing.\n * @throws {PickError} `code: 'camera-component-missing'` when `cameraEntity` has no `Camera`.\n */\nexport function pick(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): PickHit | undefined {\n // --- camera validation + view/projection + screen->world ray (pick-core skeleton) ---\n // Throws PickError('camera-component-missing') when cameraEntity has no Camera;\n // returns undefined when the camera has no resolvable Transform.world (degenerate miss).\n const screenRay = computeScreenRay(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n );\n if (screenRay === undefined) return undefined;\n const r = screenRay.ray;\n\n const query = world.query({ read: [Transform, MeshFilter, MeshRenderer] }).unwrap();\n\n const worldAabb = box3.create();\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestEntity: EntityHandle | undefined;\n\n for (const row of query) {\n const assetHandleRaw = Math.round(row.get(MeshFilter).assetHandle as number);\n if (assetHandleRaw === 0) continue;\n const meshRes = resolvePickingAsset<MeshAsset>(world, toShared<'MeshAsset'>(assetHandleRaw));\n if (!meshRes.ok) continue;\n const localAabb = meshRes.value.aabb;\n if (localAabb === undefined) continue;\n // Inverted-infinity empty box (mesh without positions): not pickable.\n if ((localAabb[0] as number) > (localAabb[3] as number)) continue;\n\n // read the packed Entity for this row from the essential id=0 Entity\n // column (`self` field); the column exists on every archetype.\n const entity = row.entity;\n\n // local AABB -> world AABB using the resolved Transform.world mat4\n // directly (feat-20260601 D-3: no compose from decomposed TRS).\n const entityWorld = readWorldMatrix(world, entity);\n if (entityWorld === undefined) continue;\n box3.transformBox3(\n worldAabb,\n localAabb,\n entityWorld as unknown as Parameters<typeof box3.transformBox3>[2],\n );\n\n const result = ray.rayAabbIntersects(r, worldAabb);\n if (result.hit && result.tmin < bestDistance) {\n bestDistance = result.tmin;\n bestEntity = entity;\n }\n }\n\n if (bestEntity === undefined) return undefined;\n\n // entry point = origin + direction * tmin\n const origin = vec3.create();\n const dir = vec3.create();\n ray.getOrigin(origin, r);\n ray.getDirection(dir, r);\n const point = vec3.create(\n (origin[0] as number) + (dir[0] as number) * bestDistance,\n (origin[1] as number) + (dir[1] as number) * bestDistance,\n (origin[2] as number) + (dir[2] as number) * bestDistance,\n );\n\n return { entity: bestEntity, point, distance: bestDistance };\n}\n","// pick-tile.ts - cell-level Tilemap query (feat-20260608 M0 baseline rebuild).\n//\n// `pickTile(world, tilemapEntity, worldX, worldY)` is a free function (not a\n// method on `World` -- charter F1 single-import barrel from\n// `@forgeax/engine-runtime`). It converts world-space coordinates into the\n// Tilemap's local cell grid, walks every TileLayer that ChildOf-s the\n// supplied tilemap, and returns the FIRST non-zero tile id seen when scanning\n// layers in DESCENDING `layerOrder` (highest layer drawn on top wins).\n//\n// charter mapping:\n// - P3 explicit failure as value: out-of-bounds and \"every layer empty\"\n// resolve to `Result.ok(null)` -- never throws or fires onError. Dead\n// handles and live entities without Tilemap are separate structural\n// failures; the `PickTileError` union is closed to two variants and stays\n// a runtime-only type (not exported through `@forgeax/engine-types`, since\n// pickTile is a runtime-only system).\n// - P4 consistent abstraction: matches the `pick(world, ...)` raycast\n// surface in pick.ts -- free function, world+entity input, structured\n// error union, hit-or-null return.\n// - F1 single-import: exported from `@forgeax/engine-picking`'s barrel.\n//\n// Anchors: requirements §integration-points (engine-runtime pickTile);\n// plan-tasks m0-t8; plan-strategy §D-5 file-by-file ECS API adaptation\n// (World-owned Query pattern from render-system-extract.ts).\n\nimport { Entity, type EntityHandle, type World } from '@forgeax/engine-ecs';\nimport { mat4, vec3 } from '@forgeax/engine-math';\nimport { TileLayer, Tilemap } from '@forgeax/engine-render/authoring';\nimport { ChildOf, Transform } from '@forgeax/engine-scene';\nimport { err, ok, type Result } from '@forgeax/engine-types';\n\n/**\n * Closed error union for `pickTile` (charter P3 + P4). Two variants cover the\n * structural-break paths; \"ray hit nothing\" is a value (`Result.ok(null)`),\n * not an error.\n */\nexport type PickTileError =\n | { readonly code: 'tilemap-not-found'; readonly tilemapEntity: EntityHandle }\n | { readonly code: 'tilemap-component-missing'; readonly tilemapEntity: EntityHandle };\n\n/**\n * Successful picking outcome. Returned through `Result.ok`; a `null` value\n * means the query landed in-bounds but every layer at that cell was empty\n * (or the point was outside the tilemap world bounds).\n */\nexport interface PickTileHit {\n readonly layerEntity: EntityHandle;\n readonly cellX: number;\n readonly cellY: number;\n readonly tileId: number;\n}\n\n/**\n * Find the topmost non-zero tile under `(worldX, worldY)` for a given\n * Tilemap entity, walking child TileLayer entities in DESCENDING\n * `layerOrder`.\n *\n * @returns\n * - `Result.ok(PickTileHit)` for a non-zero cell on some layer.\n * - `Result.ok(null)` for an empty cell or out-of-bounds query.\n * - `Result.err({ code: 'tilemap-not-found' })` for a dead handle.\n * - `Result.err({ code: 'tilemap-component-missing' })` for a live entity\n * without a Tilemap component.\n *\n * The caller must propagate the World so `Transform.world` is current. A\n * singular world transform follows `mat4.invert`'s deterministic identity\n * fallback, which keeps this error union structural rather than adding a\n * third diagnostic arm.\n */\nexport function pickTile(\n world: World,\n tilemapEntity: EntityHandle,\n worldX: number,\n worldY: number,\n): Result<PickTileHit | null, PickTileError> {\n const entityResult = world.get(tilemapEntity, Entity);\n if (!entityResult.ok) {\n return err({ code: 'tilemap-not-found', tilemapEntity });\n }\n\n const tilemapResult = world.get(tilemapEntity, Tilemap);\n if (!tilemapResult.ok) {\n return err({ code: 'tilemap-component-missing', tilemapEntity });\n }\n const tilemap = tilemapResult.value;\n const cols = tilemap.cols;\n const rows = tilemap.rows;\n // feat-20260709 M3: tileSize is one inline array<f32,2> column; the\n // world.get read path materialises it as a Float32Array ([width, height]).\n const tileSizeX = tilemap.tileSize[0] ?? 1;\n const tileSizeY = tilemap.tileSize[1] ?? 1;\n\n // Tilemap entities without a Transform retain the origin-default path. For\n // transformed maps, the full propagated affine inverse is the only correct\n // way to recover local cell coordinates under rotation and non-uniform scale.\n let localX = worldX;\n let localY = worldY;\n const transformResult = world.get(tilemapEntity, Transform);\n if (transformResult.ok) {\n const w = transformResult.value.world;\n if (w !== undefined && w.length >= 16) {\n const local = mat4.transformPoint(vec3.create(), mat4.invert(mat4.create(), w), [\n worldX,\n worldY,\n 0,\n ]);\n localX = local[0] ?? 0;\n localY = local[1] ?? 0;\n }\n }\n\n if (tileSizeX <= 0 || tileSizeY <= 0) return ok(null);\n if (localX < 0 || localY < 0) return ok(null);\n\n const cellX = Math.floor(localX / tileSizeX);\n const cellY = Math.floor(localY / tileSizeY);\n if (cellX < 0 || cellY < 0 || cellX >= cols || cellY >= rows) return ok(null);\n\n // Collect every TileLayer ChildOf-ing the supplied Tilemap entity, with\n // its layerOrder + tile array snapshot through one row iterator.\n type LayerInfo = {\n readonly entity: EntityHandle;\n readonly tiles: ArrayLike<number>;\n readonly layerOrder: number;\n };\n const layers: LayerInfo[] = [];\n const layerQuery = world.query({ read: [TileLayer, ChildOf] }).unwrap();\n for (const row of layerQuery) {\n const layerEntity = row.entity;\n const parent = row.get(ChildOf).parent;\n if ((parent as unknown as number) !== (tilemapEntity as unknown as number)) continue;\n const layerData = world.get(layerEntity, TileLayer);\n if (!layerData.ok) continue;\n layers.push({\n entity: layerEntity,\n tiles: layerData.value.tiles as ArrayLike<number>,\n layerOrder: row.get(TileLayer).layerOrder,\n });\n }\n\n layers.sort((a, b) => b.layerOrder - a.layerOrder);\n\n const cellIndex = cellY * cols + cellX;\n for (const layer of layers) {\n if (cellIndex >= layer.tiles.length) continue;\n const tileId = layer.tiles[cellIndex] ?? 0;\n if (tileId !== 0) {\n return ok({\n layerEntity: layer.entity,\n cellX,\n cellY,\n tileId,\n });\n }\n }\n return ok(null);\n}\n","// pick-vertex.ts — per-entity + full-scene vertex-level picking (feat-20260630-vertex-snapping-picking).\n//\n// `pickVertexOnEntity(world, cameraEntity, screenX, screenY, vpW, vpH, entity, options?)`\n// returns the vertex(es) on a single entity nearest to the supplied screen coordinate.\n//\n// `pickVertex(world, cameraEntity, screenX, screenY, vpW, vpH, options?)`\n// walks all renderable archetypes (AABB coarse cull), calls pickVertexOnEntity on each\n// ray-intersecting entity, and returns the globally-nearest vertex hits sorted by screenDist.\n//\n// M2 scope (w5): single-entity query with overloaded three-state return (D-2).\n// M3 scope (w8): degradation branches (D-4/D-5/AC-07/R-3) + pickVertex full-scene (R-2).\n//\n// Overload contract:\n// - No options / no limit → VertexHit | undefined\n// - { limit: N } → VertexHit[]\n//\n// Reuses pick()'s full skeleton (camera validation / view=invert(world) / projection branch /\n// screenToRay / world.sharedRefs.resolve / Transform.world / AABB coarse cull), then for each\n// triangle-list submesh that passes the AABB test, iterates every triangle and collects\n// vertex candidates via rayTriangleIntersects.\n//\n// Key design points:\n// - D-7: worldPos uses Vec3Like (not branded Vec3), mirroring PickHit.point.\n// - D-8: reuses PickError with ZERO new error codes — the sole throw remains\n// camera-component-missing; all other miss conditions return undefined / [].\n// - D-9: caller MUST propagateTransforms(world) before calling — the function reads\n// Transform.world directly.\n// - AC-08: deformed = isSkinned (attributes.skinIndex && attributes.skinWeight both\n// defined, SSOT at asset-registry.ts:1417-1418).\n// - R-3: behind-camera vertices (worldToScreen returns behind=true) are excluded.\n// - D-4: position three-branch narrow (Float32Array → use; ArrayBuffer → new Float32Array;\n// Uint16Array / undefined → skip mesh), mirrors computeAABB asset-registry.ts:1898-1929.\n// - D-5: only triangle-list topology participates; triangle-strip / line-list /\n// line-strip / point-list → skip submesh.\n// - AC-07: builtin mesh with aabb===undefined → fallback to walk-all-vertices\n// (not continue like pick.ts:184).\n//\n// Related: requirements AC-01/AC-02/AC-03/AC-04/AC-05/AC-07/AC-08/AC-09/AC-10/AC-13;\n// plan-strategy D-2/D-3/D-4/D-5/D-7/D-8/D-9 §4 R-2/R-3;\n// research Finding 1 (reuse pick skeleton) / Finding 2 (PickError) /\n// Finding 4 (computeAABB three-branch) / Finding 5 (builtin withoutAabb) /\n// Finding 6 (non-indexed sequence) / Finding 8 (Transform.world) /\n// Finding 9 (behind flag) / Finding 10 (isSkinned);\n// plan-tasks.json w5/w8 acceptanceCheck.\n\nimport type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport { mat4, ray, type Vec3Like, vec2, vec3 } from '@forgeax/engine-math';\nimport { MeshFilter, MeshRenderer } from '@forgeax/engine-render';\nimport { Transform } from '@forgeax/engine-scene';\nimport type { MeshAsset } from '@forgeax/engine-types';\nimport { toShared } from '@forgeax/engine-types';\nimport { resolvePickingAsset } from './asset-port';\nimport { computeScreenRay, readWorldMatrix } from './pick-core';\n\n// ── types ────────────────────────────────────────────────────────────────\n\n/**\n * Result of a successful vertex pick: the mesh vertex nearest to the\n * supplied screen-space coordinate.\n *\n * Field set:\n * - `entity` — the entity this vertex belongs to.\n * - `vertexIndex` — index into the vertex position buffer (0-based).\n * - `worldPos` — world-space position of the vertex (Vec3Like; rest-pose when\n * deformed=true). D-7: Vec3Like avoids math brand cast lint in\n * the runtime package.\n * - `screenDist` — screen-space pixel distance from the query coordinate to the\n * projected vertex position (non-negative).\n * - `worldDist` — perpendicular 3D distance from the vertex world position to\n * the pick ray (non-negative). Orthogonal counterpart to screenDist.\n * - `deformed` — true when the mesh is skinned (skinIndex + skinWeight attributes\n * both present), indicating worldPos reflects rest-pose, not GPU\n * skinning output.\n */\nexport interface VertexHit {\n readonly entity: EntityHandle;\n readonly vertexIndex: number;\n readonly worldPos: Vec3Like;\n readonly screenDist: number;\n readonly worldDist: number;\n readonly deformed: boolean;\n}\n\n// ── helpers ──────────────────────────────────────────────────────────────\n\n/** Scratch Vec2 for worldToScreen calls (reused across invocations). */\nconst _scratchVec2 = vec2.create();\n\n/**\n * Compute the perpendicular distance from a point to a ray in 3D.\n * rayDir is assumed normalized; returns |(P - O) x D|.\n */\nfunction pointToRayDist(\n px: number,\n py: number,\n pz: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n): number {\n const ex = px - ox;\n const ey = py - oy;\n const ez = pz - oz;\n const cx = ey * dz - ez * dy;\n const cy = ez * dx - ex * dz;\n const cz = ex * dy - ey * dx;\n return Math.sqrt(cx * cx + cy * cy + cz * cz);\n}\n\n/**\n * Test whether a ray intersects a local-space AABB transformed by a world matrix.\n * Returns false when the ray misses the box; returns true when the box is\n * inverted (empty) — caller falls through to walk-all-vertices (AC-07).\n *\n * Extracted from collectVertexHits and pickVertex (review I-3, ~48 lines x2).\n */\nfunction rayHitsWorldAabb(\n r: Float32Array,\n localAabb: Float32Array,\n worldMat: mat4.Mat4Like,\n): boolean {\n // skip inverted-infinity empty box — fall through to walk-all-vertices\n if ((localAabb[0] as number) > (localAabb[3] as number)) return true;\n\n const corners = [\n [localAabb[0] as number, localAabb[1] as number, localAabb[2] as number],\n [localAabb[3] as number, localAabb[1] as number, localAabb[2] as number],\n [localAabb[0] as number, localAabb[4] as number, localAabb[2] as number],\n [localAabb[3] as number, localAabb[4] as number, localAabb[2] as number],\n [localAabb[0] as number, localAabb[1] as number, localAabb[5] as number],\n [localAabb[3] as number, localAabb[1] as number, localAabb[5] as number],\n [localAabb[0] as number, localAabb[4] as number, localAabb[5] as number],\n [localAabb[3] as number, localAabb[4] as number, localAabb[5] as number],\n ];\n let minX = Infinity,\n minY = Infinity,\n minZ = Infinity;\n let maxX = -Infinity,\n maxY = -Infinity,\n maxZ = -Infinity;\n const tmpP = vec3.create();\n for (const c of corners) {\n mat4.transformPoint(tmpP, worldMat, c as unknown as Vec3Like);\n const cx = tmpP[0] as number;\n const cy = tmpP[1] as number;\n const cz = tmpP[2] as number;\n if (cx < minX) minX = cx;\n if (cy < minY) minY = cy;\n if (cz < minZ) minZ = cz;\n if (cx > maxX) maxX = cx;\n if (cy > maxY) maxY = cy;\n if (cz > maxZ) maxZ = cz;\n }\n const worldAabb = new Float32Array(6);\n worldAabb[0] = minX;\n worldAabb[1] = minY;\n worldAabb[2] = minZ;\n worldAabb[3] = maxX;\n worldAabb[4] = maxY;\n worldAabb[5] = maxZ;\n\n const aabbResult = ray.rayAabbIntersects(\n r,\n worldAabb as unknown as import('@forgeax/engine-math').box3.Box3Like,\n );\n return aabbResult.hit;\n}\n\n/**\n * Narrow the position attribute per the three-branch contract (D-4),\n * mirrors computeAABB asset-registry.ts:1898-1908.\n * Returns the Float32Array position data, or undefined when the position\n * is Uint16Array / undefined / too short (skip this mesh).\n */\nfunction narrowPosition(\n positionAttr: ArrayBuffer | Float32Array | Uint16Array | undefined,\n): Float32Array | undefined {\n if (positionAttr instanceof Float32Array) {\n return positionAttr;\n }\n if (positionAttr instanceof ArrayBuffer) {\n return new Float32Array(positionAttr);\n }\n // Uint16Array or undefined → skip (no usable float-coordinate vertex data)\n return undefined;\n}\n\n// ── internal: per-entity vertex hit collection (extracted for pickVertex reuse) ──\n\n/**\n * Core per-entity vertex hit collection.\n * Returns all vertex candidates for one entity, unsorted.\n * Caller sorts and applies limit.\n */\nfunction collectVertexHits(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n): VertexHit[] {\n // ── camera validation + view/projection + screen->world ray (pick-core skeleton) ──\n // Throws PickError('camera-component-missing') when cameraEntity has no Camera;\n // returns undefined when the camera has no resolvable Transform.world (D-9 preamble miss).\n const screenRay = computeScreenRay(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n );\n if (screenRay === undefined) {\n return [];\n }\n const { ray: r, view, proj } = screenRay;\n\n // ── viewProj = proj * view (precompute for worldToScreen calls) ──\n const viewProj = mat4.create();\n mat4.multiply(viewProj, proj as unknown as mat4.Mat4Like, view as unknown as mat4.Mat4Like);\n\n const rOx = r[0] as number;\n const rOy = r[1] as number;\n const rOz = r[2] as number;\n const rDx = r[3] as number;\n const rDy = r[4] as number;\n const rDz = r[5] as number;\n\n // ── resolve entity's mesh asset ──\n const mfRes = world.get(entity, MeshFilter);\n if (!mfRes.ok) {\n return [];\n }\n const meshRes = resolvePickingAsset<MeshAsset>(\n world,\n toShared<'MeshAsset'>(mfRes.value.assetHandle as unknown as number),\n );\n if (!meshRes.ok) {\n return [];\n }\n const mesh = meshRes.value;\n\n // ── position attribute narrow (D-4: three-branch, mirrors computeAABB) ──\n const positions = narrowPosition(mesh.attributes.position);\n if (positions === undefined || positions.length < 3) {\n return [];\n }\n\n // ── entity world transform (D-9: requires propagateTransforms preamble) ──\n const entityWorld = readWorldMatrix(world, entity);\n if (entityWorld === undefined) {\n return [];\n }\n\n // ── deformed flag (AC-08: isSkinned, SSOT at asset-registry.ts:1417-1418) ──\n const attrs = mesh.attributes;\n const deformed: boolean =\n attrs !== undefined && attrs.skinIndex !== undefined && attrs.skinWeight !== undefined;\n\n // ── AABB coarse cull (if aabb present) ──\n // AC-07: if aabb is undefined, DO NOT continue — fallthrough to walk-all-vertices.\n // This is the key behavioral difference from pick.ts:184.\n const entityWMLike = entityWorld as unknown as mat4.Mat4Like;\n\n if (mesh.aabb !== undefined && !rayHitsWorldAabb(r, mesh.aabb, entityWMLike)) {\n return [];\n }\n // AC-07: aabb===undefined → fall through to walk-all-vertices (builtin no-AABB fallback).\n\n // ── iterate submeshes + triangles ──\n const candidates: VertexHit[] = [];\n const seen = new Set<number>(); // (entity, vertexIndex) dedup (review I-1)\n const indices = mesh.indices;\n const submeshes = mesh.submeshes;\n const maxVertexIndex = Math.floor(positions.length / 3) - 1;\n\n // Shared vertex-emit closure: given 3 vertex indices for a hit triangle,\n // compute worldPos + screenDist + worldDist and push candidates.\n // Extracted from the duplicated ~45-line indexed/non-indexed body (review I-2).\n const emitTriangleVertices = (i0: number, i1: number, i2: number): void => {\n const ax = positions[i0 * 3 + 0] as number;\n const ay = positions[i0 * 3 + 1] as number;\n const az = positions[i0 * 3 + 2] as number;\n const bx = positions[i1 * 3 + 0] as number;\n const by = positions[i1 * 3 + 1] as number;\n const bz = positions[i1 * 3 + 2] as number;\n const cx = positions[i2 * 3 + 0] as number;\n const cy = positions[i2 * 3 + 1] as number;\n const cz = positions[i2 * 3 + 2] as number;\n\n const triResult = ray.rayTriangleIntersects(\n r,\n [ax, ay, az] as unknown as Vec3Like,\n [bx, by, bz] as unknown as Vec3Like,\n [cx, cy, cz] as unknown as Vec3Like,\n );\n\n if (!triResult.hit) return;\n\n for (const [vi, lx, ly, lz] of [\n [i0, ax, ay, az],\n [i1, bx, by, bz],\n [i2, cx, cy, cz],\n ] as [number, number, number, number][]) {\n if (Number.isNaN(lx) || Number.isNaN(ly) || Number.isNaN(lz)) continue;\n if (!Number.isFinite(lx) || !Number.isFinite(ly) || !Number.isFinite(lz)) continue;\n\n const worldVec = vec3.create();\n mat4.transformPoint(worldVec, entityWMLike, [lx, ly, lz] as unknown as Vec3Like);\n const wx = worldVec[0] as number;\n const wy = worldVec[1] as number;\n const wz = worldVec[2] as number;\n\n const screenRes = ray.worldToScreen(\n _scratchVec2,\n [wx, wy, wz] as unknown as Vec3Like,\n viewProj as unknown as import('@forgeax/engine-math').Mat4Like,\n viewportWidth,\n viewportHeight,\n );\n\n if (screenRes.behind) continue;\n\n // Dedup: same vertex hit by multiple triangles — keep only first\n // (same vertexIndex = same world-space position, screenDist/worldDist identical).\n if (seen.has(vi)) continue;\n seen.add(vi);\n\n const sx = _scratchVec2[0] as number;\n const sy = _scratchVec2[1] as number;\n const sdx = sx - screenX;\n const sdy = sy - screenY;\n const screenDist = Math.sqrt(sdx * sdx + sdy * sdy);\n const worldDist = pointToRayDist(wx, wy, wz, rOx, rOy, rOz, rDx, rDy, rDz);\n\n candidates.push({\n entity,\n vertexIndex: vi,\n worldPos: [wx, wy, wz] as unknown as Vec3Like,\n screenDist,\n worldDist,\n deformed,\n });\n }\n };\n\n for (const submesh of submeshes) {\n // D-5: only triangle-list participates\n if (submesh.topology !== 'triangle-list') continue;\n\n const idxOffset = submesh.indexOffset;\n const idxCount = submesh.indexCount;\n\n if (indices !== undefined && indices.length > 0 && idxCount > 0) {\n // indexed draw\n const triCount = Math.floor(idxCount / 3);\n for (let ti = 0; ti < triCount; ti++) {\n const i0 = indices[idxOffset + ti * 3 + 0] as number;\n const i1 = indices[idxOffset + ti * 3 + 1] as number;\n const i2 = indices[idxOffset + ti * 3 + 2] as number;\n\n if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;\n emitTriangleVertices(i0, i1, i2);\n }\n } else {\n // AC-09: no index buffer → non-indexed triangle sequence\n const submeshVertexCount = submesh.vertexCount;\n const triCount = Math.floor(submeshVertexCount / 3);\n\n for (let ti = 0; ti < triCount; ti++) {\n const i0 = ti * 3;\n const i1 = ti * 3 + 1;\n const i2 = ti * 3 + 2;\n\n if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;\n emitTriangleVertices(i0, i1, i2);\n }\n }\n }\n\n return candidates;\n}\n\n// ── overload signatures: pickVertexOnEntity (D-2: three-state static dispatch) ──\n\n/**\n * Query the nearest vertex on a single entity.\n *\n * Without options: returns `VertexHit | undefined` (nearest hit, or `undefined` on miss).\n *\n * @param world The ECS world (propagateTransforms must have been called this frame).\n * @param cameraEntity Entity carrying the Camera component (and Transform).\n * @param screenX Horizontal pixel coordinate (viewport top-left, y-down).\n * @param screenY Vertical pixel coordinate.\n * @param viewportWidth Viewport width in pixels.\n * @param viewportHeight Viewport height in pixels.\n * @param entity The mesh entity to query (must carry MeshFilter + MeshRenderer).\n * @returns The nearest `VertexHit`, or `undefined` when nothing is hit.\n * @throws {PickError} `code: 'camera-component-missing'` when cameraEntity has no Camera.\n */\nexport function pickVertexOnEntity(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n): VertexHit | undefined;\n\n/**\n * Query up to `limit` nearest vertices on a single entity.\n *\n * With `{ limit }`: returns `VertexHit[]` sorted by `screenDist` ascending.\n *\n * @param options.limit Maximum number of candidates to return (returns all available\n * vertices when limit exceeds the hit count).\n * @returns Sorted array of `VertexHit` (empty on miss).\n */\nexport function pickVertexOnEntity(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n options: { limit: number },\n): VertexHit[];\n\nexport function pickVertexOnEntity(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n options?: { limit: number },\n): VertexHit | VertexHit[] | undefined {\n const candidates = collectVertexHits(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n entity,\n );\n\n // ── sort by screenDist ascending ──\n candidates.sort((a, b) => a.screenDist - b.screenDist);\n\n // ── apply limit / return shape ──\n const limit = options?.limit;\n if (limit !== undefined) {\n return candidates.slice(0, limit);\n }\n return candidates[0];\n}\n\n// ── overload signatures: pickVertex (full-scene, D-2: three-state static dispatch) ──\n\n/**\n * Query the nearest vertex across all pickable mesh entities in the world.\n *\n * Without options: returns `VertexHit | undefined` (globally nearest, or `undefined` on miss).\n *\n * Walks all renderable archetypes, does an AABB coarse cull (R-2), then calls\n * `pickVertexOnEntity` on each ray-intersecting entity. Builtin meshes without AABB\n * fall through to walk-all-vertices (AC-07).\n *\n * @param world The ECS world (propagateTransforms must have been called this frame).\n * @param cameraEntity Entity carrying the Camera component (and Transform).\n * @param screenX Horizontal pixel coordinate (viewport top-left, y-down).\n * @param screenY Vertical pixel coordinate.\n * @param viewportWidth Viewport width in pixels.\n * @param viewportHeight Viewport height in pixels.\n * @returns The globally nearest `VertexHit`, or `undefined` when nothing is hit.\n * @throws {PickError} `code: 'camera-component-missing'` when cameraEntity has no Camera.\n */\nexport function pickVertex(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): VertexHit | undefined;\n\n/**\n * Query up to `limit` nearest vertices across all pickable mesh entities.\n *\n * With `{ limit }`: returns `VertexHit[]` globally sorted by `screenDist` ascending.\n *\n * @param options.limit Maximum number of candidates to return.\n * @returns Sorted array of `VertexHit` (empty on miss).\n */\nexport function pickVertex(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n options: { limit: number },\n): VertexHit[];\n\nexport function pickVertex(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n options?: { limit: number },\n): VertexHit | VertexHit[] | undefined {\n // ── camera validation + view/projection + screen->world ray (pick-core skeleton) ──\n // Throws PickError('camera-component-missing') when cameraEntity has no Camera;\n // returns undefined when the camera has no resolvable Transform.world (degenerate miss).\n const screenRay = computeScreenRay(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n );\n if (screenRay === undefined) {\n if (options) return [];\n return undefined;\n }\n const r = screenRay.ray;\n\n // ── walk renderable archetypes (Transform + MeshFilter + MeshRenderer) ──\n // Reuse pick.ts archetype walk skeleton (research Finding 1).\n const query = world.query({ read: [Transform, MeshFilter, MeshRenderer] }).unwrap();\n\n const allCandidates: VertexHit[] = [];\n\n for (const row of query) {\n const assetHandleRaw = Math.round(row.get(MeshFilter).assetHandle as number);\n if (assetHandleRaw === 0) continue;\n const meshRes = resolvePickingAsset<MeshAsset>(world, toShared<'MeshAsset'>(assetHandleRaw));\n if (!meshRes.ok) continue;\n\n // read entity (mirrors pick.ts:190)\n const entity = row.entity;\n\n // read entity world matrix\n const entityWorld = readWorldMatrix(world, entity);\n if (entityWorld === undefined) continue;\n\n // AABB coarse cull (R-2): if aabb present, test ray intersection.\n // If aabb===undefined (builtin), fall through to collectVertexHits (AC-07).\n const mesh = meshRes.value;\n if (\n mesh.aabb !== undefined &&\n !rayHitsWorldAabb(r, mesh.aabb, entityWorld as unknown as mat4.Mat4Like)\n ) {\n continue;\n }\n\n // Collect vertices for this entity\n const entityHits = collectVertexHits(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n entity,\n );\n for (const h of entityHits) {\n allCandidates.push(h);\n }\n }\n\n // ── global sort by screenDist ascending ──\n allCandidates.sort((a, b) => a.screenDist - b.screenDist);\n\n // ── apply limit / return shape ──\n const limit = options?.limit;\n if (limit !== undefined) {\n return allCandidates.slice(0, limit);\n }\n return allCandidates[0];\n}\n","import type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport type { ray } from '@forgeax/engine-math';\nimport { computeScreenRay } from './pick-core';\n\n/**\n * Unproject a viewport pixel into a world-space ray.\n *\n * `screenX`/`screenY` use the canvas convention: top-left origin, y down.\n * The caller must run `propagateTransforms(world)` for the current frame first.\n * A camera without a resolvable Transform returns `undefined`; a missing\n * Camera component throws the package's structured `PickError`.\n */\nexport function viewportToWorld(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): ray.Ray | undefined {\n return computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight)\n ?.ray;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/pick-errors.ts","../src/pick-core.ts","../src/pick.ts","../src/pick-tile.ts","../src/pick-vertex.ts","../src/viewport-to-world.ts"],"names":["Transform","ray","mat4","vec3","MeshFilter","resolveAssetHandle","toShared","MeshRenderer"],"mappings":"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","file":"index.mjs","sourcesContent":["// @forgeax/engine-picking — pick error model (feat-20260529-picking-raycasting-screen-to-entity M3 / w9).\n//\n// PickError code owner + derived PickErrorCode projection. The screen-to-entity\n// `pick(...)` free function returns `undefined` for the recoverable miss path (no ray\n// hit) and surfaces a structured PickError ONLY for the one unrecoverable precondition\n// failure: the supplied cameraEntity does not hold a `Camera` component, so no view /\n// projection matrix can be built. Separating the error channel (PickError) from the\n// normal miss channel (undefined) lets AI users branch with `if (hit)` for the common\n// case and inspect `.code` for the precondition failure (charter P3).\n//\n// D-1: a NEW closed union rather than reusing RuntimeErrorCode. RuntimeErrorCode's 8\n// members are all render / skin / shadow domain; folding a picking precondition into it\n// would dilute its semantic focus. A dedicated single-member union keeps the picking\n// error surface cohesive and additively evolvable (AGENTS.md Error model minor add-only).\n//\n// Related: requirements AC-11 (structured error signal) + AC-13 (closed-union SSOT);\n// plan-strategy D-1; plan-tasks.json w9 acceptanceCheck; charter P3.\n\n/**\n * Detail for the `PickError` camera-component-missing code.\n *\n * Carries the offending camera entity (packed `Entity` u32) so AI consumers can read\n * `.detail.cameraEntity` by property access (charter P4) — no string parsing of the\n * human-facing message.\n */\nexport interface PickCameraMissingDetail {\n readonly cameraEntity: number;\n}\n\n/**\n * Structured error for the picking precondition failure: the supplied `cameraEntity`\n * does not hold a `Camera` component, so `pick()` cannot build the view / projection\n * matrices needed to unproject the screen coordinate.\n *\n * Three-field structured surface per the AGENTS.md error model + `.detail`:\n * - `.code = 'camera-component-missing'` (closed `PickErrorCode`)\n * - `.expected` — the expected precondition (camera entity carries a `Camera`)\n * - `.hint` — an actionable `world.set` recovery directive\n * - `.detail = { cameraEntity }` — the offending entity (charter P4)\n *\n * Surfaced (not the normal miss path): a no-hit ray returns `undefined` from `pick()`;\n * this error is reserved for the unrecoverable precondition (charter P3 explicit failure,\n * separate channel from the recoverable miss).\n */\nexport class PickError extends Error {\n readonly code = 'camera-component-missing';\n readonly expected: string;\n readonly hint: string;\n readonly detail: PickCameraMissingDetail;\n\n constructor(cameraEntity: number) {\n const expected = 'cameraEntity holds a Camera component';\n const hint =\n `cameraEntity ${cameraEntity} has no Camera component; spawn or attach one before ` +\n 'picking, e.g. world.set(cameraEntity, Camera, { fov: Math.PI / 4, aspect, near, far })';\n super(`pick: cameraEntity ${cameraEntity} has no Camera component`);\n this.name = 'PickError';\n this.expected = expected;\n this.hint = hint;\n this.detail = { cameraEntity };\n }\n}\n\n/**\n * Closed union of picking precondition error codes, derived from the PickError owner.\n *\n * AI users perform exhaustive `switch (err.code)` without a default; TS guards completeness.\n */\nexport type PickErrorCode = PickError['code'];\n","// pick-core.ts — shared picking skeleton for pick() / pickVertex*() (feat-20260705 M2 M0).\n//\n// `pick.ts` (screen-to-entity ray-AABB) and `pick-vertex.ts` (vertex-level) share a\n// verbatim skeleton (F11): the `Transform.world` row type, the row reader, and the\n// camera-validation → view=invert(worldMatrix) → projection-branch → screenToRay\n// sequence. This module is the single source of truth for that skeleton\n// (architecture-principles §2 Derive, Don't Duplicate; AC-201). pick.ts and\n// pick-vertex.ts import from here rather than each maintaining their own copy.\n//\n// Error channel (charter P3): a `cameraEntity` that carries no `Camera` is the one\n// unrecoverable precondition — `computeScreenRay` throws a structured `PickError`\n// (`code: 'camera-component-missing'`). A camera entity that carries no resolvable\n// `Transform.world` is a degenerate miss (no view matrix can be built) — signalled by\n// a `undefined` return, which callers translate to their own miss shape\n// (`undefined` for pick, `[]`/`undefined` for the vertex queries).\n\nimport type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport { mat4, ray } from '@forgeax/engine-math';\nimport { Camera, type CameraProjection, cameraProjectionFromF32 } from '@forgeax/engine-render';\nimport { Transform } from '@forgeax/engine-scene';\nimport { PickError } from './pick-errors';\n\n/**\n * Read an entity's resolved world mat4 (16 column-major floats) from the\n * `Transform.world` column array view (feat-20260601 D-3). Returns a fresh\n * copy (the view aliases live slot bytes); `undefined` when the entity has no\n * Transform / world column.\n */\nexport function readWorldMatrix(world: World, entity: EntityHandle): Float32Array | undefined {\n const result = world.get(entity, Transform);\n return result.ok ? new Float32Array(result.value.world) : undefined;\n}\n\n/**\n * The screen-to-world ray plus the camera matrices used to build it.\n *\n * - `ray` — the unprojected world-space pick ray (origin + direction).\n * - `view` — `invert(camera Transform.world)`.\n * - `proj` — the camera projection matrix (perspective / orthographic).\n * - `projectionKind` — the resolved camera projection discriminant.\n *\n * `view` and `proj` are returned separately so vertex picking can build its own\n * `viewProj = proj * view` for `worldToScreen` without recomputing the branch.\n */\nexport interface ScreenRay {\n readonly ray: ray.Ray;\n readonly view: mat4.Mat4;\n readonly proj: mat4.Mat4;\n readonly projectionKind: CameraProjection;\n}\n\n/**\n * Build the screen-to-world ray for `cameraEntity` at the viewport-relative\n * `(screenX, screenY)` coordinate: validate the camera component, read its world\n * transform, invert it to a view matrix, branch the projection on the camera\n * discriminant, and unproject the coordinate into a world-space ray.\n *\n * @returns The `ScreenRay`, or `undefined` when the camera entity carries no\n * resolvable `Transform.world` (a degenerate miss — no view matrix can be built).\n * @throws {PickError} `code: 'camera-component-missing'` when `cameraEntity` has no `Camera`.\n */\nexport function computeScreenRay(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): ScreenRay | undefined {\n // An invalid viewport cannot define a screen ray; refuse it before the lower\n // math layer's safe fallback can become a fabricated origin hit.\n if (\n !Number.isFinite(viewportWidth) ||\n !Number.isFinite(viewportHeight) ||\n viewportWidth <= 0 ||\n viewportHeight <= 0\n ) {\n return undefined;\n }\n\n // --- precondition: camera component present (structured error, charter P3) ---\n const camRes = world.get(cameraEntity, Camera);\n if (!camRes.ok) {\n throw new PickError(cameraEntity as unknown as number);\n }\n const cam = camRes.value;\n\n // --- camera world transform (feat-20260601 D-3: read Transform.world mat4) ---\n const camWorld = readWorldMatrix(world, cameraEntity);\n if (camWorld === undefined) {\n // A camera entity without a Transform cannot define a view matrix; treat the\n // degenerate case as a miss rather than fabricating an identity view (no spurious hit).\n return undefined;\n }\n\n // --- view = invert(camera world mat4) ---\n const view = mat4.create();\n mat4.invert(view, camWorld as unknown as mat4.Mat4Like);\n\n // --- projection: branch on the camera discriminant (research Finding 5a) ---\n const projectionKind = cameraProjectionFromF32(cam.projection);\n const proj = mat4.create();\n if (projectionKind === 'orthographic') {\n mat4.orthographic(proj, cam.left, cam.right, cam.bottom, cam.top, cam.near, cam.far);\n } else {\n mat4.perspective(proj, cam.fov, cam.aspect, cam.near, cam.far);\n }\n\n // --- screen -> world ray (two-point unproject; clamp + NaN/Inf sanitized inside) ---\n const r = ray.create();\n ray.screenToRay(r, screenX, screenY, viewportWidth, viewportHeight, view, proj, projectionKind);\n\n return { ray: r, view, proj, projectionKind };\n}\n","// pick.ts — screen-to-entity raycast (feat-20260529-picking-raycasting-screen-to-entity M3 / w13).\n//\n// `pick(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight)`\n// is a free function (NOT `world.pick()`; requirements hard constraint): it unprojects\n// a viewport-relative screen coordinate into a world-space ray through the supplied\n// camera, walks every renderable archetype, ray-AABB tests each pickable mesh's\n// world-space bounding box, and returns the NEAREST `PickHit` (or `undefined` on miss).\n//\n// Mesh AABB source (feat-20260614 M8, D-15/D-18): the ray-AABB test needs each\n// mesh's local-space `MeshAsset.aabb`, resolved from the entity's `MeshFilter`\n// handle via `resolveAssetHandle<MeshAsset>(world, handle)` (two-tier builtin /\n// world.sharedRefs dispatch). The registry no longer holds handles, so `pick`\n// takes no `AssetRegistry` -- it resolves entirely World-side. Keeping `pick` a\n// free function (rather than a `World` method) preserves the layering: `World`\n// (engine-ecs) stays asset-free; the picking glue lives in the runtime package\n// alongside the renderer.\n//\n// Error channel split (charter P3): the single unrecoverable precondition —\n// `cameraEntity` carries no `Camera` — throws a structured `PickError`\n// (`code: 'camera-component-missing'`); the ordinary \"ray hit nothing\" outcome returns\n// `undefined`. AI users branch with `if (hit)` for the common case and only handle\n// `PickError` where they cannot guarantee the camera entity is well-formed.\n//\n// Transform source (feat-20260601 D-3): per entity (camera + candidates) read the\n// single resolved `Transform.world` mat4 written by `propagateTransforms` -- the\n// GlobalTransform/Transform fallback double-track is retired (the world column\n// always exists on a Transform-bearing entity). The camera view is\n// `mat4.invert(Transform.world)`; the candidate AABB is the local AABB\n// transformed by `Transform.world` directly. The world mat4 is read through the\n// M1 row-level access, zero `{}` materialization.\n//\n// Related: requirements in-scope #5/#6/#7 + AC-05..AC-11; plan-strategy D-3 / D-6 / 5.3;\n// research Finding 4 (local->world AABB) + Finding 5 (entity-id via query rows).\n\nimport { resolveAssetHandle } from '@forgeax/engine-assets-runtime';\nimport type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport { box3, ray, type Vec3Like, vec3 } from '@forgeax/engine-math';\nimport { MeshFilter, MeshRenderer } from '@forgeax/engine-render';\nimport { Transform } from '@forgeax/engine-scene';\nimport type { MeshAsset } from '@forgeax/engine-types';\nimport { toShared } from '@forgeax/engine-types';\nimport { computeScreenRay, readWorldMatrix } from './pick-core';\n\n/**\n * Result of a successful screen-to-entity pick.\n *\n * Minimal three-field surface (D-6):\n * - `entity` — the picked `Entity` (packed u32 handle, ready for `world.get` / `world.set`)\n * - `point` — the world-space ray/AABB entry point (`Vec3Like`; a 3-element array)\n * - `distance` — the entry distance along the ray from the camera (>= 0)\n *\n * No `face` / `uv` / `normal` fields: AABB picking has no triangle resolution, so those\n * would be a lie. A future mesh-precise pick spin-off owns them (requirements OOS).\n */\nexport interface PickHit {\n readonly entity: EntityHandle;\n readonly point: Vec3Like;\n readonly distance: number;\n}\n\n/**\n * Raycast from a viewport-relative screen coordinate into the world and return the\n * nearest pickable mesh entity whose world-space AABB the ray enters.\n *\n * @param world The ECS world holding the camera + candidate mesh entities (and the\n * per-World SharedRefStore that owns each `MeshAsset` and its local-space `aabb`).\n * @param cameraEntity The entity carrying the `Camera` component (and a Transform).\n * @param screenX Horizontal pixel coordinate relative to the viewport top-left (y-down).\n * @param screenY Vertical pixel coordinate.\n * @param viewportWidth Viewport width in pixels.\n * @param viewportHeight Viewport height in pixels.\n * @returns The nearest `PickHit`, or `undefined` when the ray hits nothing.\n * @throws {PickError} `code: 'camera-component-missing'` when `cameraEntity` has no `Camera`.\n */\nexport function pick(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): PickHit | undefined {\n // --- camera validation + view/projection + screen->world ray (pick-core skeleton) ---\n // Throws PickError('camera-component-missing') when cameraEntity has no Camera;\n // returns undefined when the camera has no resolvable Transform.world (degenerate miss).\n const screenRay = computeScreenRay(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n );\n if (screenRay === undefined) return undefined;\n const r = screenRay.ray;\n\n const query = world.query({ read: [Transform, MeshFilter, MeshRenderer] }).unwrap();\n\n const worldAabb = box3.create();\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestEntity: EntityHandle | undefined;\n\n for (const row of query) {\n const assetHandleRaw = Math.round(row.get(MeshFilter).assetHandle as number);\n if (assetHandleRaw === 0) continue;\n const meshRes = resolveAssetHandle<MeshAsset>(world, toShared<'MeshAsset'>(assetHandleRaw));\n if (!meshRes.ok) continue;\n const localAabb = meshRes.value.aabb;\n if (localAabb === undefined) continue;\n // Inverted-infinity empty box (mesh without positions): not pickable.\n if ((localAabb[0] as number) > (localAabb[3] as number)) continue;\n\n // read the packed Entity for this row from the essential id=0 Entity\n // column (`self` field); the column exists on every archetype.\n const entity = row.entity;\n\n // local AABB -> world AABB using the resolved Transform.world mat4\n // directly (feat-20260601 D-3: no compose from decomposed TRS).\n const entityWorld = readWorldMatrix(world, entity);\n if (entityWorld === undefined) continue;\n box3.transformBox3(\n worldAabb,\n localAabb,\n entityWorld as unknown as Parameters<typeof box3.transformBox3>[2],\n );\n\n const result = ray.rayAabbIntersects(r, worldAabb);\n if (result.hit && result.tmin < bestDistance) {\n bestDistance = result.tmin;\n bestEntity = entity;\n }\n }\n\n if (bestEntity === undefined) return undefined;\n\n // entry point = origin + direction * tmin\n const origin = vec3.create();\n const dir = vec3.create();\n ray.getOrigin(origin, r);\n ray.getDirection(dir, r);\n const point = vec3.create(\n (origin[0] as number) + (dir[0] as number) * bestDistance,\n (origin[1] as number) + (dir[1] as number) * bestDistance,\n (origin[2] as number) + (dir[2] as number) * bestDistance,\n );\n\n return { entity: bestEntity, point, distance: bestDistance };\n}\n","// pick-tile.ts - cell-level Tilemap query (feat-20260608 M0 baseline rebuild).\n//\n// `pickTile(world, tilemapEntity, worldX, worldY)` is a free function (not a\n// method on `World` -- charter F1 single-import barrel from\n// `@forgeax/engine-runtime`). It converts world-space coordinates into the\n// Tilemap's local cell grid, walks every TileLayer that ChildOf-s the\n// supplied tilemap, and returns the FIRST non-zero tile id seen when scanning\n// layers in DESCENDING `layerOrder` (highest layer drawn on top wins).\n//\n// charter mapping:\n// - P3 explicit failure as value: out-of-bounds and \"every layer empty\"\n// resolve to `Result.ok(null)` -- never throws or fires onError. Dead\n// handles and live entities without Tilemap are separate structural\n// failures; the `PickTileError` union is closed to two variants and stays\n// a runtime-only type (not exported through `@forgeax/engine-types`, since\n// pickTile is a runtime-only system).\n// - P4 consistent abstraction: matches the `pick(world, ...)` raycast\n// surface in pick.ts -- free function, world+entity input, structured\n// error union, hit-or-null return.\n// - F1 single-import: exported from `@forgeax/engine-picking`'s barrel.\n//\n// Anchors: requirements §integration-points (engine-runtime pickTile);\n// plan-tasks m0-t8; plan-strategy §D-5 file-by-file ECS API adaptation\n// (World-owned Query pattern from render-system-extract.ts).\n\nimport { Entity, type EntityHandle, type World } from '@forgeax/engine-ecs';\nimport { mat4, vec3 } from '@forgeax/engine-math';\nimport { TileLayer, Tilemap } from '@forgeax/engine-render/authoring';\nimport { ChildOf, Transform } from '@forgeax/engine-scene';\nimport { err, ok, type Result } from '@forgeax/engine-types';\n\n/**\n * Closed error union for `pickTile` (charter P3 + P4). Two variants cover the\n * structural-break paths; \"ray hit nothing\" is a value (`Result.ok(null)`),\n * not an error.\n */\nexport type PickTileError =\n | { readonly code: 'tilemap-not-found'; readonly tilemapEntity: EntityHandle }\n | { readonly code: 'tilemap-component-missing'; readonly tilemapEntity: EntityHandle };\n\n/**\n * Successful picking outcome. Returned through `Result.ok`; a `null` value\n * means the query landed in-bounds but every layer at that cell was empty\n * (or the point was outside the tilemap world bounds).\n */\nexport interface PickTileHit {\n readonly layerEntity: EntityHandle;\n readonly cellX: number;\n readonly cellY: number;\n readonly tileId: number;\n}\n\n/**\n * Find the topmost non-zero tile under `(worldX, worldY)` for a given\n * Tilemap entity, walking child TileLayer entities in DESCENDING\n * `layerOrder`.\n *\n * @returns\n * - `Result.ok(PickTileHit)` for a non-zero cell on some layer.\n * - `Result.ok(null)` for an empty cell or out-of-bounds query.\n * - `Result.err({ code: 'tilemap-not-found' })` for a dead handle.\n * - `Result.err({ code: 'tilemap-component-missing' })` for a live entity\n * without a Tilemap component.\n *\n * The caller must propagate the World so `Transform.world` is current. A\n * singular world transform follows `mat4.invert`'s deterministic identity\n * fallback, which keeps this error union structural rather than adding a\n * third diagnostic arm.\n */\nexport function pickTile(\n world: World,\n tilemapEntity: EntityHandle,\n worldX: number,\n worldY: number,\n): Result<PickTileHit | null, PickTileError> {\n const entityResult = world.get(tilemapEntity, Entity);\n if (!entityResult.ok) {\n return err({ code: 'tilemap-not-found', tilemapEntity });\n }\n\n const tilemapResult = world.get(tilemapEntity, Tilemap);\n if (!tilemapResult.ok) {\n return err({ code: 'tilemap-component-missing', tilemapEntity });\n }\n const tilemap = tilemapResult.value;\n const cols = tilemap.cols;\n const rows = tilemap.rows;\n // feat-20260709 M3: tileSize is one inline array<f32,2> column; the\n // world.get read path materialises it as a Float32Array ([width, height]).\n const tileSizeX = tilemap.tileSize[0] ?? 1;\n const tileSizeY = tilemap.tileSize[1] ?? 1;\n\n // Tilemap entities without a Transform retain the origin-default path. For\n // transformed maps, the full propagated affine inverse is the only correct\n // way to recover local cell coordinates under rotation and non-uniform scale.\n let localX = worldX;\n let localY = worldY;\n const transformResult = world.get(tilemapEntity, Transform);\n if (transformResult.ok) {\n const w = transformResult.value.world;\n if (w !== undefined && w.length >= 16) {\n const local = mat4.transformPoint(vec3.create(), mat4.invert(mat4.create(), w), [\n worldX,\n worldY,\n 0,\n ]);\n localX = local[0] ?? 0;\n localY = local[1] ?? 0;\n }\n }\n\n if (tileSizeX <= 0 || tileSizeY <= 0) return ok(null);\n if (localX < 0 || localY < 0) return ok(null);\n\n const cellX = Math.floor(localX / tileSizeX);\n const cellY = Math.floor(localY / tileSizeY);\n if (cellX < 0 || cellY < 0 || cellX >= cols || cellY >= rows) return ok(null);\n\n // Collect every TileLayer ChildOf-ing the supplied Tilemap entity, with\n // its layerOrder + tile array snapshot through one row iterator.\n type LayerInfo = {\n readonly entity: EntityHandle;\n readonly tiles: ArrayLike<number>;\n readonly layerOrder: number;\n };\n const layers: LayerInfo[] = [];\n const layerQuery = world.query({ read: [TileLayer, ChildOf] }).unwrap();\n for (const row of layerQuery) {\n const layerEntity = row.entity;\n const parent = row.get(ChildOf).parent;\n if ((parent as unknown as number) !== (tilemapEntity as unknown as number)) continue;\n const layerData = world.get(layerEntity, TileLayer);\n if (!layerData.ok) continue;\n layers.push({\n entity: layerEntity,\n tiles: layerData.value.tiles as ArrayLike<number>,\n layerOrder: row.get(TileLayer).layerOrder,\n });\n }\n\n layers.sort((a, b) => b.layerOrder - a.layerOrder);\n\n const cellIndex = cellY * cols + cellX;\n for (const layer of layers) {\n if (cellIndex >= layer.tiles.length) continue;\n const tileId = layer.tiles[cellIndex] ?? 0;\n if (tileId !== 0) {\n return ok({\n layerEntity: layer.entity,\n cellX,\n cellY,\n tileId,\n });\n }\n }\n return ok(null);\n}\n","// pick-vertex.ts — per-entity + full-scene vertex-level picking (feat-20260630-vertex-snapping-picking).\n//\n// `pickVertexOnEntity(world, cameraEntity, screenX, screenY, vpW, vpH, entity, options?)`\n// returns the vertex(es) on a single entity nearest to the supplied screen coordinate.\n//\n// `pickVertex(world, cameraEntity, screenX, screenY, vpW, vpH, options?)`\n// walks all renderable archetypes (AABB coarse cull), calls pickVertexOnEntity on each\n// ray-intersecting entity, and returns the globally-nearest vertex hits sorted by screenDist.\n//\n// M2 scope (w5): single-entity query with overloaded three-state return (D-2).\n// M3 scope (w8): degradation branches (D-4/D-5/AC-07/R-3) + pickVertex full-scene (R-2).\n//\n// Overload contract:\n// - No options / no limit → VertexHit | undefined\n// - { limit: N } → VertexHit[]\n//\n// Reuses pick()'s full skeleton (camera validation / view=invert(world) / projection branch /\n// screenToRay / resolveAssetHandle / Transform.world / AABB coarse cull), then for each\n// triangle-list submesh that passes the AABB test, iterates every triangle and collects\n// vertex candidates via rayTriangleIntersects.\n//\n// Key design points:\n// - D-7: worldPos uses Vec3Like (not branded Vec3), mirroring PickHit.point.\n// - D-8: reuses PickError with ZERO new error codes — the sole throw remains\n// camera-component-missing; all other miss conditions return undefined / [].\n// - D-9: caller MUST propagateTransforms(world) before calling — the function reads\n// Transform.world directly.\n// - AC-08: deformed = isSkinned (attributes.skinIndex && attributes.skinWeight both\n// defined, SSOT at asset-registry.ts:1417-1418).\n// - R-3: behind-camera vertices (worldToScreen returns behind=true) are excluded.\n// - D-4: position three-branch narrow (Float32Array → use; ArrayBuffer → new Float32Array;\n// Uint16Array / undefined → skip mesh), mirrors computeAABB asset-registry.ts:1898-1929.\n// - D-5: only triangle-list topology participates; triangle-strip / line-list /\n// line-strip / point-list → skip submesh.\n// - AC-07: builtin mesh with aabb===undefined → fallback to walk-all-vertices\n// (not continue like pick.ts:184).\n//\n// Related: requirements AC-01/AC-02/AC-03/AC-04/AC-05/AC-07/AC-08/AC-09/AC-10/AC-13;\n// plan-strategy D-2/D-3/D-4/D-5/D-7/D-8/D-9 §4 R-2/R-3;\n// research Finding 1 (reuse pick skeleton) / Finding 2 (PickError) /\n// Finding 4 (computeAABB three-branch) / Finding 5 (builtin withoutAabb) /\n// Finding 6 (non-indexed sequence) / Finding 8 (Transform.world) /\n// Finding 9 (behind flag) / Finding 10 (isSkinned);\n// plan-tasks.json w5/w8 acceptanceCheck.\n\nimport { resolveAssetHandle } from '@forgeax/engine-assets-runtime';\nimport type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport { mat4, ray, type Vec3Like, vec2, vec3 } from '@forgeax/engine-math';\nimport { MeshFilter, MeshRenderer } from '@forgeax/engine-render';\nimport { Transform } from '@forgeax/engine-scene';\nimport type { MeshAsset } from '@forgeax/engine-types';\nimport { toShared } from '@forgeax/engine-types';\nimport { computeScreenRay, readWorldMatrix } from './pick-core';\n\n// ── types ────────────────────────────────────────────────────────────────\n\n/**\n * Result of a successful vertex pick: the mesh vertex nearest to the\n * supplied screen-space coordinate.\n *\n * Field set:\n * - `entity` — the entity this vertex belongs to.\n * - `vertexIndex` — index into the vertex position buffer (0-based).\n * - `worldPos` — world-space position of the vertex (Vec3Like; rest-pose when\n * deformed=true). D-7: Vec3Like avoids math brand cast lint in\n * the runtime package.\n * - `screenDist` — screen-space pixel distance from the query coordinate to the\n * projected vertex position (non-negative).\n * - `worldDist` — perpendicular 3D distance from the vertex world position to\n * the pick ray (non-negative). Orthogonal counterpart to screenDist.\n * - `deformed` — true when the mesh is skinned (skinIndex + skinWeight attributes\n * both present), indicating worldPos reflects rest-pose, not GPU\n * skinning output.\n */\nexport interface VertexHit {\n readonly entity: EntityHandle;\n readonly vertexIndex: number;\n readonly worldPos: Vec3Like;\n readonly screenDist: number;\n readonly worldDist: number;\n readonly deformed: boolean;\n}\n\n// ── helpers ──────────────────────────────────────────────────────────────\n\n/** Scratch Vec2 for worldToScreen calls (reused across invocations). */\nconst _scratchVec2 = vec2.create();\n\n/**\n * Compute the perpendicular distance from a point to a ray in 3D.\n * rayDir is assumed normalized; returns |(P - O) x D|.\n */\nfunction pointToRayDist(\n px: number,\n py: number,\n pz: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n): number {\n const ex = px - ox;\n const ey = py - oy;\n const ez = pz - oz;\n const cx = ey * dz - ez * dy;\n const cy = ez * dx - ex * dz;\n const cz = ex * dy - ey * dx;\n return Math.sqrt(cx * cx + cy * cy + cz * cz);\n}\n\n/**\n * Test whether a ray intersects a local-space AABB transformed by a world matrix.\n * Returns false when the ray misses the box; returns true when the box is\n * inverted (empty) — caller falls through to walk-all-vertices (AC-07).\n *\n * Extracted from collectVertexHits and pickVertex (review I-3, ~48 lines x2).\n */\nfunction rayHitsWorldAabb(\n r: Float32Array,\n localAabb: Float32Array,\n worldMat: mat4.Mat4Like,\n): boolean {\n // skip inverted-infinity empty box — fall through to walk-all-vertices\n if ((localAabb[0] as number) > (localAabb[3] as number)) return true;\n\n const corners = [\n [localAabb[0] as number, localAabb[1] as number, localAabb[2] as number],\n [localAabb[3] as number, localAabb[1] as number, localAabb[2] as number],\n [localAabb[0] as number, localAabb[4] as number, localAabb[2] as number],\n [localAabb[3] as number, localAabb[4] as number, localAabb[2] as number],\n [localAabb[0] as number, localAabb[1] as number, localAabb[5] as number],\n [localAabb[3] as number, localAabb[1] as number, localAabb[5] as number],\n [localAabb[0] as number, localAabb[4] as number, localAabb[5] as number],\n [localAabb[3] as number, localAabb[4] as number, localAabb[5] as number],\n ];\n let minX = Infinity,\n minY = Infinity,\n minZ = Infinity;\n let maxX = -Infinity,\n maxY = -Infinity,\n maxZ = -Infinity;\n const tmpP = vec3.create();\n for (const c of corners) {\n mat4.transformPoint(tmpP, worldMat, c as unknown as Vec3Like);\n const cx = tmpP[0] as number;\n const cy = tmpP[1] as number;\n const cz = tmpP[2] as number;\n if (cx < minX) minX = cx;\n if (cy < minY) minY = cy;\n if (cz < minZ) minZ = cz;\n if (cx > maxX) maxX = cx;\n if (cy > maxY) maxY = cy;\n if (cz > maxZ) maxZ = cz;\n }\n const worldAabb = new Float32Array(6);\n worldAabb[0] = minX;\n worldAabb[1] = minY;\n worldAabb[2] = minZ;\n worldAabb[3] = maxX;\n worldAabb[4] = maxY;\n worldAabb[5] = maxZ;\n\n const aabbResult = ray.rayAabbIntersects(\n r,\n worldAabb as unknown as import('@forgeax/engine-math').box3.Box3Like,\n );\n return aabbResult.hit;\n}\n\n/**\n * Narrow the position attribute per the three-branch contract (D-4),\n * mirrors computeAABB asset-registry.ts:1898-1908.\n * Returns the Float32Array position data, or undefined when the position\n * is Uint16Array / undefined / too short (skip this mesh).\n */\nfunction narrowPosition(\n positionAttr: ArrayBuffer | Float32Array | Uint16Array | undefined,\n): Float32Array | undefined {\n if (positionAttr instanceof Float32Array) {\n return positionAttr;\n }\n if (positionAttr instanceof ArrayBuffer) {\n return new Float32Array(positionAttr);\n }\n // Uint16Array or undefined → skip (no usable float-coordinate vertex data)\n return undefined;\n}\n\n// ── internal: per-entity vertex hit collection (extracted for pickVertex reuse) ──\n\n/**\n * Core per-entity vertex hit collection.\n * Returns all vertex candidates for one entity, unsorted.\n * Caller sorts and applies limit.\n */\nfunction collectVertexHits(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n): VertexHit[] {\n // ── camera validation + view/projection + screen->world ray (pick-core skeleton) ──\n // Throws PickError('camera-component-missing') when cameraEntity has no Camera;\n // returns undefined when the camera has no resolvable Transform.world (D-9 preamble miss).\n const screenRay = computeScreenRay(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n );\n if (screenRay === undefined) {\n return [];\n }\n const { ray: r, view, proj } = screenRay;\n\n // ── viewProj = proj * view (precompute for worldToScreen calls) ──\n const viewProj = mat4.create();\n mat4.multiply(viewProj, proj as unknown as mat4.Mat4Like, view as unknown as mat4.Mat4Like);\n\n const rOx = r[0] as number;\n const rOy = r[1] as number;\n const rOz = r[2] as number;\n const rDx = r[3] as number;\n const rDy = r[4] as number;\n const rDz = r[5] as number;\n\n // ── resolve entity's mesh asset ──\n const mfRes = world.get(entity, MeshFilter);\n if (!mfRes.ok) {\n return [];\n }\n const meshRes = resolveAssetHandle<MeshAsset>(\n world,\n toShared<'MeshAsset'>(mfRes.value.assetHandle as unknown as number),\n );\n if (!meshRes.ok) {\n return [];\n }\n const mesh = meshRes.value;\n\n // ── position attribute narrow (D-4: three-branch, mirrors computeAABB) ──\n const positions = narrowPosition(mesh.attributes.position);\n if (positions === undefined || positions.length < 3) {\n return [];\n }\n\n // ── entity world transform (D-9: requires propagateTransforms preamble) ──\n const entityWorld = readWorldMatrix(world, entity);\n if (entityWorld === undefined) {\n return [];\n }\n\n // ── deformed flag (AC-08: isSkinned, SSOT at asset-registry.ts:1417-1418) ──\n const attrs = mesh.attributes;\n const deformed: boolean =\n attrs !== undefined && attrs.skinIndex !== undefined && attrs.skinWeight !== undefined;\n\n // ── AABB coarse cull (if aabb present) ──\n // AC-07: if aabb is undefined, DO NOT continue — fallthrough to walk-all-vertices.\n // This is the key behavioral difference from pick.ts:184.\n const entityWMLike = entityWorld as unknown as mat4.Mat4Like;\n\n if (mesh.aabb !== undefined && !rayHitsWorldAabb(r, mesh.aabb, entityWMLike)) {\n return [];\n }\n // AC-07: aabb===undefined → fall through to walk-all-vertices (builtin no-AABB fallback).\n\n // ── iterate submeshes + triangles ──\n const candidates: VertexHit[] = [];\n const seen = new Set<number>(); // (entity, vertexIndex) dedup (review I-1)\n const indices = mesh.indices;\n const submeshes = mesh.submeshes;\n const maxVertexIndex = Math.floor(positions.length / 3) - 1;\n\n // Shared vertex-emit closure: given 3 vertex indices for a hit triangle,\n // compute worldPos + screenDist + worldDist and push candidates.\n // Extracted from the duplicated ~45-line indexed/non-indexed body (review I-2).\n const emitTriangleVertices = (i0: number, i1: number, i2: number): void => {\n const ax = positions[i0 * 3 + 0] as number;\n const ay = positions[i0 * 3 + 1] as number;\n const az = positions[i0 * 3 + 2] as number;\n const bx = positions[i1 * 3 + 0] as number;\n const by = positions[i1 * 3 + 1] as number;\n const bz = positions[i1 * 3 + 2] as number;\n const cx = positions[i2 * 3 + 0] as number;\n const cy = positions[i2 * 3 + 1] as number;\n const cz = positions[i2 * 3 + 2] as number;\n\n const triResult = ray.rayTriangleIntersects(\n r,\n [ax, ay, az] as unknown as Vec3Like,\n [bx, by, bz] as unknown as Vec3Like,\n [cx, cy, cz] as unknown as Vec3Like,\n );\n\n if (!triResult.hit) return;\n\n for (const [vi, lx, ly, lz] of [\n [i0, ax, ay, az],\n [i1, bx, by, bz],\n [i2, cx, cy, cz],\n ] as [number, number, number, number][]) {\n if (Number.isNaN(lx) || Number.isNaN(ly) || Number.isNaN(lz)) continue;\n if (!Number.isFinite(lx) || !Number.isFinite(ly) || !Number.isFinite(lz)) continue;\n\n const worldVec = vec3.create();\n mat4.transformPoint(worldVec, entityWMLike, [lx, ly, lz] as unknown as Vec3Like);\n const wx = worldVec[0] as number;\n const wy = worldVec[1] as number;\n const wz = worldVec[2] as number;\n\n const screenRes = ray.worldToScreen(\n _scratchVec2,\n [wx, wy, wz] as unknown as Vec3Like,\n viewProj as unknown as import('@forgeax/engine-math').Mat4Like,\n viewportWidth,\n viewportHeight,\n );\n\n if (screenRes.behind) continue;\n\n // Dedup: same vertex hit by multiple triangles — keep only first\n // (same vertexIndex = same world-space position, screenDist/worldDist identical).\n if (seen.has(vi)) continue;\n seen.add(vi);\n\n const sx = _scratchVec2[0] as number;\n const sy = _scratchVec2[1] as number;\n const sdx = sx - screenX;\n const sdy = sy - screenY;\n const screenDist = Math.sqrt(sdx * sdx + sdy * sdy);\n const worldDist = pointToRayDist(wx, wy, wz, rOx, rOy, rOz, rDx, rDy, rDz);\n\n candidates.push({\n entity,\n vertexIndex: vi,\n worldPos: [wx, wy, wz] as unknown as Vec3Like,\n screenDist,\n worldDist,\n deformed,\n });\n }\n };\n\n for (const submesh of submeshes) {\n // D-5: only triangle-list participates\n if (submesh.topology !== 'triangle-list') continue;\n\n const idxOffset = submesh.indexOffset;\n const idxCount = submesh.indexCount;\n\n if (indices !== undefined && indices.length > 0 && idxCount > 0) {\n // indexed draw\n const triCount = Math.floor(idxCount / 3);\n for (let ti = 0; ti < triCount; ti++) {\n const i0 = indices[idxOffset + ti * 3 + 0] as number;\n const i1 = indices[idxOffset + ti * 3 + 1] as number;\n const i2 = indices[idxOffset + ti * 3 + 2] as number;\n\n if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;\n emitTriangleVertices(i0, i1, i2);\n }\n } else {\n // AC-09: no index buffer → non-indexed triangle sequence\n const submeshVertexCount = submesh.vertexCount;\n const triCount = Math.floor(submeshVertexCount / 3);\n\n for (let ti = 0; ti < triCount; ti++) {\n const i0 = ti * 3;\n const i1 = ti * 3 + 1;\n const i2 = ti * 3 + 2;\n\n if (i0 > maxVertexIndex || i1 > maxVertexIndex || i2 > maxVertexIndex) continue;\n emitTriangleVertices(i0, i1, i2);\n }\n }\n }\n\n return candidates;\n}\n\n// ── overload signatures: pickVertexOnEntity (D-2: three-state static dispatch) ──\n\n/**\n * Query the nearest vertex on a single entity.\n *\n * Without options: returns `VertexHit | undefined` (nearest hit, or `undefined` on miss).\n *\n * @param world The ECS world (propagateTransforms must have been called this frame).\n * @param cameraEntity Entity carrying the Camera component (and Transform).\n * @param screenX Horizontal pixel coordinate (viewport top-left, y-down).\n * @param screenY Vertical pixel coordinate.\n * @param viewportWidth Viewport width in pixels.\n * @param viewportHeight Viewport height in pixels.\n * @param entity The mesh entity to query (must carry MeshFilter + MeshRenderer).\n * @returns The nearest `VertexHit`, or `undefined` when nothing is hit.\n * @throws {PickError} `code: 'camera-component-missing'` when cameraEntity has no Camera.\n */\nexport function pickVertexOnEntity(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n): VertexHit | undefined;\n\n/**\n * Query up to `limit` nearest vertices on a single entity.\n *\n * With `{ limit }`: returns `VertexHit[]` sorted by `screenDist` ascending.\n *\n * @param options.limit Maximum number of candidates to return (returns all available\n * vertices when limit exceeds the hit count).\n * @returns Sorted array of `VertexHit` (empty on miss).\n */\nexport function pickVertexOnEntity(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n options: { limit: number },\n): VertexHit[];\n\nexport function pickVertexOnEntity(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n entity: EntityHandle,\n options?: { limit: number },\n): VertexHit | VertexHit[] | undefined {\n const candidates = collectVertexHits(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n entity,\n );\n\n // ── sort by screenDist ascending ──\n candidates.sort((a, b) => a.screenDist - b.screenDist);\n\n // ── apply limit / return shape ──\n const limit = options?.limit;\n if (limit !== undefined) {\n return candidates.slice(0, limit);\n }\n return candidates[0];\n}\n\n// ── overload signatures: pickVertex (full-scene, D-2: three-state static dispatch) ──\n\n/**\n * Query the nearest vertex across all pickable mesh entities in the world.\n *\n * Without options: returns `VertexHit | undefined` (globally nearest, or `undefined` on miss).\n *\n * Walks all renderable archetypes, does an AABB coarse cull (R-2), then calls\n * `pickVertexOnEntity` on each ray-intersecting entity. Builtin meshes without AABB\n * fall through to walk-all-vertices (AC-07).\n *\n * @param world The ECS world (propagateTransforms must have been called this frame).\n * @param cameraEntity Entity carrying the Camera component (and Transform).\n * @param screenX Horizontal pixel coordinate (viewport top-left, y-down).\n * @param screenY Vertical pixel coordinate.\n * @param viewportWidth Viewport width in pixels.\n * @param viewportHeight Viewport height in pixels.\n * @returns The globally nearest `VertexHit`, or `undefined` when nothing is hit.\n * @throws {PickError} `code: 'camera-component-missing'` when cameraEntity has no Camera.\n */\nexport function pickVertex(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): VertexHit | undefined;\n\n/**\n * Query up to `limit` nearest vertices across all pickable mesh entities.\n *\n * With `{ limit }`: returns `VertexHit[]` globally sorted by `screenDist` ascending.\n *\n * @param options.limit Maximum number of candidates to return.\n * @returns Sorted array of `VertexHit` (empty on miss).\n */\nexport function pickVertex(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n options: { limit: number },\n): VertexHit[];\n\nexport function pickVertex(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n options?: { limit: number },\n): VertexHit | VertexHit[] | undefined {\n // ── camera validation + view/projection + screen->world ray (pick-core skeleton) ──\n // Throws PickError('camera-component-missing') when cameraEntity has no Camera;\n // returns undefined when the camera has no resolvable Transform.world (degenerate miss).\n const screenRay = computeScreenRay(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n );\n if (screenRay === undefined) {\n if (options) return [];\n return undefined;\n }\n const r = screenRay.ray;\n\n // ── walk renderable archetypes (Transform + MeshFilter + MeshRenderer) ──\n // Reuse pick.ts archetype walk skeleton (research Finding 1).\n const query = world.query({ read: [Transform, MeshFilter, MeshRenderer] }).unwrap();\n\n const allCandidates: VertexHit[] = [];\n\n for (const row of query) {\n const assetHandleRaw = Math.round(row.get(MeshFilter).assetHandle as number);\n if (assetHandleRaw === 0) continue;\n const meshRes = resolveAssetHandle<MeshAsset>(world, toShared<'MeshAsset'>(assetHandleRaw));\n if (!meshRes.ok) continue;\n\n // read entity (mirrors pick.ts:190)\n const entity = row.entity;\n\n // read entity world matrix\n const entityWorld = readWorldMatrix(world, entity);\n if (entityWorld === undefined) continue;\n\n // AABB coarse cull (R-2): if aabb present, test ray intersection.\n // If aabb===undefined (builtin), fall through to collectVertexHits (AC-07).\n const mesh = meshRes.value;\n if (\n mesh.aabb !== undefined &&\n !rayHitsWorldAabb(r, mesh.aabb, entityWorld as unknown as mat4.Mat4Like)\n ) {\n continue;\n }\n\n // Collect vertices for this entity\n const entityHits = collectVertexHits(\n world,\n cameraEntity,\n screenX,\n screenY,\n viewportWidth,\n viewportHeight,\n entity,\n );\n for (const h of entityHits) {\n allCandidates.push(h);\n }\n }\n\n // ── global sort by screenDist ascending ──\n allCandidates.sort((a, b) => a.screenDist - b.screenDist);\n\n // ── apply limit / return shape ──\n const limit = options?.limit;\n if (limit !== undefined) {\n return allCandidates.slice(0, limit);\n }\n return allCandidates[0];\n}\n","import type { EntityHandle, World } from '@forgeax/engine-ecs';\nimport type { ray } from '@forgeax/engine-math';\nimport { computeScreenRay } from './pick-core';\n\n/**\n * Unproject a viewport pixel into a world-space ray.\n *\n * `screenX`/`screenY` use the canvas convention: top-left origin, y down.\n * The caller must run `propagateTransforms(world)` for the current frame first.\n * A camera without a resolvable Transform returns `undefined`; a missing\n * Camera component throws the package's structured `PickError`.\n */\nexport function viewportToWorld(\n world: World,\n cameraEntity: EntityHandle,\n screenX: number,\n screenY: number,\n viewportWidth: number,\n viewportHeight: number,\n): ray.Ray | undefined {\n return computeScreenRay(world, cameraEntity, screenX, screenY, viewportWidth, viewportHeight)\n ?.ray;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pick-vertex.d.ts","sourceRoot":"","sources":["../src/pick-vertex.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pick-vertex.d.ts","sourceRoot":"","sources":["../src/pick-vertex.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAa,KAAK,QAAQ,EAAc,MAAM,sBAAsB,CAAC;AAS5E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAqTD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,YAAY,GACnB,SAAS,GAAG,SAAS,CAAC;AAEzB;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzB,SAAS,EAAE,CAAC;AAmCf;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,SAAS,GAAG,SAAS,CAAC;AAEzB;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzB,SAAS,EAAE,CAAC"}
|
package/dist/pick.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pick.d.ts","sourceRoot":"","sources":["../src/pick.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pick.d.ts","sourceRoot":"","sources":["../src/pick.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAa,KAAK,QAAQ,EAAQ,MAAM,sBAAsB,CAAC;AAOtE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,IAAI,CAClB,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,GAAG,SAAS,CAkErB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-picking",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-
|
|
26
|
-
"@forgeax/engine-
|
|
27
|
-
"@forgeax/engine-
|
|
28
|
-
"@forgeax/engine-
|
|
29
|
-
"@forgeax/engine-
|
|
30
|
-
"@forgeax/engine-
|
|
25
|
+
"@forgeax/engine-assets-runtime": "0.1.6",
|
|
26
|
+
"@forgeax/engine-ecs": "0.1.6",
|
|
27
|
+
"@forgeax/engine-math": "0.1.6",
|
|
28
|
+
"@forgeax/engine-render": "0.1.6",
|
|
29
|
+
"@forgeax/engine-runtime": "0.1.6",
|
|
30
|
+
"@forgeax/engine-scene": "0.1.6",
|
|
31
|
+
"@forgeax/engine-types": "0.1.6"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@forgeax/engine-
|
|
34
|
-
"@forgeax/engine-
|
|
35
|
-
"@forgeax/engine-shader": "0.1.4"
|
|
34
|
+
"@forgeax/engine-pack": "0.1.6",
|
|
35
|
+
"@forgeax/engine-shader": "0.1.6"
|
|
36
36
|
},
|
|
37
37
|
"forgeax": {
|
|
38
38
|
"metrics": {
|
|
@@ -8,20 +8,20 @@
|
|
|
8
8
|
|
|
9
9
|
import { type EntityHandle, World } from '@forgeax/engine-ecs';
|
|
10
10
|
import { CAMERA_PROJECTION_PERSPECTIVE, Camera } from '@forgeax/engine-render';
|
|
11
|
+
import { GlyphText } from '@forgeax/engine-render/authoring';
|
|
12
|
+
import { propagateTransforms, Transform } from '@forgeax/engine-scene';
|
|
13
|
+
import type { FontAsset, GlyphMetric, Handle } from '@forgeax/engine-types';
|
|
14
|
+
import { describe, expect, it } from 'vitest';
|
|
15
|
+
import { GpuResidencyCache } from '../../../render/src/device/gpu-residency';
|
|
11
16
|
import {
|
|
12
|
-
GlyphText,
|
|
13
17
|
glyphTextLayoutSystem,
|
|
14
18
|
resetGlyphBakeCache,
|
|
15
|
-
} from '
|
|
16
|
-
import { GpuResourceStore } from '@forgeax/engine-render/internal';
|
|
17
|
-
import { Transform } from '@forgeax/engine-scene';
|
|
18
|
-
import type { FontAsset, GlyphMetric, Handle } from '@forgeax/engine-types';
|
|
19
|
-
import { describe, expect, it } from 'vitest';
|
|
19
|
+
} from '../../../render/src/glyph-text-layout-system';
|
|
20
20
|
import { pick } from '../pick';
|
|
21
21
|
|
|
22
22
|
describe('glyph-text-pick', () => {
|
|
23
23
|
// Pick tests do not wire a GPU device; the residency store stays CPU-only.
|
|
24
|
-
const gpuStore = new
|
|
24
|
+
const gpuStore = new GpuResidencyCache();
|
|
25
25
|
|
|
26
26
|
const VP = 600;
|
|
27
27
|
|
|
@@ -122,6 +122,7 @@ describe('glyph-text-pick', () => {
|
|
|
122
122
|
const label = spawnLabel(world, fontId);
|
|
123
123
|
|
|
124
124
|
glyphTextLayoutSystem(world, gpuStore); // bake + attach MeshFilter + MeshRenderer
|
|
125
|
+
propagateTransforms(world);
|
|
125
126
|
|
|
126
127
|
const hit = pick(world, camera, VP / 2, VP / 2, VP, VP);
|
|
127
128
|
expect(hit).toBeDefined();
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Shared test helper: minimal mock ShaderRegistry for AssetRegistry constructor.
|
|
2
|
+
// feat-20260527-material-registration-unification M1 / w4.
|
|
3
|
+
//
|
|
4
|
+
// All tests that create AssetRegistry directly must pass a ShaderRegistry
|
|
5
|
+
// since the constructor parameter became required in w1. This helper
|
|
6
|
+
// provides a zero-config mock that satisfies the constructor signature
|
|
7
|
+
// without requiring a real GPU device.
|
|
8
|
+
|
|
9
|
+
import { ShaderRegistry, type ShaderRegistryDevice } from '@forgeax/engine-shader';
|
|
10
|
+
|
|
11
|
+
export function makeMockShaderRegistry(): ShaderRegistry {
|
|
12
|
+
const mockDevice: ShaderRegistryDevice = {
|
|
13
|
+
createShaderModule() {
|
|
14
|
+
return {
|
|
15
|
+
ok: true,
|
|
16
|
+
value: undefined,
|
|
17
|
+
unwrap: () => undefined,
|
|
18
|
+
unwrapOr: (d: unknown) => d,
|
|
19
|
+
} as unknown as ReturnType<ShaderRegistryDevice['createShaderModule']>;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const sr = new ShaderRegistry({
|
|
23
|
+
device: mockDevice,
|
|
24
|
+
manifestUrl: undefined,
|
|
25
|
+
});
|
|
26
|
+
// Register a default test shader so M2 tests can validate against it.
|
|
27
|
+
sr.installMaterialArtifact('test::standard', {
|
|
28
|
+
source: 'fn main() {}',
|
|
29
|
+
paramSchema: [
|
|
30
|
+
{ name: 'baseColor', type: 'color' },
|
|
31
|
+
{ name: 'metallic', type: 'f32' },
|
|
32
|
+
{ name: 'roughness', type: 'f32' },
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
sr.installMaterialArtifact('test::unlit', {
|
|
36
|
+
source: 'fn main() {}',
|
|
37
|
+
paramSchema: [
|
|
38
|
+
{ name: 'baseColor', type: 'color' },
|
|
39
|
+
{ name: 'baseColorTexture', type: 'texture2d' },
|
|
40
|
+
{ name: 'sampler', type: 'sampler', default: null },
|
|
41
|
+
{ name: 'lightIntensity', type: 'f32' }, // required — no default
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
// Register the engine-shipped default-standard-pbr shader so tests
|
|
45
|
+
// that use it pass the register-time validation gate (M2 / w6).
|
|
46
|
+
// paramSchema mirrors packages/shader/src/default-standard-pbr.schema.json.
|
|
47
|
+
sr.installMaterialArtifact('forgeax::default-standard-pbr', {
|
|
48
|
+
source: 'fn main() {}',
|
|
49
|
+
paramSchema: [
|
|
50
|
+
{ name: 'baseColor', type: 'color', default: [1.0, 1.0, 1.0, 1.0] },
|
|
51
|
+
{ name: 'metallic', type: 'f32', default: 0.0 },
|
|
52
|
+
{ name: 'roughness', type: 'f32', default: 0.5 },
|
|
53
|
+
// feat-20260613 fix-issue-1 (D-8): channelMap split into 4 f32 selectors.
|
|
54
|
+
{ name: 'metallicChannel', type: 'f32', default: 2.0 },
|
|
55
|
+
{ name: 'roughnessChannel', type: 'f32', default: 1.0 },
|
|
56
|
+
{ name: 'aoChannel', type: 'f32', default: 0.0 },
|
|
57
|
+
{ name: 'extraChannel', type: 'f32', default: 0.0 },
|
|
58
|
+
{ name: 'emissive', type: 'vec3', default: [0.0, 0.0, 0.0] },
|
|
59
|
+
{ name: 'emissiveIntensity', type: 'f32', default: 0.0 },
|
|
60
|
+
{ name: 'occlusionStrength', type: 'f32', default: 1.0 },
|
|
61
|
+
{ name: 'baseColorTexture', type: 'texture2d' },
|
|
62
|
+
{ name: 'metallicRoughnessTexture', type: 'texture2d' },
|
|
63
|
+
{ name: 'normalTexture', type: 'texture2d' },
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
sr.installMaterialArtifact('forgeax::default-unlit', {
|
|
67
|
+
source: 'fn main() {}',
|
|
68
|
+
paramSchema: [
|
|
69
|
+
{ name: 'baseColor', type: 'color' },
|
|
70
|
+
{ name: 'baseColorTexture', type: 'texture2d' },
|
|
71
|
+
{ name: 'sampler', type: 'sampler', default: null },
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
// test::dummy used by M1 test (c)
|
|
75
|
+
sr.installMaterialArtifact('test::dummy', {
|
|
76
|
+
source: 'fn main() {}',
|
|
77
|
+
paramSchema: [],
|
|
78
|
+
});
|
|
79
|
+
// forgeax::msdf-text used by the glyph layout system (feat-20260531 F-1).
|
|
80
|
+
// paramSchema mirrors packages/shader/src/msdf-text.material.json so the
|
|
81
|
+
// layout system's per-font MaterialAsset register passes validation in the
|
|
82
|
+
// GPU-less unit tests.
|
|
83
|
+
sr.installMaterialArtifact('forgeax::msdf-text', {
|
|
84
|
+
source: 'fn main() {}',
|
|
85
|
+
paramSchema: [
|
|
86
|
+
{ name: 'tintColor', type: 'color', default: [1.0, 1.0, 1.0, 1.0] },
|
|
87
|
+
{ name: 'distanceRange', type: 'f32', default: 4.0 },
|
|
88
|
+
{ name: 'baseColorTexture', type: 'texture2d' },
|
|
89
|
+
{ name: 'sampler', type: 'sampler', default: null },
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
// feat-20260609-pipeline-driven-pass-selector-shadowcaster-via-mat M3 / T-007:
|
|
93
|
+
// forgeax::default-shadow-caster — vertex-only depth pass shader for
|
|
94
|
+
// directional shadow maps. Registered as the 6th built-in material shader.
|
|
95
|
+
sr.installMaterialArtifact('forgeax::default-shadow-caster', {
|
|
96
|
+
source: 'fn main() {}',
|
|
97
|
+
paramSchema: [],
|
|
98
|
+
});
|
|
99
|
+
// feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild:
|
|
100
|
+
// forgeax::sprite registered in the mock so resolveTilesetMaterial inside
|
|
101
|
+
// tilemap-chunk-extract-system can register a per-tile material in unit
|
|
102
|
+
// tests (paramSchema mirrors packages/shader/src/sprite.material.json).
|
|
103
|
+
sr.installMaterialArtifact('forgeax::sprite', {
|
|
104
|
+
source: 'fn main() {}',
|
|
105
|
+
paramSchema: [
|
|
106
|
+
{ name: 'baseColor', type: 'color', default: [1.0, 1.0, 1.0, 1.0] },
|
|
107
|
+
{ name: 'texture', type: 'texture2d' },
|
|
108
|
+
{ name: 'sampler', type: 'sampler', default: null },
|
|
109
|
+
{ name: 'region', type: 'vec4', default: [0.0, 0.0, 1.0, 1.0] },
|
|
110
|
+
{ name: 'pivot', type: 'vec2', default: [0.5, 0.5] },
|
|
111
|
+
{ name: 'flipX', type: 'f32', default: 0.0 },
|
|
112
|
+
{ name: 'flipY', type: 'f32', default: 0.0 },
|
|
113
|
+
{ name: 'slices', type: 'vec4', default: [0.0, 0.0, 0.0, 0.0] },
|
|
114
|
+
{ name: 'sliceMode', type: 'f32', default: 0.0 },
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
return sr;
|
|
118
|
+
}
|