@jgengine/shell 0.8.0 → 0.10.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/CHANGELOG.md +36 -0
- package/README.md +11 -0
- package/dist/GamePlayerShell.d.ts +10 -22
- package/dist/GamePlayerShell.js +709 -264
- package/dist/GameUiPreview.js +2 -1
- package/dist/audio/AudioComponents.js +25 -8
- package/dist/audio/audioEngine.d.ts +9 -0
- package/dist/audio/audioEngine.js +37 -0
- package/dist/audio/musicDirector.d.ts +31 -0
- package/dist/audio/musicDirector.js +125 -0
- package/dist/audio/musicVoices.d.ts +10 -0
- package/dist/audio/musicVoices.js +338 -0
- package/dist/audio/synthEngine.d.ts +9 -0
- package/dist/audio/synthEngine.js +57 -0
- package/dist/behaviour.d.ts +5 -24
- package/dist/behaviour.js +14 -50
- package/dist/behaviourAttach.d.ts +9 -0
- package/dist/behaviourAttach.js +32 -0
- package/dist/behaviourDriver.d.ts +7 -0
- package/dist/behaviourDriver.js +21 -0
- package/dist/camera/GameCameraRig.js +1 -1
- package/dist/camera/GameFirstPersonCamera.d.ts +3 -0
- package/dist/camera/GameFirstPersonCamera.js +35 -4
- package/dist/camera/GameInspectionCamera.js +12 -1
- package/dist/camera/GameOrbitCamera.js +44 -1
- package/dist/camera/PlayerFov.d.ts +18 -0
- package/dist/camera/PlayerFov.js +48 -0
- package/dist/camera/cameraBlendMath.d.ts +13 -0
- package/dist/camera/cameraBlendMath.js +36 -0
- package/dist/camera/cameraRigs.d.ts +3 -0
- package/dist/camera/cameraRigs.js +36 -33
- package/dist/camera/fovPreference.d.ts +25 -0
- package/dist/camera/fovPreference.js +73 -0
- package/dist/camera/index.d.ts +3 -0
- package/dist/camera/index.js +3 -0
- package/dist/camera/orbitCameraMath.d.ts +18 -0
- package/dist/camera/orbitCameraMath.js +6 -1
- package/dist/camera/rigMath.d.ts +10 -0
- package/dist/camera/rigMath.js +36 -1
- package/dist/camera/shakeChannel.d.ts +3 -17
- package/dist/camera/shakeChannel.js +2 -21
- package/dist/camera/shakeChannelMath.d.ts +8 -0
- package/dist/camera/shakeChannelMath.js +21 -0
- package/dist/cartridge.d.ts +145 -0
- package/dist/cartridge.js +246 -0
- package/dist/commandSink.d.ts +20 -0
- package/dist/commandSink.js +27 -0
- package/dist/defineGame.js +4 -1
- package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
- package/dist/devtools/CollisionDebugWorld.js +181 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +65 -1
- package/dist/devtools/DevtoolsOverlay.js +383 -41
- package/dist/devtools/collisionDebug.d.ts +57 -0
- package/dist/devtools/collisionDebug.js +127 -0
- package/dist/devtools/collisionDebugMath.d.ts +103 -0
- package/dist/devtools/collisionDebugMath.js +129 -0
- package/dist/environment/Daylight.d.ts +20 -8
- package/dist/environment/Daylight.js +63 -15
- package/dist/environment/EnvironmentScene.js +91 -35
- package/dist/environment/RoadRibbons.d.ts +7 -0
- package/dist/environment/RoadRibbons.js +57 -0
- package/dist/environment/groundPadMath.d.ts +2 -2
- package/dist/environment/groundPadMath.js +1 -1
- package/dist/environment/index.d.ts +2 -1
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +10 -0
- package/dist/environment/skyLightingPolicy.js +6 -0
- package/dist/input/mouseLook.d.ts +27 -0
- package/dist/input/mouseLook.js +35 -0
- package/dist/inputSink.d.ts +18 -0
- package/dist/inputSink.js +35 -0
- package/dist/materialOverride.d.ts +4 -6
- package/dist/materialOverride.js +12 -16
- package/dist/multiplayer.js +7 -3
- package/dist/pointer/PointerProbe.js +6 -2
- package/dist/pointer/pointerService.d.ts +3 -0
- package/dist/pointer/pointerService.js +6 -0
- package/dist/postfx/PostProcessing.d.ts +10 -0
- package/dist/postfx/PostProcessing.js +82 -0
- package/dist/postfx/gradeShader.d.ts +4 -0
- package/dist/postfx/gradeShader.js +64 -0
- package/dist/render/modelRender.d.ts +10 -1
- package/dist/render/modelRender.js +32 -5
- package/dist/render/resolveModel.d.ts +14 -0
- package/dist/render/resolveModel.js +24 -0
- package/dist/settings/QuickControls.d.ts +4 -0
- package/dist/settings/QuickControls.js +42 -0
- package/dist/settings/SettingsChrome.d.ts +1 -0
- package/dist/settings/SettingsChrome.js +10 -0
- package/dist/settings/SettingsMenu.d.ts +6 -0
- package/dist/settings/SettingsMenu.js +148 -0
- package/dist/settings/SettingsRuntime.d.ts +11 -0
- package/dist/settings/SettingsRuntime.js +19 -0
- package/dist/settings/appliedSettings.d.ts +14 -0
- package/dist/settings/appliedSettings.js +27 -0
- package/dist/settings/settingsController.d.ts +20 -0
- package/dist/settings/settingsController.js +165 -0
- package/dist/structures/GeneratedBuilding.d.ts +10 -0
- package/dist/structures/GeneratedBuilding.js +96 -8
- package/dist/structures/index.d.ts +1 -1
- package/dist/structures/index.js +1 -1
- package/dist/terrain/CarvedTerrain.d.ts +5 -1
- package/dist/terrain/CarvedTerrain.js +6 -5
- package/dist/terrain/GrassField.d.ts +3 -1
- package/dist/terrain/GrassField.js +4 -2
- package/dist/terrain/grassBudget.d.ts +3 -0
- package/dist/terrain/grassBudget.js +6 -0
- package/dist/terrain/grassGeometry.js +1 -1
- package/dist/terrain/terrainDetailMaterial.d.ts +14 -0
- package/dist/terrain/terrainDetailMaterial.js +90 -0
- package/dist/terrain/terrainMath.d.ts +8 -0
- package/dist/terrain/terrainMath.js +9 -0
- package/dist/touch/OrientationHint.d.ts +3 -0
- package/dist/touch/OrientationHint.js +13 -0
- package/dist/touch/TouchControlsOverlay.d.ts +17 -1
- package/dist/touch/TouchControlsOverlay.js +125 -9
- package/dist/visibility/CullingProvider.d.ts +21 -0
- package/dist/visibility/CullingProvider.js +134 -0
- package/dist/vision/FrustumSensorHud.d.ts +1 -6
- package/dist/vision/FrustumSensorHud.js +25 -27
- package/dist/vision/frustumSampleEqual.d.ts +2 -0
- package/dist/vision/frustumSampleEqual.js +10 -0
- package/dist/water/Ocean.js +1 -1
- package/dist/water/OceanConfig.d.ts +13 -0
- package/dist/water/OceanConfig.js +25 -17
- package/dist/water/index.d.ts +1 -1
- package/dist/water/index.js +1 -1
- package/dist/weather/FireSpreadLayer.js +7 -2
- package/dist/weather/RainField.d.ts +3 -1
- package/dist/weather/RainField.js +4 -4
- package/dist/weather/SnowField.d.ts +3 -1
- package/dist/weather/SnowField.js +4 -4
- package/dist/weather/fireSpreadPose.d.ts +2 -0
- package/dist/weather/fireSpreadPose.js +4 -0
- package/dist/weather/weatherMath.d.ts +5 -1
- package/dist/weather/weatherMath.js +7 -2
- package/dist/world/DataObjects.d.ts +1 -1
- package/dist/world/DataObjects.js +3 -1
- package/dist/world/SpriteBatch.d.ts +44 -0
- package/dist/world/SpriteBatch.js +112 -0
- package/dist/world/WorldHud.d.ts +6 -1
- package/dist/world/WorldHud.js +95 -48
- package/dist/world/entityPose.d.ts +14 -0
- package/dist/world/entityPose.js +10 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +4 -0
- package/dist/world/worldBarSamples.d.ts +30 -0
- package/dist/world/worldBarSamples.js +57 -0
- package/dist/worldSync.d.ts +10 -0
- package/dist/worldSync.js +19 -0
- package/llms.txt +1522 -1143
- package/package.json +4 -4
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
4
|
import * as THREE from "three";
|
|
5
|
+
import { setBillboardQuaternion } from "./fireSpreadPose.js";
|
|
5
6
|
const HIDDEN = new THREE.Matrix4().makeScale(0, 0, 0);
|
|
6
7
|
export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, flameHeight = 1.6, burningColor = "#ff6a1a", emberColor = "#4a1206", }) {
|
|
7
8
|
const flames = useRef(null);
|
|
@@ -24,6 +25,8 @@ export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, fla
|
|
|
24
25
|
const matrix = useMemo(() => new THREE.Matrix4(), []);
|
|
25
26
|
const position = useMemo(() => new THREE.Vector3(), []);
|
|
26
27
|
const quaternion = useMemo(() => new THREE.Quaternion(), []);
|
|
28
|
+
const identityQuat = useMemo(() => new THREE.Quaternion(), []);
|
|
29
|
+
const euler = useMemo(() => new THREE.Euler(), []);
|
|
27
30
|
const scale = useMemo(() => new THREE.Vector3(1, 1, 1), []);
|
|
28
31
|
useFrame((state) => {
|
|
29
32
|
const flameMesh = flames.current;
|
|
@@ -42,7 +45,7 @@ export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, fla
|
|
|
42
45
|
const groundY = heightAt?.(x, z) ?? 0;
|
|
43
46
|
if (cell.state === "burning") {
|
|
44
47
|
position.set(x, groundY + flameHeight * 0.5, z);
|
|
45
|
-
quaternion
|
|
48
|
+
setBillboardQuaternion(quaternion, euler, state.camera.rotation.y);
|
|
46
49
|
scale.set(1, flicker, 1);
|
|
47
50
|
matrix.compose(position, quaternion, scale);
|
|
48
51
|
flameMesh.setMatrixAt(flameIndex, matrix);
|
|
@@ -50,7 +53,9 @@ export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, fla
|
|
|
50
53
|
}
|
|
51
54
|
else if (cell.state === "burnt") {
|
|
52
55
|
position.set(x, groundY + 0.05, z);
|
|
53
|
-
|
|
56
|
+
scale.set(1, 1, 1);
|
|
57
|
+
matrix.compose(position, identityQuat, scale);
|
|
58
|
+
scorchMesh.setMatrixAt(scorchIndex, matrix);
|
|
54
59
|
scorchIndex += 1;
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -3,6 +3,7 @@ import { type WeatherVector } from "./weatherUniforms.js";
|
|
|
3
3
|
export interface RainFieldProps {
|
|
4
4
|
count?: number;
|
|
5
5
|
density?: number;
|
|
6
|
+
budget?: number;
|
|
6
7
|
volume?: WeatherVector;
|
|
7
8
|
wind?: WeatherVector;
|
|
8
9
|
origin?: WeatherVector;
|
|
@@ -16,5 +17,6 @@ export interface RainFieldProps {
|
|
|
16
17
|
timeScale?: number;
|
|
17
18
|
seed?: number;
|
|
18
19
|
renderOrder?: number;
|
|
20
|
+
frustumCulled?: boolean;
|
|
19
21
|
}
|
|
20
|
-
export declare function RainField({ count, density, volume, wind, origin, followCamera, speed, length, width, opacity, color, lightning, timeScale, seed, renderOrder, }: RainFieldProps): import("react").JSX.Element;
|
|
22
|
+
export declare function RainField({ count, density, budget, volume, wind, origin, followCamera, speed, length, width, opacity, color, lightning, timeScale, seed, renderOrder, frustumCulled, }: RainFieldProps): import("react").JSX.Element;
|
|
@@ -3,12 +3,12 @@ import { useEffect, useMemo } from "react";
|
|
|
3
3
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
import { createWeatherQuadGeometry } from "./weatherGeometry.js";
|
|
6
|
-
import { resolveWeatherInstanceCount } from "./weatherMath.js";
|
|
6
|
+
import { DEFAULT_RAIN_COUNT, DEFAULT_RAIN_DENSITY, resolveWeatherInstanceCount } from "./weatherMath.js";
|
|
7
7
|
import { useWeatherUniformSet } from "./weatherUniforms.js";
|
|
8
8
|
const DEFAULT_VOLUME = [56, 42, 56];
|
|
9
9
|
const DEFAULT_ORIGIN = [0, 0, 0];
|
|
10
10
|
const DEFAULT_RAIN_COLOR = "#b8c4d8";
|
|
11
|
-
export function RainField({ count =
|
|
11
|
+
export function RainField({ count = DEFAULT_RAIN_COUNT, density = DEFAULT_RAIN_DENSITY, budget, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 22, length = 1.35, width = 0.018, opacity = 0.48, color = DEFAULT_RAIN_COLOR, lightning, timeScale, seed = 11939, renderOrder = 10, frustumCulled, }) {
|
|
12
12
|
const { camera } = useThree();
|
|
13
13
|
const shared = useWeatherUniformSet({ wind, lightning, timeScale });
|
|
14
14
|
const geometry = useMemo(() => createWeatherQuadGeometry(count, seed), [count, seed]);
|
|
@@ -97,11 +97,11 @@ export function RainField({ count = 8000, density = 0.45, volume = DEFAULT_VOLUM
|
|
|
97
97
|
uniforms.uWidth.value = width;
|
|
98
98
|
uniforms.uOpacity.value = opacity;
|
|
99
99
|
uniforms.uColor.value.set(color);
|
|
100
|
-
geometry.instanceCount = resolveWeatherInstanceCount(count, density);
|
|
100
|
+
geometry.instanceCount = resolveWeatherInstanceCount(count, density, budget);
|
|
101
101
|
});
|
|
102
102
|
useEffect(() => () => {
|
|
103
103
|
geometry.dispose();
|
|
104
104
|
material.dispose();
|
|
105
105
|
}, [geometry, material]);
|
|
106
|
-
return _jsx("mesh", { geometry: geometry, material: material, frustumCulled:
|
|
106
|
+
return (_jsx("mesh", { geometry: geometry, material: material, frustumCulled: frustumCulled ?? !followCamera, renderOrder: renderOrder }));
|
|
107
107
|
}
|
|
@@ -3,6 +3,7 @@ import { type WeatherVector } from "./weatherUniforms.js";
|
|
|
3
3
|
export interface SnowFieldProps {
|
|
4
4
|
count?: number;
|
|
5
5
|
density?: number;
|
|
6
|
+
budget?: number;
|
|
6
7
|
volume?: WeatherVector;
|
|
7
8
|
wind?: WeatherVector;
|
|
8
9
|
origin?: WeatherVector;
|
|
@@ -15,5 +16,6 @@ export interface SnowFieldProps {
|
|
|
15
16
|
timeScale?: number;
|
|
16
17
|
seed?: number;
|
|
17
18
|
renderOrder?: number;
|
|
19
|
+
frustumCulled?: boolean;
|
|
18
20
|
}
|
|
19
|
-
export declare function SnowField({ count, density, volume, wind, origin, followCamera, speed, size, sway, opacity, color, timeScale, seed, renderOrder, }: SnowFieldProps): import("react").JSX.Element;
|
|
21
|
+
export declare function SnowField({ count, density, budget, volume, wind, origin, followCamera, speed, size, sway, opacity, color, timeScale, seed, renderOrder, frustumCulled, }: SnowFieldProps): import("react").JSX.Element;
|
|
@@ -3,12 +3,12 @@ import { useEffect, useMemo } from "react";
|
|
|
3
3
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
import { createWeatherQuadGeometry } from "./weatherGeometry.js";
|
|
6
|
-
import { resolveWeatherInstanceCount } from "./weatherMath.js";
|
|
6
|
+
import { DEFAULT_SNOW_COUNT, DEFAULT_SNOW_DENSITY, resolveWeatherInstanceCount } from "./weatherMath.js";
|
|
7
7
|
import { useWeatherUniformSet } from "./weatherUniforms.js";
|
|
8
8
|
const DEFAULT_VOLUME = [52, 38, 52];
|
|
9
9
|
const DEFAULT_ORIGIN = [0, 0, 0];
|
|
10
10
|
const DEFAULT_SNOW_COLOR = "#ffffff";
|
|
11
|
-
export function SnowField({ count =
|
|
11
|
+
export function SnowField({ count = DEFAULT_SNOW_COUNT, density = DEFAULT_SNOW_DENSITY, budget, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 3.2, size = 0.11, sway = 0.62, opacity = 0.86, color = DEFAULT_SNOW_COLOR, timeScale, seed = 72931, renderOrder = 11, frustumCulled, }) {
|
|
12
12
|
const { camera } = useThree();
|
|
13
13
|
const shared = useWeatherUniformSet({ wind, timeScale });
|
|
14
14
|
const geometry = useMemo(() => createWeatherQuadGeometry(count, seed), [count, seed]);
|
|
@@ -98,11 +98,11 @@ export function SnowField({ count = 6000, density = 0.5, volume = DEFAULT_VOLUME
|
|
|
98
98
|
uniforms.uSway.value = sway;
|
|
99
99
|
uniforms.uOpacity.value = opacity;
|
|
100
100
|
uniforms.uColor.value.set(color);
|
|
101
|
-
geometry.instanceCount = resolveWeatherInstanceCount(count, density);
|
|
101
|
+
geometry.instanceCount = resolveWeatherInstanceCount(count, density, budget);
|
|
102
102
|
});
|
|
103
103
|
useEffect(() => () => {
|
|
104
104
|
geometry.dispose();
|
|
105
105
|
material.dispose();
|
|
106
106
|
}, [geometry, material]);
|
|
107
|
-
return _jsx("mesh", { geometry: geometry, material: material, frustumCulled:
|
|
107
|
+
return (_jsx("mesh", { geometry: geometry, material: material, frustumCulled: frustumCulled ?? !followCamera, renderOrder: renderOrder }));
|
|
108
108
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
export declare const DEFAULT_RAIN_COUNT = 2000;
|
|
2
|
+
export declare const DEFAULT_SNOW_COUNT = 1500;
|
|
3
|
+
export declare const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
|
+
export declare const DEFAULT_SNOW_DENSITY = 0.5;
|
|
1
5
|
export declare function clampWeatherRatio(value: number): number;
|
|
2
|
-
export declare function resolveWeatherInstanceCount(maxCount: number, density: number): number;
|
|
6
|
+
export declare function resolveWeatherInstanceCount(maxCount: number, density: number, budget?: number): number;
|
|
3
7
|
export interface WeatherSeedAttributes {
|
|
4
8
|
spawn: Float32Array;
|
|
5
9
|
drift: Float32Array;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export const DEFAULT_RAIN_COUNT = 2000;
|
|
2
|
+
export const DEFAULT_SNOW_COUNT = 1500;
|
|
3
|
+
export const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
|
+
export const DEFAULT_SNOW_DENSITY = 0.5;
|
|
1
5
|
export function clampWeatherRatio(value) {
|
|
2
6
|
if (!Number.isFinite(value))
|
|
3
7
|
return 0;
|
|
@@ -7,9 +11,10 @@ export function clampWeatherRatio(value) {
|
|
|
7
11
|
return 1;
|
|
8
12
|
return value;
|
|
9
13
|
}
|
|
10
|
-
export function resolveWeatherInstanceCount(maxCount, density) {
|
|
14
|
+
export function resolveWeatherInstanceCount(maxCount, density, budget) {
|
|
11
15
|
const safeMax = Math.max(0, Math.floor(maxCount));
|
|
12
|
-
|
|
16
|
+
const capped = budget === undefined ? safeMax : Math.min(safeMax, Math.max(0, Math.floor(budget)));
|
|
17
|
+
return Math.floor(capped * clampWeatherRatio(density));
|
|
13
18
|
}
|
|
14
19
|
export function createWeatherSeedAttributes(maxCount, seed) {
|
|
15
20
|
const count = Math.max(0, Math.floor(maxCount));
|
|
@@ -41,4 +41,4 @@ export interface DataObjectsProps<T> {
|
|
|
41
41
|
height: number;
|
|
42
42
|
}) => ReactNode;
|
|
43
43
|
}
|
|
44
|
-
export declare function DataObjects<T>({ data, position, height, color, cellSize, hovered, hoverColor, onHover, grow, castShadow, receiveShadow, renderItem, }: DataObjectsProps<T>): import("react").JSX.Element;
|
|
44
|
+
export declare function DataObjects<T>({ data, position, height, color, cellSize, hovered, hoverColor, onHover, grow, castShadow, receiveShadow, renderItem, }: DataObjectsProps<T>): import("react").JSX.Element | null;
|
|
@@ -71,5 +71,7 @@ export function DataObjects({ data, position, height, color, cellSize = 0.28, ho
|
|
|
71
71
|
return (_jsx("group", { position: [x, 0, z], children: renderItem(item, i, { x, z, height: height(item, i) }) }, i));
|
|
72
72
|
}) }));
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
if (capacity === 0)
|
|
75
|
+
return null;
|
|
76
|
+
return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, material, capacity], castShadow: castShadow, receiveShadow: receiveShadow, frustumCulled: false, onPointerMove: onHover === undefined ? undefined : handleMove, onPointerOut: onHover === undefined ? undefined : () => onHover(null) }, capacity));
|
|
75
77
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface SpriteBatchInstance {
|
|
2
|
+
/** World X position. */
|
|
3
|
+
x: number;
|
|
4
|
+
/** World Y position. */
|
|
5
|
+
y: number;
|
|
6
|
+
/** World Z position. Defaults to 0. */
|
|
7
|
+
z?: number;
|
|
8
|
+
/** Atlas frame index, row-major from the sheet's top-left. Defaults to 0. */
|
|
9
|
+
frame?: number;
|
|
10
|
+
/** Uniform scale multiplier for the unit quad. Defaults to 1. */
|
|
11
|
+
scale?: number;
|
|
12
|
+
/** Rotation in radians about the plane's facing axis. Defaults to 0. */
|
|
13
|
+
rotation?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface SpriteBatchProps {
|
|
16
|
+
/** Sprite sheet / atlas texture URL. */
|
|
17
|
+
url: string;
|
|
18
|
+
/** Atlas column count. Defaults to 1. */
|
|
19
|
+
columns?: number;
|
|
20
|
+
/** Atlas row count. Defaults to 1. */
|
|
21
|
+
rows?: number;
|
|
22
|
+
/** Max instances the InstancedMesh is allocated for. Defaults to 1024. */
|
|
23
|
+
capacity?: number;
|
|
24
|
+
/** Per-frame instance source, called once inside useFrame. */
|
|
25
|
+
instances: () => readonly SpriteBatchInstance[];
|
|
26
|
+
/** Facing plane when not billboarding: "xy" faces +Z (side-scroll/ortho), "xz" lies flat facing +Y. Defaults to "xy". */
|
|
27
|
+
plane?: "xy" | "xz";
|
|
28
|
+
/** When true, every instance orients to face the camera each frame, overriding `plane`. Defaults to false. */
|
|
29
|
+
billboard?: boolean;
|
|
30
|
+
/** Use nearest-neighbor filtering for crisp pixel art. Defaults to true. */
|
|
31
|
+
pixelated?: boolean;
|
|
32
|
+
/** Alpha test cutoff for transparent pixels. Defaults to 0.08. */
|
|
33
|
+
alphaTest?: number;
|
|
34
|
+
/** Material opacity. Defaults to 1. */
|
|
35
|
+
opacity?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renders a sprite sheet / tile atlas as a single InstancedMesh — one draw call for the whole
|
|
39
|
+
* batch. Each instance picks its atlas frame via a per-instance UV offset attribute patched into
|
|
40
|
+
* the material's vertex shader, so platformer/puzzle-grid presentation never needs one draw call
|
|
41
|
+
* per sprite. Transforms and UV offsets are written directly into the mesh's typed arrays each
|
|
42
|
+
* frame from a plain instance list (bodies never touch the per-entity React path).
|
|
43
|
+
*/
|
|
44
|
+
export declare function SpriteBatch({ url, columns, rows, capacity, instances, plane, billboard, pixelated, alphaTest, opacity, }: SpriteBatchProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame, useLoader, useThree } from "@react-three/fiber";
|
|
3
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
const DEFAULT_CAPACITY = 1024;
|
|
6
|
+
const DEFAULT_ALPHA_TEST = 0.08;
|
|
7
|
+
const XZ_BASE_QUATERNION = new THREE.Quaternion().setFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0));
|
|
8
|
+
const LOCAL_NORMAL_AXIS = new THREE.Vector3(0, 0, 1);
|
|
9
|
+
/**
|
|
10
|
+
* Renders a sprite sheet / tile atlas as a single InstancedMesh — one draw call for the whole
|
|
11
|
+
* batch. Each instance picks its atlas frame via a per-instance UV offset attribute patched into
|
|
12
|
+
* the material's vertex shader, so platformer/puzzle-grid presentation never needs one draw call
|
|
13
|
+
* per sprite. Transforms and UV offsets are written directly into the mesh's typed arrays each
|
|
14
|
+
* frame from a plain instance list (bodies never touch the per-entity React path).
|
|
15
|
+
*/
|
|
16
|
+
export function SpriteBatch({ url, columns = 1, rows = 1, capacity = DEFAULT_CAPACITY, instances, plane = "xy", billboard = false, pixelated = true, alphaTest = DEFAULT_ALPHA_TEST, opacity = 1, }) {
|
|
17
|
+
const meshRef = useRef(null);
|
|
18
|
+
const { camera } = useThree();
|
|
19
|
+
const texture = useLoader(THREE.TextureLoader, url);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
22
|
+
if (pixelated) {
|
|
23
|
+
texture.magFilter = THREE.NearestFilter;
|
|
24
|
+
texture.minFilter = THREE.NearestFilter;
|
|
25
|
+
texture.anisotropy = 1;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
texture.anisotropy = 4;
|
|
29
|
+
}
|
|
30
|
+
texture.needsUpdate = true;
|
|
31
|
+
}, [texture, pixelated]);
|
|
32
|
+
const geometry = useMemo(() => new THREE.PlaneGeometry(1, 1), []);
|
|
33
|
+
const spriteUvOffset = useMemo(() => new THREE.InstancedBufferAttribute(new Float32Array(capacity * 2), 2), [capacity]);
|
|
34
|
+
const material = useMemo(() => {
|
|
35
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
36
|
+
map: texture,
|
|
37
|
+
transparent: true,
|
|
38
|
+
alphaTest,
|
|
39
|
+
depthWrite: false,
|
|
40
|
+
opacity,
|
|
41
|
+
});
|
|
42
|
+
mat.onBeforeCompile = (shader) => {
|
|
43
|
+
shader.uniforms.spriteUvScale = { value: new THREE.Vector2(1 / columns, 1 / rows) };
|
|
44
|
+
shader.vertexShader = shader.vertexShader
|
|
45
|
+
.replace("#include <common>", `#include <common>
|
|
46
|
+
attribute vec2 spriteUvOffset;
|
|
47
|
+
uniform vec2 spriteUvScale;`)
|
|
48
|
+
.replace("#include <uv_vertex>", `#include <uv_vertex>
|
|
49
|
+
#ifdef USE_MAP
|
|
50
|
+
vMapUv = vMapUv * spriteUvScale + spriteUvOffset;
|
|
51
|
+
#endif`);
|
|
52
|
+
};
|
|
53
|
+
mat.customProgramCacheKey = () => `jgengine-sprite-batch-${columns}x${rows}`;
|
|
54
|
+
return mat;
|
|
55
|
+
}, [texture, columns, rows, alphaTest, opacity]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const mesh = meshRef.current;
|
|
58
|
+
if (mesh === null)
|
|
59
|
+
return;
|
|
60
|
+
mesh.geometry.setAttribute("spriteUvOffset", spriteUvOffset);
|
|
61
|
+
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
|
|
62
|
+
spriteUvOffset.setUsage(THREE.DynamicDrawUsage);
|
|
63
|
+
}, [spriteUvOffset]);
|
|
64
|
+
const baseQuaternion = useMemo(() => (plane === "xz" ? XZ_BASE_QUATERNION.clone() : new THREE.Quaternion()), [plane]);
|
|
65
|
+
const position = useMemo(() => new THREE.Vector3(), []);
|
|
66
|
+
const quaternion = useMemo(() => new THREE.Quaternion(), []);
|
|
67
|
+
const rotationQuaternion = useMemo(() => new THREE.Quaternion(), []);
|
|
68
|
+
const scaleVector = useMemo(() => new THREE.Vector3(), []);
|
|
69
|
+
const matrix = useMemo(() => new THREE.Matrix4(), []);
|
|
70
|
+
useFrame(() => {
|
|
71
|
+
const mesh = meshRef.current;
|
|
72
|
+
if (mesh === null)
|
|
73
|
+
return;
|
|
74
|
+
const list = instances();
|
|
75
|
+
const count = Math.min(list.length, capacity);
|
|
76
|
+
mesh.count = count;
|
|
77
|
+
const matrixArray = mesh.instanceMatrix.array;
|
|
78
|
+
const uvArray = spriteUvOffset.array;
|
|
79
|
+
for (let i = 0; i < count; i += 1) {
|
|
80
|
+
const instance = list[i];
|
|
81
|
+
position.set(instance.x, instance.y, instance.z ?? 0);
|
|
82
|
+
if (billboard) {
|
|
83
|
+
quaternion.copy(camera.quaternion);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
quaternion.copy(baseQuaternion);
|
|
87
|
+
}
|
|
88
|
+
const rotation = instance.rotation ?? 0;
|
|
89
|
+
if (rotation !== 0) {
|
|
90
|
+
rotationQuaternion.setFromAxisAngle(LOCAL_NORMAL_AXIS, rotation);
|
|
91
|
+
quaternion.multiply(rotationQuaternion);
|
|
92
|
+
}
|
|
93
|
+
const scale = instance.scale ?? 1;
|
|
94
|
+
scaleVector.set(scale, scale, scale);
|
|
95
|
+
matrix.compose(position, quaternion, scaleVector);
|
|
96
|
+
matrix.toArray(matrixArray, i * 16);
|
|
97
|
+
const frame = instance.frame ?? 0;
|
|
98
|
+
const col = frame % columns;
|
|
99
|
+
const row = Math.floor(frame / columns);
|
|
100
|
+
const uo = i * 2;
|
|
101
|
+
uvArray[uo] = col / columns;
|
|
102
|
+
uvArray[uo + 1] = 1 - (row + 1) / rows;
|
|
103
|
+
}
|
|
104
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
105
|
+
spriteUvOffset.needsUpdate = true;
|
|
106
|
+
});
|
|
107
|
+
useEffect(() => () => {
|
|
108
|
+
geometry.dispose();
|
|
109
|
+
material.dispose();
|
|
110
|
+
}, [geometry, material]);
|
|
111
|
+
return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, material, capacity], frustumCulled: false, castShadow: false, receiveShadow: false }));
|
|
112
|
+
}
|
package/dist/world/WorldHud.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { SceneEntity } from "@jgengine/core/scene/entityStore";
|
|
2
2
|
import type { CatalogEntityRole } from "@jgengine/core/runtime/gameContext";
|
|
3
|
-
export
|
|
3
|
+
export type { WorldBarSample } from "./worldBarSamples.js";
|
|
4
|
+
export { collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
5
|
+
export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
6
|
+
export declare function WorldEntityBars({ statId, height, roles, resolveRole, maxDistance, }: {
|
|
4
7
|
statId: string;
|
|
5
8
|
height?: number;
|
|
6
9
|
roles?: readonly CatalogEntityRole[];
|
|
7
10
|
resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined;
|
|
11
|
+
/** Hide bars for entities farther than this from the player (world units). Default 60. */
|
|
12
|
+
maxDistance?: number;
|
|
8
13
|
}): import("react").JSX.Element;
|
|
9
14
|
export declare function WorldFloatText({ height, lifeMs }: {
|
|
10
15
|
height?: number;
|
package/dist/world/WorldHud.js
CHANGED
|
@@ -3,27 +3,43 @@ import { Html, Line } from "@react-three/drei";
|
|
|
3
3
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
4
4
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import * as THREE from "three";
|
|
6
|
-
import { worldHealthBarAllowsRole } from "@jgengine/core/game/playableGame";
|
|
7
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
8
|
-
import {
|
|
7
|
+
import { useCameraShake } from "../camera/shakeChannel.js";
|
|
8
|
+
import { readFirstPersonMuzzle } from "../camera/GameFirstPersonCamera.js";
|
|
9
9
|
import { resolveFloatTextStyle } from "./floatTextStyle.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const percent = range <= 0 ? 0 : Math.max(0, Math.min(1, (stat.current - stat.min) / range));
|
|
17
|
-
return (_jsx(Html, { position: [entity.position[0], entity.position[1] + height, entity.position[2]], center: true, distanceFactor: 12, zIndexRange: [20, 0], children: _jsx("div", { className: "h-2.5 w-28 overflow-hidden rounded-sm border border-black/70 bg-black/70 shadow", children: _jsx("div", { className: "h-full bg-gradient-to-r from-rose-600 to-red-400", style: { width: `${percent * 100}%` } }) }) }));
|
|
10
|
+
import { collectWorldBarSamples, paintWorldBarSamples, } from "./worldBarSamples.js";
|
|
11
|
+
import { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
12
|
+
export { collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
13
|
+
export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
14
|
+
function pinOverlayToViewport(_el, _camera, size) {
|
|
15
|
+
return [size.width / 2, size.height / 2];
|
|
18
16
|
}
|
|
19
|
-
export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, }) {
|
|
17
|
+
export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxDistance = 60, }) {
|
|
20
18
|
const ctx = useGameContext();
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const camera = useThree((state) => state.camera);
|
|
20
|
+
const size = useThree((state) => state.size);
|
|
21
|
+
const gl = useThree((state) => state.gl);
|
|
22
|
+
const canvasRef = useRef(null);
|
|
23
|
+
const samplesRef = useRef([]);
|
|
24
|
+
const projectRef = useRef(new THREE.Vector3());
|
|
25
|
+
useFrame(() => {
|
|
26
|
+
const canvas = canvasRef.current;
|
|
27
|
+
if (canvas === null)
|
|
28
|
+
return;
|
|
29
|
+
const dpr = Math.min(2, gl.getPixelRatio());
|
|
30
|
+
const cssW = size.width;
|
|
31
|
+
const cssH = size.height;
|
|
32
|
+
const pixelW = Math.max(1, Math.floor(cssW * dpr));
|
|
33
|
+
const pixelH = Math.max(1, Math.floor(cssH * dpr));
|
|
34
|
+
if (canvas.width !== pixelW || canvas.height !== pixelH) {
|
|
35
|
+
canvas.width = pixelW;
|
|
36
|
+
canvas.height = pixelH;
|
|
37
|
+
}
|
|
38
|
+
camera.updateMatrixWorld();
|
|
39
|
+
collectWorldBarSamples(ctx, statId, height, roles, resolveRole, camera, { width: cssW, height: cssH }, samplesRef.current, projectRef.current, maxDistance);
|
|
40
|
+
paintWorldBarSamples(canvas, samplesRef.current, dpr);
|
|
41
|
+
});
|
|
42
|
+
return (_jsx(Html, { fullscreen: true, calculatePosition: pinOverlayToViewport, zIndexRange: [20, 0], style: { pointerEvents: "none" }, children: _jsx("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block", pointerEvents: "none" } }) }));
|
|
27
43
|
}
|
|
28
44
|
function FloatTextItem({ event, lifeMs }) {
|
|
29
45
|
const [shown, setShown] = useState(false);
|
|
@@ -47,12 +63,24 @@ export function WorldFloatText({ height = 1.9, lifeMs = 950 }) {
|
|
|
47
63
|
const ctx = useGameContext();
|
|
48
64
|
const [floaters, setFloaters] = useState([]);
|
|
49
65
|
const nextId = useRef(0);
|
|
66
|
+
const timers = useRef(new Set());
|
|
50
67
|
useEffect(() => {
|
|
51
|
-
|
|
68
|
+
const pending = timers.current;
|
|
69
|
+
const off = ctx.game.events.on("entity.floatText", (event) => {
|
|
52
70
|
const id = nextId.current++;
|
|
53
71
|
setFloaters((current) => [...current, { id, position: event.position, event }]);
|
|
54
|
-
|
|
72
|
+
const timer = window.setTimeout(() => {
|
|
73
|
+
pending.delete(timer);
|
|
74
|
+
setFloaters((current) => current.filter((floater) => floater.id !== id));
|
|
75
|
+
}, lifeMs);
|
|
76
|
+
pending.add(timer);
|
|
55
77
|
});
|
|
78
|
+
return () => {
|
|
79
|
+
off();
|
|
80
|
+
for (const timer of pending)
|
|
81
|
+
window.clearTimeout(timer);
|
|
82
|
+
pending.clear();
|
|
83
|
+
};
|
|
56
84
|
}, [ctx, lifeMs]);
|
|
57
85
|
return (_jsx(_Fragment, { children: floaters.map((floater) => (_jsx(Html, { position: [floater.position[0], floater.position[1] + height, floater.position[2]], center: true, distanceFactor: 12, zIndexRange: [30, 0], children: _jsx(FloatTextItem, { event: floater.event, lifeMs: lifeMs }) }, floater.id))) }));
|
|
58
86
|
}
|
|
@@ -65,73 +93,92 @@ function TelegraphGeometry({ shape }) {
|
|
|
65
93
|
return _jsx("circleGeometry", { args: [shape.radius, 40, -shape.angle / 2, shape.angle] });
|
|
66
94
|
return _jsx("planeGeometry", { args: [shape.width, shape.length] });
|
|
67
95
|
}
|
|
68
|
-
function TelegraphDecal({ active
|
|
69
|
-
const
|
|
96
|
+
function TelegraphDecal({ active }) {
|
|
97
|
+
const materialRef = useRef(null);
|
|
70
98
|
const [x, y, z] = active.event.position;
|
|
71
99
|
const dir = active.event.dir ?? 0;
|
|
72
100
|
const shape = active.event.shape;
|
|
73
101
|
const forwardOffset = shape.kind === "line" ? shape.length / 2 : 0;
|
|
74
102
|
const color = active.event.kind === "danger" ? "#ef4444" : "#f59e0b";
|
|
75
|
-
|
|
76
|
-
|
|
103
|
+
useFrame(() => {
|
|
104
|
+
const material = materialRef.current;
|
|
105
|
+
if (material === null)
|
|
106
|
+
return;
|
|
107
|
+
material.opacity = telegraphPulseOpacity(active.bornMs, active.event.windupMs, performance.now());
|
|
108
|
+
});
|
|
109
|
+
return (_jsxs("mesh", { position: [x + Math.sin(dir) * forwardOffset, y + 0.06, z + Math.cos(dir) * forwardOffset], rotation: [-Math.PI / 2, 0, shape.kind === "line" || shape.kind === "cone" ? -dir : 0], renderOrder: 999, children: [_jsx(TelegraphGeometry, { shape: shape }), _jsx("meshBasicMaterial", { ref: materialRef, color: color, transparent: true, opacity: 0.45, side: THREE.DoubleSide, depthWrite: false, depthTest: false, toneMapped: false })] }));
|
|
77
110
|
}
|
|
78
111
|
export function WorldTelegraphs() {
|
|
79
112
|
const ctx = useGameContext();
|
|
80
113
|
const [telegraphs, setTelegraphs] = useState([]);
|
|
81
114
|
const key = useRef(0);
|
|
82
|
-
const
|
|
83
|
-
useFrame(() => setNowMs(performance.now()));
|
|
115
|
+
const timers = useRef(new Set());
|
|
84
116
|
useEffect(() => {
|
|
85
|
-
|
|
117
|
+
const pending = timers.current;
|
|
118
|
+
const off = ctx.game.events.on("combat.telegraph", (event) => {
|
|
86
119
|
const entry = { key: key.current++, event, bornMs: performance.now() };
|
|
87
120
|
setTelegraphs((current) => [...current, entry]);
|
|
88
|
-
|
|
121
|
+
const timer = window.setTimeout(() => {
|
|
122
|
+
pending.delete(timer);
|
|
123
|
+
setTelegraphs((current) => current.filter((t) => t.key !== entry.key));
|
|
124
|
+
}, event.windupMs + 120);
|
|
125
|
+
pending.add(timer);
|
|
89
126
|
});
|
|
127
|
+
const offCancel = ctx.game.events.on("combat.telegraphCancelled", (event) => {
|
|
128
|
+
setTelegraphs((current) => current.filter((t) => t.event.id !== event.id));
|
|
129
|
+
});
|
|
130
|
+
return () => {
|
|
131
|
+
off();
|
|
132
|
+
offCancel();
|
|
133
|
+
for (const timer of pending)
|
|
134
|
+
window.clearTimeout(timer);
|
|
135
|
+
pending.clear();
|
|
136
|
+
};
|
|
90
137
|
}, [ctx]);
|
|
91
|
-
return (_jsx(_Fragment, { children: telegraphs.map((active) => (_jsx(TelegraphDecal, { active: active
|
|
138
|
+
return (_jsx(_Fragment, { children: telegraphs.map((active) => (_jsx(TelegraphDecal, { active: active }, active.key))) }));
|
|
92
139
|
}
|
|
93
140
|
export function CombatCameraShake() {
|
|
94
141
|
const ctx = useGameContext();
|
|
95
|
-
const
|
|
96
|
-
const trauma = useRef(0);
|
|
97
|
-
const decay = useRef(4);
|
|
142
|
+
const shake = useCameraShake();
|
|
98
143
|
useEffect(() => {
|
|
99
144
|
return ctx.game.events.on("combat.hitReaction", (event) => {
|
|
100
145
|
if (event.shake === undefined)
|
|
101
146
|
return;
|
|
102
|
-
|
|
103
|
-
decay.current = event.shake.decay;
|
|
147
|
+
shake.shake(event.shake.amplitude, event.shake.decay);
|
|
104
148
|
});
|
|
105
|
-
}, [ctx]);
|
|
106
|
-
useFrame((_state, dt) => {
|
|
107
|
-
if (trauma.current <= 0.0001)
|
|
108
|
-
return;
|
|
109
|
-
const magnitude = trauma.current * trauma.current;
|
|
110
|
-
camera.position.x += (Math.random() * 2 - 1) * magnitude * 0.4;
|
|
111
|
-
camera.position.y += (Math.random() * 2 - 1) * magnitude * 0.4;
|
|
112
|
-
trauma.current = Math.max(0, trauma.current - decay.current * dt);
|
|
113
|
-
});
|
|
149
|
+
}, [ctx, shake]);
|
|
114
150
|
return null;
|
|
115
151
|
}
|
|
116
152
|
export function ProjectileTracers({ lifeMs = 130 }) {
|
|
117
153
|
const ctx = useGameContext();
|
|
118
154
|
const [tracers, setTracers] = useState([]);
|
|
119
155
|
const nextId = useRef(0);
|
|
156
|
+
const timers = useRef(new Set());
|
|
120
157
|
useEffect(() => {
|
|
121
|
-
|
|
158
|
+
const unsub = ctx.game.events.on("projectile.settled", (event) => {
|
|
122
159
|
const id = nextId.current++;
|
|
160
|
+
const start = new THREE.Vector3(event.origin[0], event.origin[1], event.origin[2]);
|
|
161
|
+
if (event.from === ctx.player.userId)
|
|
162
|
+
readFirstPersonMuzzle(start);
|
|
123
163
|
setTracers((current) => [
|
|
124
164
|
...current,
|
|
125
165
|
{
|
|
126
166
|
id,
|
|
127
|
-
points: [
|
|
128
|
-
new THREE.Vector3(event.origin[0], event.origin[1] + MUZZLE_HEIGHT, event.origin[2]),
|
|
129
|
-
new THREE.Vector3(event.at[0], event.at[1], event.at[2]),
|
|
130
|
-
],
|
|
167
|
+
points: [start, new THREE.Vector3(event.at[0], event.at[1], event.at[2])],
|
|
131
168
|
},
|
|
132
169
|
]);
|
|
133
|
-
|
|
170
|
+
const handle = setTimeout(() => {
|
|
171
|
+
timers.current.delete(handle);
|
|
172
|
+
setTracers((current) => current.filter((tracer) => tracer.id !== id));
|
|
173
|
+
}, lifeMs);
|
|
174
|
+
timers.current.add(handle);
|
|
134
175
|
});
|
|
176
|
+
return () => {
|
|
177
|
+
unsub();
|
|
178
|
+
for (const handle of timers.current)
|
|
179
|
+
clearTimeout(handle);
|
|
180
|
+
timers.current.clear();
|
|
181
|
+
};
|
|
135
182
|
}, [ctx, lifeMs]);
|
|
136
183
|
return (_jsx(_Fragment, { children: tracers.map((tracer) => (_jsx(Line, { points: tracer.points, color: "#fde68a", lineWidth: 2, transparent: true, opacity: 0.85 }, tracer.id))) }));
|
|
137
184
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PoseWritable {
|
|
2
|
+
position: {
|
|
3
|
+
set(x: number, y: number, z: number): void;
|
|
4
|
+
};
|
|
5
|
+
rotation: {
|
|
6
|
+
y: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface PoseSource {
|
|
10
|
+
position: readonly [number, number, number];
|
|
11
|
+
rotationY: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function writeEntityPose(target: PoseWritable, source: PoseSource): void;
|
|
14
|
+
export declare function posesEqual(a: PoseSource, b: PoseSource): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function writeEntityPose(target, source) {
|
|
2
|
+
target.position.set(source.position[0], source.position[1], source.position[2]);
|
|
3
|
+
target.rotation.y = source.rotationY;
|
|
4
|
+
}
|
|
5
|
+
export function posesEqual(a, b) {
|
|
6
|
+
return (a.position[0] === b.position[0] &&
|
|
7
|
+
a.position[1] === b.position[1] &&
|
|
8
|
+
a.position[2] === b.position[2] &&
|
|
9
|
+
a.rotationY === b.rotationY);
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function telegraphPulseOpacity(bornMs: number, windupMs: number, nowMs: number): number;
|