lecodes-sdk 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/global.d.ts +503 -0
- package/dist/host.d.ts +61 -0
- package/dist/inject.js +4730 -0
- package/dist/types/animate/animate.d.ts +20 -0
- package/dist/types/animate/bezier.d.ts +2 -0
- package/dist/types/animate/easings.d.ts +21 -0
- package/dist/types/canvas/Canvas.d.ts +168 -0
- package/dist/types/core/Aspect.d.ts +171 -0
- package/dist/types/core/InspectorUI.d.ts +88 -0
- package/dist/types/core/StateMachine.d.ts +81 -0
- package/dist/types/core/color.d.ts +13 -0
- package/dist/types/core/compWrite.d.ts +11 -0
- package/dist/types/core/events.d.ts +11 -0
- package/dist/types/core/fields.d.ts +58 -0
- package/dist/types/core/registry.d.ts +7 -0
- package/dist/types/core/signals.d.ts +29 -0
- package/dist/types/core/time.d.ts +31 -0
- package/dist/types/g2/Camera2D.d.ts +19 -0
- package/dist/types/g2/CharacterController2D.d.ts +134 -0
- package/dist/types/g2/Node2D.d.ts +91 -0
- package/dist/types/g2/OneWay2D.d.ts +24 -0
- package/dist/types/g2/Physics2D.d.ts +147 -0
- package/dist/types/g2/Scene2D.d.ts +72 -0
- package/dist/types/g2/Shape2D.d.ts +58 -0
- package/dist/types/g2/Sprite.d.ts +51 -0
- package/dist/types/g2/SpriteAnimation.d.ts +50 -0
- package/dist/types/g2/SpriteSheet.d.ts +69 -0
- package/dist/types/g2/Texture2D.d.ts +16 -0
- package/dist/types/g2/Tilemap.d.ts +20 -0
- package/dist/types/g2/Tileset.d.ts +32 -0
- package/dist/types/g2/Trigger2D.d.ts +20 -0
- package/dist/types/g2/autotile.d.ts +133 -0
- package/dist/types/g2/cells.d.ts +14 -0
- package/dist/types/g2/defineScene2d.d.ts +141 -0
- package/dist/types/g2/groups2d.d.ts +16 -0
- package/dist/types/g2/loop.d.ts +4 -0
- package/dist/types/g2/scenarios2d.d.ts +30 -0
- package/dist/types/g2/touch.d.ts +2 -0
- package/dist/types/gl/Camera.d.ts +79 -0
- package/dist/types/gl/CameraPlace.d.ts +26 -0
- package/dist/types/gl/CharacterController.d.ts +98 -0
- package/dist/types/gl/Gearbox.d.ts +86 -0
- package/dist/types/gl/Geometry.d.ts +39 -0
- package/dist/types/gl/IK.d.ts +53 -0
- package/dist/types/gl/InstancedMesh.d.ts +46 -0
- package/dist/types/gl/Light.d.ts +73 -0
- package/dist/types/gl/Lightmap.d.ts +85 -0
- package/dist/types/gl/Locomotion.d.ts +279 -0
- package/dist/types/gl/Material.d.ts +100 -0
- package/dist/types/gl/Mesh.d.ts +33 -0
- package/dist/types/gl/Model.d.ts +53 -0
- package/dist/types/gl/NavAgent.d.ts +99 -0
- package/dist/types/gl/NavMesh.d.ts +169 -0
- package/dist/types/gl/Node.d.ts +99 -0
- package/dist/types/gl/Noise.d.ts +20 -0
- package/dist/types/gl/Particles.d.ts +277 -0
- package/dist/types/gl/Physics.d.ts +91 -0
- package/dist/types/gl/Plane.d.ts +19 -0
- package/dist/types/gl/Ragdoll.d.ts +86 -0
- package/dist/types/gl/Ray.d.ts +8 -0
- package/dist/types/gl/Scene.d.ts +221 -0
- package/dist/types/gl/Shape.d.ts +122 -0
- package/dist/types/gl/Terrain.d.ts +280 -0
- package/dist/types/gl/Texture.d.ts +27 -0
- package/dist/types/gl/Trigger.d.ts +10 -0
- package/dist/types/gl/Vehicle.d.ts +191 -0
- package/dist/types/gl/Wheel.d.ts +95 -0
- package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
- package/dist/types/gl/animation/Animator.d.ts +219 -0
- package/dist/types/gl/animation/Layer.d.ts +31 -0
- package/dist/types/gl/animation/Loop.d.ts +17 -0
- package/dist/types/gl/animation/Playback.d.ts +36 -0
- package/dist/types/gl/animation/core.d.ts +367 -0
- package/dist/types/gl/controls.d.ts +19 -0
- package/dist/types/gl/physicsEvents.d.ts +1 -0
- package/dist/types/gl/scenarios.d.ts +98 -0
- package/dist/types/gl/state.d.ts +5 -0
- package/dist/types/gl/terrainMesh.d.ts +46 -0
- package/dist/types/gl/touch.d.ts +2 -0
- package/dist/types/inject.d.ts +123 -0
- package/dist/types/math/Mathf.d.ts +39 -0
- package/dist/types/math/mat4.d.ts +66 -0
- package/dist/types/math/quat.d.ts +53 -0
- package/dist/types/math/vec.d.ts +129 -0
- package/dist/types/net/codec.d.ts +25 -0
- package/dist/types/net/core.d.ts +106 -0
- package/dist/types/net/index.d.ts +80 -0
- package/dist/types/net/replication.d.ts +101 -0
- package/dist/types/plugins/camera.d.ts +25 -0
- package/dist/types/plugins/geolocation.d.ts +39 -0
- package/dist/types/plugins/oauth.d.ts +25 -0
- package/dist/types/plugins/permission.d.ts +1 -0
- package/dist/types/plugins/push.d.ts +46 -0
- package/dist/types/plugins/qr.d.ts +21 -0
- package/dist/types/plugins/service.d.ts +33 -0
- package/dist/types/runtime/app.d.ts +67 -0
- package/dist/types/runtime/appEvents.d.ts +7 -0
- package/dist/types/runtime/channel.d.ts +1 -0
- package/dist/types/runtime/clipboard.d.ts +7 -0
- package/dist/types/runtime/datetime.d.ts +80 -0
- package/dist/types/runtime/device.d.ts +126 -0
- package/dist/types/runtime/fetch.d.ts +31 -0
- package/dist/types/runtime/files.d.ts +33 -0
- package/dist/types/runtime/input.d.ts +121 -0
- package/dist/types/runtime/media.d.ts +46 -0
- package/dist/types/runtime/misc.d.ts +11 -0
- package/dist/types/runtime/net.d.ts +15 -0
- package/dist/types/runtime/rpc.d.ts +44 -0
- package/dist/types/runtime/service.d.ts +1 -0
- package/dist/types/runtime/share.d.ts +2 -0
- package/dist/types/runtime/storage.d.ts +5 -0
- package/dist/types/runtime/touch.d.ts +56 -0
- package/dist/types/scene/defineScene.d.ts +296 -0
- package/dist/types/scene/editorPlugins.d.ts +82 -0
- package/dist/types/scene/gizmos.d.ts +56 -0
- package/dist/types/scene/grammar.d.ts +56 -0
- package/dist/types/scene/level.d.ts +98 -0
- package/dist/types/scene/material.d.ts +76 -0
- package/dist/types/server/auth/appConfig.d.ts +16 -0
- package/dist/types/server/auth/global.d.ts +56 -0
- package/dist/types/server/auth/models.d.ts +164 -0
- package/dist/types/server/auth/types.d.ts +56 -0
- package/dist/types/server/channel.d.ts +28 -0
- package/dist/types/server/context.d.ts +27 -0
- package/dist/types/server/db/defineDb.d.ts +36 -0
- package/dist/types/server/db/fields.d.ts +146 -0
- package/dist/types/server/db/index.d.ts +6 -0
- package/dist/types/server/db/marci/query.d.ts +326 -0
- package/dist/types/server/db/types.d.ts +163 -0
- package/dist/types/server/errors.d.ts +8 -0
- package/dist/types/server/inject.d.ts +5 -0
- package/dist/types/ui/NativeView.d.ts +38 -0
- package/dist/types/ui/UI.d.ts +27 -0
- package/dist/types/ui/UIBottomSheet.d.ts +71 -0
- package/dist/types/ui/UIButton.d.ts +30 -0
- package/dist/types/ui/UIContainer.d.ts +33 -0
- package/dist/types/ui/UIImage.d.ts +35 -0
- package/dist/types/ui/UIInput.d.ts +110 -0
- package/dist/types/ui/UIModal.d.ts +70 -0
- package/dist/types/ui/UINode.d.ts +363 -0
- package/dist/types/ui/UIPager.d.ts +123 -0
- package/dist/types/ui/UIPopover.d.ts +39 -0
- package/dist/types/ui/UIScreen.d.ts +57 -0
- package/dist/types/ui/UIScrollable.d.ts +40 -0
- package/dist/types/ui/UISpacer.d.ts +9 -0
- package/dist/types/ui/UITabs.d.ts +96 -0
- package/dist/types/ui/UIText.d.ts +19 -0
- package/dist/types/ui/UIVideo.d.ts +28 -0
- package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
- package/dist/types/ui/UIWidget.d.ts +65 -0
- package/dist/types/ui/fonts.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +68 -0
- package/dist/types/ui/router.d.ts +40 -0
- package/dist/types/ui/theme.d.ts +26 -0
- package/dist/types.json +1 -0
- package/package.json +46 -0
- package/prompts/2d.md +395 -0
- package/prompts/3d-scene-files.md +113 -0
- package/prompts/3d-scene.md +119 -0
- package/prompts/3d.md +303 -0
- package/prompts/README.md +142 -0
- package/prompts/ar.md +109 -0
- package/prompts/canvas.md +27 -0
- package/prompts/compose.ts +46 -0
- package/prompts/concept.md +49 -0
- package/prompts/core-design.md +75 -0
- package/prompts/core.md +264 -0
- package/prompts/design.md +218 -0
- package/prompts/dist/2d-game.md +1530 -0
- package/prompts/dist/3d-app.md +1672 -0
- package/prompts/dist/ar-app.md +1548 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +498 -0
- package/prompts/dist/ui-app.md +1134 -0
- package/prompts/index.md +37 -0
- package/prompts/intake-prompt.md +33 -0
- package/prompts/namer-prompt.md +12 -0
- package/prompts/router-prompt.md +50 -0
- package/prompts/select.ts +111 -0
- package/prompts/ui-design.md +203 -0
- package/prompts/ui.md +803 -0
- package/src/animate/animate.ts +238 -0
- package/src/animate/bezier.ts +138 -0
- package/src/animate/easings.ts +126 -0
- package/src/bridges.d.ts +1345 -0
- package/src/canvas/Canvas.ts +305 -0
- package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
- package/src/compile/__tests__/assetMacro.test.ts +74 -0
- package/src/compile/__tests__/assetName.test.ts +55 -0
- package/src/compile/__tests__/compile.test.ts +287 -0
- package/src/compile/__tests__/detectEntry.test.ts +132 -0
- package/src/compile/__tests__/fontMacro.test.ts +199 -0
- package/src/compile/aspectMacro.ts +86 -0
- package/src/compile/assetIconMacro.ts +384 -0
- package/src/compile/assetMacro.ts +146 -0
- package/src/compile/assetName.ts +50 -0
- package/src/compile/bundler.ts +287 -0
- package/src/compile/compileProject.ts +127 -0
- package/src/compile/detectEntry.ts +128 -0
- package/src/compile/fontMacro.ts +459 -0
- package/src/compile/fontRegistry.ts +78 -0
- package/src/compile/header.ts +67 -0
- package/src/compile/index.ts +100 -0
- package/src/compile/libraryImports.ts +52 -0
- package/src/compile/liteMaterial.ts +247 -0
- package/src/compile/sceneEditor.ts +88 -0
- package/src/compile/serverSplit.ts +233 -0
- package/src/compile/serverTypes.ts +227 -0
- package/src/compile/sfnt.ts +98 -0
- package/src/compile/shaderSchema.ts +202 -0
- package/src/compile/shaderTargets.ts +77 -0
- package/src/compile/sourcemap.ts +25 -0
- package/src/core/Aspect.ts +568 -0
- package/src/core/InspectorUI.ts +212 -0
- package/src/core/StateMachine.ts +308 -0
- package/src/core/__tests__/stateMachine.test.ts +132 -0
- package/src/core/color.ts +66 -0
- package/src/core/compWrite.ts +42 -0
- package/src/core/events.ts +38 -0
- package/src/core/fields.ts +120 -0
- package/src/core/registry.ts +23 -0
- package/src/core/signals.ts +277 -0
- package/src/core/time.ts +84 -0
- package/src/g2/Camera2D.ts +40 -0
- package/src/g2/CharacterController2D.ts +276 -0
- package/src/g2/Node2D.ts +267 -0
- package/src/g2/OneWay2D.ts +66 -0
- package/src/g2/Physics2D.ts +346 -0
- package/src/g2/Scene2D.ts +209 -0
- package/src/g2/Shape2D.ts +259 -0
- package/src/g2/Sprite.ts +89 -0
- package/src/g2/SpriteAnimation.ts +171 -0
- package/src/g2/SpriteSheet.ts +166 -0
- package/src/g2/Texture2D.ts +47 -0
- package/src/g2/Tilemap.ts +41 -0
- package/src/g2/Tileset.ts +71 -0
- package/src/g2/Trigger2D.ts +77 -0
- package/src/g2/autotile.ts +433 -0
- package/src/g2/cells.ts +91 -0
- package/src/g2/defineScene2d.ts +381 -0
- package/src/g2/groups2d.ts +106 -0
- package/src/g2/loop.ts +50 -0
- package/src/g2/scenarios2d.ts +69 -0
- package/src/g2/touch.ts +83 -0
- package/src/gl/Camera.ts +160 -0
- package/src/gl/CameraPlace.ts +52 -0
- package/src/gl/CharacterController.ts +238 -0
- package/src/gl/Gearbox.ts +212 -0
- package/src/gl/Geometry.ts +279 -0
- package/src/gl/IK.ts +193 -0
- package/src/gl/InstancedMesh.ts +132 -0
- package/src/gl/Light.ts +135 -0
- package/src/gl/Lightmap.ts +402 -0
- package/src/gl/Locomotion.ts +498 -0
- package/src/gl/Material.ts +279 -0
- package/src/gl/Mesh.ts +83 -0
- package/src/gl/Model.ts +124 -0
- package/src/gl/NavAgent.ts +337 -0
- package/src/gl/NavMesh.ts +397 -0
- package/src/gl/Node.ts +350 -0
- package/src/gl/Noise.ts +30 -0
- package/src/gl/Particles.ts +676 -0
- package/src/gl/Physics.ts +222 -0
- package/src/gl/Plane.ts +53 -0
- package/src/gl/Ragdoll.ts +270 -0
- package/src/gl/Ray.ts +16 -0
- package/src/gl/Scene.ts +505 -0
- package/src/gl/Shape.ts +409 -0
- package/src/gl/Terrain.ts +1092 -0
- package/src/gl/Texture.ts +63 -0
- package/src/gl/Trigger.ts +45 -0
- package/src/gl/Vehicle.ts +473 -0
- package/src/gl/Wheel.ts +240 -0
- package/src/gl/animation/AnimationClip.ts +204 -0
- package/src/gl/animation/Animator.ts +329 -0
- package/src/gl/animation/Layer.ts +39 -0
- package/src/gl/animation/Loop.ts +31 -0
- package/src/gl/animation/Playback.ts +52 -0
- package/src/gl/animation/core.ts +670 -0
- package/src/gl/controls.ts +95 -0
- package/src/gl/physicsEvents.ts +20 -0
- package/src/gl/scenarios.ts +291 -0
- package/src/gl/state.ts +6 -0
- package/src/gl/terrainMesh.ts +219 -0
- package/src/gl/touch.ts +68 -0
- package/src/host.d.ts +61 -0
- package/src/inject.ts +203 -0
- package/src/math/Mathf.ts +118 -0
- package/src/math/mat4.ts +278 -0
- package/src/math/quat.ts +232 -0
- package/src/math/vec.ts +255 -0
- package/src/net/codec.ts +119 -0
- package/src/net/core.ts +384 -0
- package/src/net/index.ts +181 -0
- package/src/net/replication.ts +622 -0
- package/src/plugins/camera.ts +81 -0
- package/src/plugins/geolocation.ts +123 -0
- package/src/plugins/oauth.ts +61 -0
- package/src/plugins/permission.ts +7 -0
- package/src/plugins/push.ts +132 -0
- package/src/plugins/qr.ts +73 -0
- package/src/plugins/service.ts +47 -0
- package/src/runtime/app.ts +101 -0
- package/src/runtime/appEvents.ts +54 -0
- package/src/runtime/channel.ts +61 -0
- package/src/runtime/clipboard.ts +20 -0
- package/src/runtime/datetime.ts +329 -0
- package/src/runtime/device.ts +293 -0
- package/src/runtime/fetch.ts +77 -0
- package/src/runtime/files.ts +108 -0
- package/src/runtime/input.ts +175 -0
- package/src/runtime/media.ts +111 -0
- package/src/runtime/misc.ts +16 -0
- package/src/runtime/net.ts +36 -0
- package/src/runtime/rpc.ts +218 -0
- package/src/runtime/service.ts +83 -0
- package/src/runtime/share.ts +9 -0
- package/src/runtime/storage.ts +13 -0
- package/src/runtime/touch.ts +76 -0
- package/src/scene/defineScene.ts +1396 -0
- package/src/scene/editorPlugins.ts +110 -0
- package/src/scene/gizmos.ts +148 -0
- package/src/scene/grammar.ts +120 -0
- package/src/scene/level.ts +296 -0
- package/src/scene/material.ts +188 -0
- package/src/server/auth/appConfig.ts +12 -0
- package/src/server/auth/global.ts +80 -0
- package/src/server/auth/host.ts +318 -0
- package/src/server/auth/models.ts +83 -0
- package/src/server/auth/types.ts +50 -0
- package/src/server/channel.ts +56 -0
- package/src/server/context.ts +36 -0
- package/src/server/db/defineDb.ts +237 -0
- package/src/server/db/fields.ts +132 -0
- package/src/server/db/httpTransport.ts +93 -0
- package/src/server/db/index.ts +7 -0
- package/src/server/db/marci/query.ts +412 -0
- package/src/server/db/types.ts +202 -0
- package/src/server/errors.ts +12 -0
- package/src/server/host.ts +74 -0
- package/src/server/inject.ts +13 -0
- package/src/server/runtime.ts +133 -0
- package/src/server/validate.ts +87 -0
- package/src/ui/NativeView.ts +142 -0
- package/src/ui/UI.ts +39 -0
- package/src/ui/UIBottomSheet.ts +139 -0
- package/src/ui/UIButton.ts +101 -0
- package/src/ui/UIContainer.ts +60 -0
- package/src/ui/UIImage.ts +83 -0
- package/src/ui/UIInput.ts +185 -0
- package/src/ui/UIModal.ts +139 -0
- package/src/ui/UINode.ts +830 -0
- package/src/ui/UIPager.ts +362 -0
- package/src/ui/UIPopover.ts +100 -0
- package/src/ui/UIScreen.ts +123 -0
- package/src/ui/UIScrollable.ts +87 -0
- package/src/ui/UISpacer.ts +14 -0
- package/src/ui/UITabs.ts +236 -0
- package/src/ui/UIText.ts +51 -0
- package/src/ui/UIVideo.ts +88 -0
- package/src/ui/UIVirtualizedList.ts +241 -0
- package/src/ui/UIWidget.ts +127 -0
- package/src/ui/fonts.ts +13 -0
- package/src/ui/presentable.ts +117 -0
- package/src/ui/router.ts +132 -0
- package/src/ui/theme.ts +84 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
// Navigation meshes (docs/navmesh-plan.md). A level bakes ONE navmesh at dev time from its static
|
|
2
|
+
// collision geometry (`lecodes navmesh bake` — the desktop host dumps every static Jolt body's
|
|
3
|
+
// triangles, Recast builds the mesh in the CLI) and loads it at runtime; agents (`NavAgent`) walk
|
|
4
|
+
// it through DetourCrowd, and the game asks it for paths, nearest points, walkability rays and
|
|
5
|
+
// random spots. A scene FILE does the loading by itself:
|
|
6
|
+
//
|
|
7
|
+
// env: { navmesh: { data: asset('./assets/nav/yard.navmesh'), agent: { radius: 0.4, height: 1.8 } } }
|
|
8
|
+
//
|
|
9
|
+
// Hand-built scenes call it once the level is complete:
|
|
10
|
+
//
|
|
11
|
+
// const nav = await NavMesh.load(scene, { data: asset('./assets/nav/yard.navmesh') })
|
|
12
|
+
// nav?.findPath(a, b) // Vec3[] corners (+ .partial)
|
|
13
|
+
// enemy.aspect(NavAgent, { speed: 3 }).agent.goTo(player.position)
|
|
14
|
+
//
|
|
15
|
+
// Under `lecodes navmesh bake` the same call is where the geometry dump fires ("the scene is
|
|
16
|
+
// complete"); on a host without navigation, or with a placeholder file, `load` resolves null and
|
|
17
|
+
// agents stay inert — the project runs everywhere. All logic lives here in JS; the native side
|
|
18
|
+
// (creator-nav) is the algorithms and the per-step hot path: ONE crowd update + ONE flat read per
|
|
19
|
+
// fixed step for every agent in the scene (`NavCrowd`, a system the mesh attaches to its scene).
|
|
20
|
+
|
|
21
|
+
import { fetch } from "../runtime/fetch"
|
|
22
|
+
import { System } from "../core/Aspect"
|
|
23
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
24
|
+
import { Geometry } from "./Geometry"
|
|
25
|
+
import { Material } from "./Material"
|
|
26
|
+
import { Mesh } from "./Mesh"
|
|
27
|
+
import type { Node } from "./Node"
|
|
28
|
+
import type { Scene } from "./Scene"
|
|
29
|
+
import type { NavAgent } from "./NavAgent"
|
|
30
|
+
|
|
31
|
+
// Fixed-order layouts — keep in step with creator-nav/creator-nav.h (CNAV_AGENT_* / CNAV_READ_*).
|
|
32
|
+
export const NAV_AGENT = {
|
|
33
|
+
RADIUS: 0, HEIGHT: 1, MAX_SPEED: 2, MAX_ACCELERATION: 3, COLLISION_QUERY_RANGE: 4, PATH_OPTIMIZATION_RANGE: 5,
|
|
34
|
+
SEPARATION_WEIGHT: 6, AVOIDANCE: 7, INCLUDE_FLAGS: 8, EXCLUDE_FLAGS: 9, ANTICIPATE_TURNS: 10,
|
|
35
|
+
OPTIMIZE_VIS: 11, OPTIMIZE_TOPO: 12, COUNT: 13,
|
|
36
|
+
} as const
|
|
37
|
+
export const NAV_READ = {
|
|
38
|
+
POS_X: 0, POS_Y: 1, POS_Z: 2, VEL_X: 3, VEL_Y: 4, VEL_Z: 5, DVEL_X: 6, DVEL_Y: 7, DVEL_Z: 8,
|
|
39
|
+
STATE: 9, TARGET_STATE: 10, REMAINING: 11, PARTIAL: 12, STRIDE: 13,
|
|
40
|
+
} as const
|
|
41
|
+
const FLAGS_ALL = 0xffff
|
|
42
|
+
const AREA_UNWALKABLE = 255
|
|
43
|
+
const AREA_SKIP = 254
|
|
44
|
+
|
|
45
|
+
/** The agent size a navmesh is built for (and the crowd's defaults). */
|
|
46
|
+
export type NavAgentSize = {
|
|
47
|
+
/** metres — polygons are eroded by it. Default 0.4 */
|
|
48
|
+
radius?: number
|
|
49
|
+
/** metres. Default 1.8 */
|
|
50
|
+
height?: number
|
|
51
|
+
/** the highest step walked over, metres. Default 0.4 */
|
|
52
|
+
climb?: number
|
|
53
|
+
/** the steepest walkable slope, degrees. Default 50 */
|
|
54
|
+
slope?: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type NavMeshLoadOptions = {
|
|
58
|
+
/** `asset('./assets/nav/<scene>.navmesh')` — what `lecodes navmesh bake` writes. */
|
|
59
|
+
data: string
|
|
60
|
+
/** The agent size the bake uses (the file records it; the crowd reads it back). */
|
|
61
|
+
agent?: NavAgentSize
|
|
62
|
+
/** Named areas → traversal cost (1 = plain ground). Ids are assigned in this order (1..15); the
|
|
63
|
+
* bake stores the names, queries and agents refer to them (`include` / `exclude`, `cost`). */
|
|
64
|
+
areas?: Record<string, number>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The file header `lecodes navmesh bake` wrote. */
|
|
68
|
+
export type NavMeshInfo = {
|
|
69
|
+
version: number
|
|
70
|
+
agent: { radius: number, height: number, climb: number, slope: number }
|
|
71
|
+
cell: number
|
|
72
|
+
cellHeight: number
|
|
73
|
+
tileSize: number
|
|
74
|
+
bounds: [[number, number, number], [number, number, number]]
|
|
75
|
+
stats: { tiles: number, polys: number, verts: number, triangles: number, volumes: number, links: number }
|
|
76
|
+
meta: { scene?: string, areas?: Record<string, number>, costs?: Record<string, number>, [k: string]: unknown } | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A straight path: the corners to walk, plus whether it stops short of the target. */
|
|
80
|
+
export type NavPath = Vec3[] & { partial: boolean }
|
|
81
|
+
|
|
82
|
+
export type NavQueryOptions = {
|
|
83
|
+
/** Only these areas (names). Default: every area. */
|
|
84
|
+
include?: string[]
|
|
85
|
+
/** Never these areas (names). */
|
|
86
|
+
exclude?: string[]
|
|
87
|
+
/** Search half-extents around each endpoint (how far off the mesh a point may be). Default [2, 4, 2]. */
|
|
88
|
+
extents?: Vec3Like
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type NavRaycastHit = { hit: boolean, point: Vec3, normal: Vec3 }
|
|
92
|
+
|
|
93
|
+
type BakeConfig = { outDir: string, name?: string }
|
|
94
|
+
|
|
95
|
+
const bakeConfig = (): BakeConfig | null => {
|
|
96
|
+
const g = (globalThis as unknown as { __lecodesNavmesh?: BakeConfig }).__lecodesNavmesh
|
|
97
|
+
return g && typeof g.outDir === "string" ? g : null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const hasNav = (): boolean => typeof _creatorNav !== "undefined" && _creatorNav !== null
|
|
101
|
+
|
|
102
|
+
const scratch = new Float32Array(6)
|
|
103
|
+
const pathScratch = new Float32Array(256 * 3)
|
|
104
|
+
|
|
105
|
+
/** The crowd behind a scene's agents — a system `NavMesh.load` attaches to the scene so the whole
|
|
106
|
+
* crowd advances once per fixed step, BEFORE every `NavAgent` applies its slot (`static after`). */
|
|
107
|
+
export class NavCrowd extends System<"navCrowd"> {
|
|
108
|
+
static readonly aspect = "navCrowd"
|
|
109
|
+
|
|
110
|
+
/** @internal */ _mesh: NavMesh | null = null
|
|
111
|
+
/** @internal */ _id = 0
|
|
112
|
+
/** @internal */ _max = 0
|
|
113
|
+
/** @internal */ _buf = new Float32Array(0)
|
|
114
|
+
/** @internal */ _agents = new Set<NavAgent>()
|
|
115
|
+
/** @internal */ _step = 0
|
|
116
|
+
|
|
117
|
+
/** @internal Make room for one more agent (the crowd is re-created with double the slots; every
|
|
118
|
+
* agent re-joins — slots change, targets are re-requested). */
|
|
119
|
+
_ensureCapacity(): boolean {
|
|
120
|
+
if (!this._mesh || !hasNav()) return false
|
|
121
|
+
if (this._id && this._agents.size < this._max) return true
|
|
122
|
+
const max = Math.max(16, this._max * 2)
|
|
123
|
+
const id = _creatorNav.crowdCreate(this._mesh.id, max, Math.max(0.6, this._mesh._maxRadius))
|
|
124
|
+
if (!id) { console.warn(`NavCrowd: ${_creatorNav.lastError()}`); return false }
|
|
125
|
+
const old = this._id
|
|
126
|
+
this._id = id
|
|
127
|
+
this._max = max
|
|
128
|
+
this._buf = new Float32Array(max * NAV_READ.STRIDE)
|
|
129
|
+
if (old) {
|
|
130
|
+
for (const a of this._agents) a._rejoin()
|
|
131
|
+
_creatorNav.crowdDestroy(old)
|
|
132
|
+
}
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
updateFixed(dt: number): void {
|
|
137
|
+
if (!this._id) return
|
|
138
|
+
_creatorNav.crowdUpdate(this._id, dt)
|
|
139
|
+
_creatorNav.crowdRead(this._id, this._buf)
|
|
140
|
+
this._step++
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
onDetach(): void {
|
|
144
|
+
if (this._id && hasNav()) _creatorNav.crowdDestroy(this._id)
|
|
145
|
+
this._id = 0
|
|
146
|
+
this._max = 0
|
|
147
|
+
this._agents.clear()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export class NavMesh {
|
|
152
|
+
/** True while `lecodes navmesh bake` runs the app — skip menus and build the level straight away. */
|
|
153
|
+
static get baking(): boolean { return bakeConfig() !== null }
|
|
154
|
+
|
|
155
|
+
/** The navmesh agents bind to (the last one loaded; one per world at a time). */
|
|
156
|
+
static active: NavMesh | null = null
|
|
157
|
+
|
|
158
|
+
private static byScene = new WeakMap<Scene, NavMesh>()
|
|
159
|
+
private static overrides = new Map<number, number | string>() // entity → area id / name / skip
|
|
160
|
+
private static warned = false
|
|
161
|
+
private static baked = false
|
|
162
|
+
|
|
163
|
+
/** Leave a static body out of the bake (`nav: false` in a scene file). */
|
|
164
|
+
static exclude(node: Node): void { NavMesh.overrides.set(node.id, AREA_SKIP) }
|
|
165
|
+
/** A static body nobody may walk on (`nav: 'unwalkable'`): its footprint is cut out. */
|
|
166
|
+
static unwalkable(node: Node): void { NavMesh.overrides.set(node.id, AREA_UNWALKABLE) }
|
|
167
|
+
/** A static body's surface belongs to a named area (`navArea: 'road'`; names from `load`'s `areas`). */
|
|
168
|
+
static area(node: Node, name: string): void { NavMesh.overrides.set(node.id, name) }
|
|
169
|
+
/** Forget every override (a scene rebuild). */
|
|
170
|
+
static clear(): void { NavMesh.overrides.clear() }
|
|
171
|
+
|
|
172
|
+
static of(scene: Scene): NavMesh | null { return NavMesh.byScene.get(scene) ?? null }
|
|
173
|
+
|
|
174
|
+
/** Load the level's navmesh — or, under `lecodes navmesh bake`, dump the collision geometry for
|
|
175
|
+
* it. Resolves null when nothing was loaded (no bake yet, a host without navigation, bake mode). */
|
|
176
|
+
static async load(scene: Scene, opts: NavMeshLoadOptions): Promise<NavMesh | null> {
|
|
177
|
+
const bake = bakeConfig()
|
|
178
|
+
if (bake) { NavMesh.bake(bake, opts); return null }
|
|
179
|
+
if (!hasNav()) {
|
|
180
|
+
if (!NavMesh.warned) { NavMesh.warned = true; console.warn("NavMesh: this host has no navigation support — agents stay still") }
|
|
181
|
+
return null
|
|
182
|
+
}
|
|
183
|
+
if (_creatorNav.ready) await _creatorNav.ready()
|
|
184
|
+
let id = 0
|
|
185
|
+
try {
|
|
186
|
+
const resp = await fetch(opts.data, { useOnce: true })
|
|
187
|
+
if (resp.status >= 400) { console.warn(`NavMesh: no navmesh at ${opts.data} (HTTP ${resp.status}) — run \`lecodes navmesh bake\``); return null }
|
|
188
|
+
id = _creatorNav.load((resp as unknown as { _id: number })._id)
|
|
189
|
+
resp.dispose()
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.warn(`NavMesh: could not read ${opts.data}: ${String(e)}`)
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
if (!id) { console.warn(`NavMesh: ${opts.data}: ${_creatorNav.lastError()}`); return null }
|
|
195
|
+
let info: NavMeshInfo
|
|
196
|
+
try { info = JSON.parse(_creatorNav.header(id)) as NavMeshInfo } catch { info = { version: 0 } as NavMeshInfo }
|
|
197
|
+
if (!info.stats || info.stats.polys === 0) {
|
|
198
|
+
console.warn(`NavMesh: ${opts.data} is a placeholder — run \`lecodes navmesh bake\``)
|
|
199
|
+
_creatorNav.unload(id)
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
const previous = NavMesh.byScene.get(scene)
|
|
203
|
+
if (previous) previous.dispose()
|
|
204
|
+
const nav = new NavMesh(scene, id, info, opts)
|
|
205
|
+
NavMesh.byScene.set(scene, nav)
|
|
206
|
+
NavMesh.active = nav
|
|
207
|
+
return nav
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
readonly scene: Scene
|
|
211
|
+
/** @internal the host handle */
|
|
212
|
+
readonly id: number
|
|
213
|
+
readonly info: NavMeshInfo
|
|
214
|
+
/** The agent size the mesh was built for. */
|
|
215
|
+
readonly agent: { radius: number, height: number, climb: number, slope: number }
|
|
216
|
+
/** Named areas → ids (from the bake; `load`'s `areas` when the file has none). */
|
|
217
|
+
readonly areas: Record<string, number>
|
|
218
|
+
/** @internal the largest agent radius seen (sizes the crowd's grid) */
|
|
219
|
+
_maxRadius = 0
|
|
220
|
+
private _debug: Mesh[] = []
|
|
221
|
+
private _seed = 0x9e3779b9
|
|
222
|
+
private _disposed = false
|
|
223
|
+
|
|
224
|
+
private constructor(scene: Scene, id: number, info: NavMeshInfo, opts: NavMeshLoadOptions) {
|
|
225
|
+
this.scene = scene
|
|
226
|
+
this.id = id
|
|
227
|
+
this.info = info
|
|
228
|
+
this.agent = info.agent ?? { radius: opts.agent?.radius ?? 0.4, height: opts.agent?.height ?? 1.8, climb: opts.agent?.climb ?? 0.4, slope: opts.agent?.slope ?? 50 }
|
|
229
|
+
this._maxRadius = this.agent.radius
|
|
230
|
+
this.areas = info.meta?.areas ?? NavMesh.areaIds(opts.areas)
|
|
231
|
+
const costs = { ...(info.meta?.costs ?? {}), ...(opts.areas ?? {}) }
|
|
232
|
+
for (const [ name, cost ] of Object.entries(costs)) this.cost(name, cost)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private static areaIds(areas: Record<string, number> | undefined): Record<string, number> {
|
|
236
|
+
const out: Record<string, number> = {}
|
|
237
|
+
let next = 1
|
|
238
|
+
for (const name of Object.keys(areas ?? {})) {
|
|
239
|
+
if (next > 15) { console.warn(`NavMesh: more than 15 areas — "${name}" ignored`); break }
|
|
240
|
+
out[name] = next++
|
|
241
|
+
}
|
|
242
|
+
return out
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The include/exclude flag mask for a list of area names (undefined = every area). */
|
|
246
|
+
flags(names?: string[]): number {
|
|
247
|
+
if (!names) return FLAGS_ALL
|
|
248
|
+
let mask = 0
|
|
249
|
+
for (const n of names) {
|
|
250
|
+
const id = this.areas[n]
|
|
251
|
+
if (id === undefined) { console.warn(`NavMesh: unknown area "${n}"`); continue }
|
|
252
|
+
mask |= 1 << id
|
|
253
|
+
}
|
|
254
|
+
return mask
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private masks(opts?: NavQueryOptions): [number, number] {
|
|
258
|
+
return [ opts?.include ? this.flags(opts.include) : FLAGS_ALL, opts?.exclude ? this.flags(opts.exclude) : 0 ]
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Traversal cost of a named area (1 = plain ground; > 1 = avoided when a cheaper way exists). */
|
|
262
|
+
cost(area: string, cost: number): void {
|
|
263
|
+
const id = this.areas[area]
|
|
264
|
+
if (id === undefined) { console.warn(`NavMesh: unknown area "${area}"`); return }
|
|
265
|
+
if (!this._disposed) _creatorNav.setAreaCost(this.id, id, cost)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** The straight (string-pulled) path from → to. Empty when an endpoint is off the mesh;
|
|
269
|
+
* `.partial` when the target is unreachable (the path ends at the closest polygon). */
|
|
270
|
+
findPath(from: Vec3Like, to: Vec3Like, opts?: NavQueryOptions & { maxCorners?: number }): NavPath {
|
|
271
|
+
const path = [] as unknown as NavPath
|
|
272
|
+
path.partial = false
|
|
273
|
+
if (this._disposed) return path
|
|
274
|
+
const [ inc, exc ] = this.masks(opts)
|
|
275
|
+
const e = opts?.extents
|
|
276
|
+
const max = Math.min(opts?.maxCorners ?? 256, 256)
|
|
277
|
+
const n = _creatorNav.findPath(this.id, cx(from), cy(from), cz(from), cx(to), cy(to), cz(to),
|
|
278
|
+
e ? cx(e) : 0, e ? cy(e) : 0, e ? cz(e) : 0, inc, exc, max, pathScratch)
|
|
279
|
+
const count = Math.abs(n)
|
|
280
|
+
for (let i = 0; i < count; i++) path.push(new Vec3(pathScratch[i * 3]!, pathScratch[i * 3 + 1]!, pathScratch[i * 3 + 2]!))
|
|
281
|
+
path.partial = n < 0
|
|
282
|
+
return path
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** The closest point on the mesh (within `extents` of `p`), or null. */
|
|
286
|
+
nearest(p: Vec3Like, opts?: NavQueryOptions): Vec3 | null {
|
|
287
|
+
if (this._disposed) return null
|
|
288
|
+
const [ inc, exc ] = this.masks(opts)
|
|
289
|
+
const e = opts?.extents
|
|
290
|
+
const ok = _creatorNav.nearest(this.id, cx(p), cy(p), cz(p), e ? cx(e) : 0, e ? cy(e) : 0, e ? cz(e) : 0, inc, exc, scratch)
|
|
291
|
+
return ok ? new Vec3(scratch[0]!, scratch[1]!, scratch[2]!) : null
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Can an agent walk straight from → to? `hit` = a wall in between (at `point`, facing `normal`). */
|
|
295
|
+
raycast(from: Vec3Like, to: Vec3Like, opts?: NavQueryOptions): NavRaycastHit {
|
|
296
|
+
if (this._disposed) return { hit: true, point: new Vec3(cx(from), cy(from), cz(from)), normal: new Vec3(0, 0, 0) }
|
|
297
|
+
const [ inc, exc ] = this.masks(opts)
|
|
298
|
+
const hit = _creatorNav.raycast(this.id, cx(from), cy(from), cz(from), cx(to), cy(to), cz(to), inc, exc, scratch)
|
|
299
|
+
return { hit, point: new Vec3(scratch[0]!, scratch[1]!, scratch[2]!), normal: new Vec3(scratch[3]!, scratch[4]!, scratch[5]!) }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** A random reachable point — within walking distance `radius` of `center`, or anywhere on the
|
|
303
|
+
* mesh. Deterministic: the sequence depends only on the calls made (a seeded generator). */
|
|
304
|
+
randomPoint(center?: Vec3Like, radius = 0, opts?: NavQueryOptions): Vec3 | null {
|
|
305
|
+
if (this._disposed) return null
|
|
306
|
+
const [ inc, exc ] = this.masks(opts)
|
|
307
|
+
this._seed = (Math.imul(this._seed, 1664525) + 1013904223) >>> 0
|
|
308
|
+
const c = center && radius > 0
|
|
309
|
+
const ok = _creatorNav.randomPoint(this.id, c ? cx(center) : 0, c ? cy(center) : 0, c ? cz(center) : 0, c ? radius : 0, this._seed || 1, inc, exc, scratch)
|
|
310
|
+
return ok ? new Vec3(scratch[0]!, scratch[1]!, scratch[2]!) : null
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Draw the polygon mesh (a flat unlit surface 3 cm above the walkable area). */
|
|
314
|
+
get debug(): boolean { return this._debug.length > 0 }
|
|
315
|
+
set debug(on: boolean) {
|
|
316
|
+
if (on === this.debug || this._disposed) return
|
|
317
|
+
if (!on) { for (const m of this._debug) m.destroy(); this._debug = []; return }
|
|
318
|
+
const tris = _creatorNav.debugTriangles(this.id)
|
|
319
|
+
const material = Material.unlit({ color: "#3b82f6" })
|
|
320
|
+
// Geometry indexes with 16-bit indices: chunk the soup
|
|
321
|
+
const perChunk = 65535 - (65535 % 3)
|
|
322
|
+
for (let start = 0; start < tris.length / 3; start += perChunk) {
|
|
323
|
+
const count = Math.min(perChunk, tris.length / 3 - start)
|
|
324
|
+
const vertices = new Float32Array(count * 3)
|
|
325
|
+
const normals = new Float32Array(count * 3)
|
|
326
|
+
const uv = new Float32Array(count * 2)
|
|
327
|
+
const indices = new Uint16Array(count)
|
|
328
|
+
for (let i = 0; i < count; i++) {
|
|
329
|
+
vertices[i * 3] = tris[(start + i) * 3]!
|
|
330
|
+
vertices[i * 3 + 1] = tris[(start + i) * 3 + 1]! + 0.03
|
|
331
|
+
vertices[i * 3 + 2] = tris[(start + i) * 3 + 2]!
|
|
332
|
+
normals[i * 3 + 1] = 1
|
|
333
|
+
indices[i] = i
|
|
334
|
+
}
|
|
335
|
+
const mesh = new Mesh(new Geometry(vertices, normals, indices, uv), material)
|
|
336
|
+
mesh.name = "navmesh-debug"
|
|
337
|
+
mesh.castShadows = false
|
|
338
|
+
this.scene.add(mesh)
|
|
339
|
+
this._debug.push(mesh)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** @internal The scene's crowd system (created on the first agent). */
|
|
344
|
+
_crowd(): NavCrowd {
|
|
345
|
+
const existing = this.scene.get(NavCrowd)
|
|
346
|
+
if (existing && existing._mesh === this) return existing
|
|
347
|
+
const crowd = this.scene.system(NavCrowd).navCrowd
|
|
348
|
+
crowd._mesh = this
|
|
349
|
+
return crowd
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Release the host handles (a scene teardown). Agents fall back to inert. */
|
|
353
|
+
dispose(): void {
|
|
354
|
+
if (this._disposed) return
|
|
355
|
+
this._disposed = true
|
|
356
|
+
this.debug = false
|
|
357
|
+
const crowd = this.scene.get(NavCrowd)
|
|
358
|
+
if (crowd && crowd._mesh === this) {
|
|
359
|
+
for (const a of crowd._agents) a._leave()
|
|
360
|
+
this.scene.removeSystem(NavCrowd)
|
|
361
|
+
}
|
|
362
|
+
if (hasNav()) _creatorNav.unload(this.id)
|
|
363
|
+
if (NavMesh.byScene.get(this.scene) === this) NavMesh.byScene.delete(this.scene)
|
|
364
|
+
if (NavMesh.active === this) NavMesh.active = null
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---- bake mode ----------------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
private static bake(cfg: BakeConfig, opts: NavMeshLoadOptions): void {
|
|
370
|
+
if (NavMesh.baked) { console.log("[navmesh] a second NavMesh.load — skipped (one bake per run; use --entry per scene)"); return }
|
|
371
|
+
NavMesh.baked = true
|
|
372
|
+
if (!_creator.physicsStaticGeometry) {
|
|
373
|
+
console.error("[navmesh] error: this host cannot dump collision geometry (build the desktop host from packages/desktop, or update: lecodes desktop update)")
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
const areaIds = NavMesh.areaIds(opts.areas)
|
|
377
|
+
const ids: number[] = []
|
|
378
|
+
const areas: number[] = []
|
|
379
|
+
for (const [ entity, area ] of NavMesh.overrides) {
|
|
380
|
+
let id: number
|
|
381
|
+
if (typeof area === "string") {
|
|
382
|
+
id = areaIds[area] ?? -1
|
|
383
|
+
if (id < 0) { console.warn(`[navmesh] unknown area "${area}" on entity ${entity} — declare it in the navmesh 'areas'`); continue }
|
|
384
|
+
} else id = area
|
|
385
|
+
ids.push(entity)
|
|
386
|
+
areas.push(id)
|
|
387
|
+
}
|
|
388
|
+
const path = `${cfg.outDir}/${cfg.name ?? "scene"}.navgeo`
|
|
389
|
+
const n = _creator.physicsStaticGeometry(path, Uint32Array.from(ids), Uint8Array.from(areas))
|
|
390
|
+
if (n < 0) { console.error(`[navmesh] error: the geometry dump failed (${path})`); return }
|
|
391
|
+
if (n === 0) { console.error("[navmesh] error: no static collision geometry — agents walk on STATIC Physics bodies (Shape + Physics { motion: 'static' })"); return }
|
|
392
|
+
const agent = { radius: opts.agent?.radius ?? 0.4, height: opts.agent?.height ?? 1.8, climb: opts.agent?.climb ?? 0.4, slope: opts.agent?.slope ?? 50 }
|
|
393
|
+
console.log(`[navmesh] geometry ${n} triangles → ${path}`)
|
|
394
|
+
console.log(`[navmesh] config ${JSON.stringify({ agent, areas: areaIds, costs: opts.areas ?? {}, data: opts.data })}`)
|
|
395
|
+
console.log("[navmesh] done")
|
|
396
|
+
}
|
|
397
|
+
}
|