@jgengine/shell 0.7.0 → 0.9.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 +63 -0
- package/README.md +37 -1
- package/dist/GameHost.d.ts +12 -0
- package/dist/GameHost.js +33 -0
- package/dist/GamePlayer.d.ts +11 -0
- package/dist/GamePlayer.js +24 -0
- package/dist/GamePlayerShell.d.ts +36 -1
- package/dist/GamePlayerShell.js +751 -80
- package/dist/GameUiPreview.js +2 -1
- package/dist/audio/AudioComponents.js +25 -8
- package/dist/audio/audioEngine.d.ts +1 -0
- package/dist/audio/audioEngine.js +4 -0
- package/dist/behaviour.d.ts +12 -0
- package/dist/behaviour.js +27 -0
- 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.d.ts +8 -3
- package/dist/camera/GameCameraRig.js +28 -14
- package/dist/camera/GameFirstPersonCamera.js +16 -0
- package/dist/camera/GameInspectionCamera.d.ts +6 -0
- package/dist/camera/GameInspectionCamera.js +24 -0
- package/dist/camera/GameOrbitCamera.js +10 -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 +8 -1
- package/dist/camera/cameraRigs.js +78 -38
- package/dist/camera/fovPreference.d.ts +25 -0
- package/dist/camera/fovPreference.js +73 -0
- package/dist/camera/index.d.ts +7 -2
- package/dist/camera/index.js +7 -2
- package/dist/camera/inspectionCameraMath.d.ts +25 -0
- package/dist/camera/inspectionCameraMath.js +44 -0
- package/dist/camera/rigMath.d.ts +50 -1
- package/dist/camera/rigMath.js +91 -1
- package/dist/camera/rigResolve.d.ts +15 -0
- package/dist/camera/rigResolve.js +52 -0
- 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 +245 -0
- package/dist/defineGame.d.ts +16 -0
- package/dist/defineGame.js +52 -0
- package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
- package/dist/devtools/CollisionDebugWorld.js +180 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +78 -0
- package/dist/devtools/DevtoolsOverlay.js +676 -0
- package/dist/devtools/collisionDebug.d.ts +57 -0
- package/dist/devtools/collisionDebug.js +127 -0
- package/dist/devtools/collisionDebugMath.d.ts +102 -0
- package/dist/devtools/collisionDebugMath.js +128 -0
- package/dist/environment/Daylight.d.ts +56 -0
- package/dist/environment/Daylight.js +101 -0
- package/dist/environment/EnvironmentScene.js +87 -48
- package/dist/environment/GroundPad.d.ts +7 -0
- package/dist/environment/GroundPad.js +12 -0
- package/dist/environment/daylightCycle.d.ts +26 -0
- package/dist/environment/daylightCycle.js +116 -0
- package/dist/environment/groundPadMath.d.ts +13 -0
- package/dist/environment/groundPadMath.js +10 -0
- package/dist/environment/index.d.ts +3 -0
- package/dist/environment/index.js +3 -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/materialOverride.d.ts +6 -0
- package/dist/materialOverride.js +28 -0
- package/dist/multiplayer.d.ts +16 -9
- package/dist/multiplayer.js +62 -12
- package/dist/pointer/PointerProbe.js +20 -3
- package/dist/pointer/pointerService.d.ts +5 -0
- package/dist/pointer/pointerService.js +51 -24
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +3 -1
- package/dist/render/modelRender.d.ts +23 -0
- package/dist/render/modelRender.js +106 -0
- 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 +2 -1
- package/dist/terrain/CarvedTerrain.js +3 -3
- 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/index.d.ts +1 -1
- package/dist/terrain/index.js +1 -1
- package/dist/terrain/terrainMath.d.ts +9 -0
- package/dist/terrain/terrainMath.js +17 -2
- package/dist/touch/OrientationHint.d.ts +3 -0
- package/dist/touch/OrientationHint.js +13 -0
- package/dist/touch/TouchControlsOverlay.d.ts +34 -0
- package/dist/touch/TouchControlsOverlay.js +279 -0
- 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/OceanShader.d.ts +1 -1
- package/dist/water/OceanShader.js +3 -3
- 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 +44 -0
- package/dist/world/DataObjects.js +77 -0
- package/dist/world/GridWorldScene.d.ts +10 -0
- package/dist/world/GridWorldScene.js +54 -0
- package/dist/world/SpriteBatch.d.ts +44 -0
- package/dist/world/SpriteBatch.js +112 -0
- package/dist/world/WorldHud.d.ts +8 -1
- package/dist/world/WorldHud.js +90 -39
- package/dist/world/WorldItems.js +21 -2
- 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 +51 -0
- package/llms.txt +2643 -0
- package/package.json +6 -5
- package/dist/demo/builderDemo.d.ts +0 -2
- package/dist/demo/builderDemo.js +0 -189
- package/dist/demo/demoGame.d.ts +0 -2
- package/dist/demo/demoGame.js +0 -208
- package/dist/demo/environmentShowcase.d.ts +0 -2
- package/dist/demo/environmentShowcase.js +0 -15
- package/dist/demo/mapDemo.d.ts +0 -2
- package/dist/demo/mapDemo.js +0 -206
- package/dist/demo/pointerDemo.d.ts +0 -2
- package/dist/demo/pointerDemo.js +0 -151
- package/dist/demo/sensorShowcase.d.ts +0 -2
- package/dist/demo/sensorShowcase.js +0 -131
- package/dist/demo/survivalDemo.d.ts +0 -2
- package/dist/demo/survivalDemo.js +0 -291
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
export const MAX_OCEAN_WAVES = 6;
|
|
3
|
+
/** Shared with `@jgengine/core/world/water` — primary wavelength in world units. */
|
|
4
|
+
export const DEFAULT_OCEAN_WAVE_SCALE = 18;
|
|
3
5
|
export const OCEAN_QUALITY_PRESETS = {
|
|
4
6
|
low: { size: 220, resolution: 80 },
|
|
5
7
|
medium: { size: 360, resolution: 128 },
|
|
@@ -9,6 +11,7 @@ export const OCEAN_QUALITY_PRESETS = {
|
|
|
9
11
|
export const DEFAULT_OCEAN_CONFIG = {
|
|
10
12
|
quality: "medium",
|
|
11
13
|
size: OCEAN_QUALITY_PRESETS.medium.size,
|
|
14
|
+
depth: OCEAN_QUALITY_PRESETS.medium.size,
|
|
12
15
|
resolution: OCEAN_QUALITY_PRESETS.medium.resolution,
|
|
13
16
|
amplitude: 1,
|
|
14
17
|
speed: 1,
|
|
@@ -16,6 +19,7 @@ export const DEFAULT_OCEAN_CONFIG = {
|
|
|
16
19
|
choppiness: 1,
|
|
17
20
|
steepness: 0.72,
|
|
18
21
|
timeScale: 1,
|
|
22
|
+
waveScale: DEFAULT_OCEAN_WAVE_SCALE,
|
|
19
23
|
color: {
|
|
20
24
|
shallow: "#2dc5d3",
|
|
21
25
|
deep: "#064468",
|
|
@@ -33,14 +37,10 @@ export const DEFAULT_OCEAN_CONFIG = {
|
|
|
33
37
|
},
|
|
34
38
|
waves: [],
|
|
35
39
|
};
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{ amplitude: 0.28, wavelength: 10, speed: 1.34, steepness: 0.38 },
|
|
41
|
-
{ amplitude: 0.18, wavelength: 6.5, speed: 1.52, steepness: 0.28 },
|
|
42
|
-
{ amplitude: 0.11, wavelength: 3.8, speed: 1.75, steepness: 0.18 },
|
|
43
|
-
];
|
|
40
|
+
const WAVE_WAVELENGTH_FALLOFF = 0.65;
|
|
41
|
+
const WAVE_AMPLITUDE_SHAPE = [1.15, 0.72, 0.44, 0.28, 0.18, 0.11];
|
|
42
|
+
const WAVE_SPEED_SHAPE = [1, 1.08, 1.18, 1.34, 1.52, 1.75];
|
|
43
|
+
const WAVE_STEEPNESS_SHAPE = [0.68, 0.58, 0.48, 0.38, 0.28, 0.18];
|
|
44
44
|
const DEFAULT_DIRECTION_OFFSETS = [-8, 7, 21, -31, 46, -58];
|
|
45
45
|
function finiteOr(value, fallback) {
|
|
46
46
|
return value === undefined || !Number.isFinite(value) ? fallback : value;
|
|
@@ -67,18 +67,21 @@ function normalizeDirection(direction, fallback) {
|
|
|
67
67
|
}
|
|
68
68
|
function createDefaultWaves(config) {
|
|
69
69
|
const baseAngle = directionAngle(config.direction);
|
|
70
|
-
return
|
|
71
|
-
amplitude
|
|
72
|
-
wavelength:
|
|
73
|
-
speed:
|
|
70
|
+
return WAVE_AMPLITUDE_SHAPE.map((amplitude, index) => ({
|
|
71
|
+
amplitude,
|
|
72
|
+
wavelength: Math.max(0.1, config.waveScale * WAVE_WAVELENGTH_FALLOFF ** index),
|
|
73
|
+
speed: WAVE_SPEED_SHAPE[index],
|
|
74
74
|
direction: directionFromAngle(baseAngle + DEFAULT_DIRECTION_OFFSETS[index]),
|
|
75
|
-
steepness:
|
|
75
|
+
steepness: WAVE_STEEPNESS_SHAPE[index],
|
|
76
76
|
}));
|
|
77
77
|
}
|
|
78
|
-
function resolveWave(wave, fallback, config) {
|
|
78
|
+
function resolveWave(wave, fallback, config, customWaves) {
|
|
79
|
+
const wavelength = customWaves && wave?.wavelength !== undefined
|
|
80
|
+
? wave.wavelength * (config.waveScale / DEFAULT_OCEAN_WAVE_SCALE)
|
|
81
|
+
: finiteOr(wave?.wavelength, fallback.wavelength);
|
|
79
82
|
return {
|
|
80
83
|
amplitude: Math.max(0, finiteOr(wave?.amplitude, fallback.amplitude) * config.amplitude),
|
|
81
|
-
wavelength: Math.max(0.1,
|
|
84
|
+
wavelength: Math.max(0.1, wavelength),
|
|
82
85
|
speed: finiteOr(wave?.speed, fallback.speed) * config.speed,
|
|
83
86
|
direction: normalizeDirection(wave?.direction, fallback.direction),
|
|
84
87
|
steepness: clamp(finiteOr(wave?.steepness, fallback.steepness) * config.steepness, 0, 1.2),
|
|
@@ -87,9 +90,12 @@ function resolveWave(wave, fallback, config) {
|
|
|
87
90
|
export function createOceanConfig(patch = {}) {
|
|
88
91
|
const quality = patch.quality ?? DEFAULT_OCEAN_CONFIG.quality;
|
|
89
92
|
const preset = OCEAN_QUALITY_PRESETS[quality];
|
|
93
|
+
const size = Math.max(1, finiteOr(patch.size, preset.size));
|
|
94
|
+
const depth = Math.max(1, finiteOr(patch.depth, size));
|
|
90
95
|
const withoutWaves = {
|
|
91
96
|
quality,
|
|
92
|
-
size
|
|
97
|
+
size,
|
|
98
|
+
depth,
|
|
93
99
|
resolution: Math.max(1, Math.floor(finiteOr(patch.resolution, preset.resolution))),
|
|
94
100
|
amplitude: Math.max(0, finiteOr(patch.amplitude, DEFAULT_OCEAN_CONFIG.amplitude)),
|
|
95
101
|
speed: finiteOr(patch.speed, DEFAULT_OCEAN_CONFIG.speed),
|
|
@@ -97,6 +103,7 @@ export function createOceanConfig(patch = {}) {
|
|
|
97
103
|
choppiness: Math.max(0, finiteOr(patch.choppiness, DEFAULT_OCEAN_CONFIG.choppiness)),
|
|
98
104
|
steepness: Math.max(0, finiteOr(patch.steepness, DEFAULT_OCEAN_CONFIG.steepness)),
|
|
99
105
|
timeScale: finiteOr(patch.timeScale, DEFAULT_OCEAN_CONFIG.timeScale),
|
|
106
|
+
waveScale: Math.max(0.1, finiteOr(patch.waveScale, DEFAULT_OCEAN_CONFIG.waveScale)),
|
|
100
107
|
color: {
|
|
101
108
|
...DEFAULT_OCEAN_CONFIG.color,
|
|
102
109
|
...patch.color,
|
|
@@ -114,8 +121,9 @@ export function createOceanConfig(patch = {}) {
|
|
|
114
121
|
},
|
|
115
122
|
};
|
|
116
123
|
const defaultWaves = createDefaultWaves(withoutWaves);
|
|
124
|
+
const customWaves = patch.waves !== undefined;
|
|
117
125
|
const inputWaves = patch.waves ?? defaultWaves;
|
|
118
|
-
const waves = Array.from({ length: MAX_OCEAN_WAVES }, (_, index) => resolveWave(inputWaves[index], defaultWaves[index], withoutWaves));
|
|
126
|
+
const waves = Array.from({ length: MAX_OCEAN_WAVES }, (_, index) => resolveWave(inputWaves[index], defaultWaves[index], withoutWaves, customWaves));
|
|
119
127
|
return { ...withoutWaves, waves };
|
|
120
128
|
}
|
|
121
129
|
export function buildOceanWaveUniforms(config) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const oceanVertexShader = "\nuniform float uTime;\nuniform vec2 uWaveDirections[6];\nuniform vec4 uWaveParams[6];\nuniform float uChoppiness;\nuniform float uFoamThreshold;\nuniform float uFoamSoftness;\nuniform float uFoamCoverage;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\nvoid main() {\n vec3 displaced = position;\n vec3 tangent = vec3(1.0, 0.0, 0.0);\n vec3 bitangent = vec3(0.0, 0.0, 1.0);\n float crest = 0.0;\n\n for (int i = 0; i < 6; i++) {\n vec2 direction = normalize(uWaveDirections[i]);\n vec4 wave = uWaveParams[i];\n float k = wave.x;\n float amplitude = wave.y;\n float steepness = wave.z;\n float omega = wave.w;\n float phase = k * dot(direction, position.xz) - omega * uTime;\n float sine = sin(phase);\n float cosine = cos(phase);\n float horizontal = steepness * amplitude * uChoppiness;\n displaced.x += horizontal * direction.x * cosine;\n displaced.z += horizontal * direction.y * cosine;\n displaced.y += amplitude * sine;\n float
|
|
1
|
+
export declare const oceanVertexShader = "\nuniform float uTime;\nuniform vec2 uWaveDirections[6];\nuniform vec4 uWaveParams[6];\nuniform float uChoppiness;\nuniform float uFoamThreshold;\nuniform float uFoamSoftness;\nuniform float uFoamCoverage;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\nvoid main() {\n vec3 displaced = position;\n vec3 tangent = vec3(1.0, 0.0, 0.0);\n vec3 bitangent = vec3(0.0, 0.0, 1.0);\n float crest = 0.0;\n\n for (int i = 0; i < 6; i++) {\n vec2 direction = normalize(uWaveDirections[i]);\n vec4 wave = uWaveParams[i];\n float k = wave.x;\n float amplitude = wave.y;\n float steepness = wave.z;\n float omega = wave.w;\n float phase = k * dot(direction, position.xz) - omega * uTime;\n float sine = sin(phase);\n float cosine = cos(phase);\n float horizontal = steepness * amplitude * uChoppiness;\n displaced.x += horizontal * direction.x * cosine;\n displaced.z += horizontal * direction.y * cosine;\n displaced.y += amplitude * sine;\n float slopeTerm = horizontal * k * sine;\n tangent += vec3(-slopeTerm * direction.x * direction.x, amplitude * k * direction.x * cosine, -slopeTerm * direction.x * direction.y);\n bitangent += vec3(-slopeTerm * direction.x * direction.y, amplitude * k * direction.y * cosine, -slopeTerm * direction.y * direction.y);\n crest = max(crest, smoothstep(uFoamThreshold, uFoamThreshold + uFoamSoftness, sine * steepness + uFoamCoverage * 0.35));\n }\n\n vNormal = normalize(cross(bitangent, tangent));\n vCrest = crest;\n vWaveHeight = displaced.y;\n vec4 worldPosition = modelMatrix * vec4(displaced, 1.0);\n vWorldPosition = worldPosition.xyz;\n gl_Position = projectionMatrix * viewMatrix * worldPosition;\n}\n";
|
|
2
2
|
export declare const oceanFragmentShader = "\nuniform vec3 uShallowColor;\nuniform vec3 uDeepColor;\nuniform vec3 uCrestColor;\nuniform vec3 uFoamColor;\nuniform float uOpacity;\nuniform float uFresnelStrength;\nuniform float uHorizonBlend;\nuniform float uFoamIntensity;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\nvoid main() {\n vec3 normal = normalize(vNormal);\n vec3 viewDirection = normalize(cameraPosition - vWorldPosition);\n float fresnel = pow(1.0 - max(dot(normal, viewDirection), 0.0), 4.5);\n float depthMix = smoothstep(-1.8, 1.6, vWaveHeight);\n vec3 waterColor = mix(uDeepColor, uShallowColor, depthMix);\n waterColor = mix(waterColor, uCrestColor, smoothstep(0.15, 1.2, vWaveHeight) * 0.28);\n waterColor = mix(waterColor, vec3(0.86, 0.95, 1.0), fresnel * uFresnelStrength);\n float horizon = smoothstep(0.0, 1.0, 1.0 - abs(normal.y));\n waterColor = mix(waterColor, uShallowColor, horizon * uHorizonBlend);\n float foam = clamp(vCrest * uFoamIntensity, 0.0, 1.0);\n vec3 color = mix(waterColor, uFoamColor, foam);\n gl_FragColor = vec4(color, uOpacity);\n}\n";
|
|
@@ -32,9 +32,9 @@ void main() {
|
|
|
32
32
|
displaced.x += horizontal * direction.x * cosine;
|
|
33
33
|
displaced.z += horizontal * direction.y * cosine;
|
|
34
34
|
displaced.y += amplitude * sine;
|
|
35
|
-
float
|
|
36
|
-
tangent += vec3(-
|
|
37
|
-
bitangent += vec3(-
|
|
35
|
+
float slopeTerm = horizontal * k * sine;
|
|
36
|
+
tangent += vec3(-slopeTerm * direction.x * direction.x, amplitude * k * direction.x * cosine, -slopeTerm * direction.x * direction.y);
|
|
37
|
+
bitangent += vec3(-slopeTerm * direction.x * direction.y, amplitude * k * direction.y * cosine, -slopeTerm * direction.y * direction.y);
|
|
38
38
|
crest = max(crest, smoothstep(uFoamThreshold, uFoamThreshold + uFoamSoftness, sine * steepness + uFoamCoverage * 0.35));
|
|
39
39
|
}
|
|
40
40
|
|
package/dist/water/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { Ocean, type OceanProps } from "./Ocean.js";
|
|
2
|
-
export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, type OceanColorConfig, type OceanConfig, type OceanDirectionVector, type OceanFoamConfig, type OceanQualityPreset, type OceanWaveConfig, type OceanWaveDirection, type ResolvedOceanColorConfig, type ResolvedOceanConfig, type ResolvedOceanFoamConfig, type ResolvedOceanWaveConfig, } from "./OceanConfig.js";
|
|
2
|
+
export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, DEFAULT_OCEAN_WAVE_SCALE, type OceanColorConfig, type OceanConfig, type OceanDirectionVector, type OceanFoamConfig, type OceanQualityPreset, type OceanWaveConfig, type OceanWaveDirection, type ResolvedOceanColorConfig, type ResolvedOceanConfig, type ResolvedOceanFoamConfig, type ResolvedOceanWaveConfig, } from "./OceanConfig.js";
|
|
3
3
|
export { createOceanMaterial, syncOceanMaterial, type OceanMaterialUniforms, type OceanShaderMaterial, } from "./OceanMaterial.js";
|
package/dist/water/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { Ocean } from "./Ocean.js";
|
|
2
|
-
export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, } from "./OceanConfig.js";
|
|
2
|
+
export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, DEFAULT_OCEAN_WAVE_SCALE, } from "./OceanConfig.js";
|
|
3
3
|
export { createOceanMaterial, syncOceanMaterial, } from "./OceanMaterial.js";
|
|
@@ -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));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
/**
|
|
4
|
+
* Renders one placed 3D object per data item — a 3D bar chart, a heatmap, a city
|
|
5
|
+
* of buildings, a crowd. By default each item is an extruded box (all boxes share
|
|
6
|
+
* one `InstancedMesh`, so hundreds cost one draw call), sized and colored from the
|
|
7
|
+
* item; `renderItem` swaps the box for arbitrary content (a sprite, a GLB, a full
|
|
8
|
+
* entity). Genre-agnostic: the caller owns the data `T`, this owns the placement.
|
|
9
|
+
*/
|
|
10
|
+
export interface DataObjectsProps<T> {
|
|
11
|
+
data: readonly T[];
|
|
12
|
+
/** World `[x, z]` for an item (placed on the ground plane). */
|
|
13
|
+
position: (item: T, index: number) => readonly [number, number];
|
|
14
|
+
/** Box height (Y-scale) for an item. */
|
|
15
|
+
height: (item: T, index: number) => number;
|
|
16
|
+
/** Box color for an item (any CSS/hex/THREE color). */
|
|
17
|
+
color: (item: T, index: number) => THREE.ColorRepresentation;
|
|
18
|
+
/** Footprint on X and Z. Default 0.28. */
|
|
19
|
+
cellSize?: number;
|
|
20
|
+
/** Index of the highlighted item, painted with `hoverColor`. */
|
|
21
|
+
hovered?: number | null;
|
|
22
|
+
hoverColor?: THREE.ColorRepresentation;
|
|
23
|
+
/** Called with the item index under the pointer, or null on exit. */
|
|
24
|
+
onHover?: (index: number | null) => void;
|
|
25
|
+
/** Staggered grow-in; restarts whenever `data` identity changes. */
|
|
26
|
+
grow?: {
|
|
27
|
+
duration?: number;
|
|
28
|
+
delay?: (item: T, index: number) => number;
|
|
29
|
+
};
|
|
30
|
+
castShadow?: boolean;
|
|
31
|
+
receiveShadow?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Render arbitrary content per item instead of an instanced box — a sprite, a
|
|
34
|
+
* GLB, a full entity. The layout positions each on the ground at its `[x, z]`
|
|
35
|
+
* and hands you the resolved `height` to size against (e.g. one walking figure
|
|
36
|
+
* per unit of value). When set, the instanced-box path is bypassed.
|
|
37
|
+
*/
|
|
38
|
+
renderItem?: (item: T, index: number, info: {
|
|
39
|
+
x: number;
|
|
40
|
+
z: number;
|
|
41
|
+
height: number;
|
|
42
|
+
}) => ReactNode;
|
|
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 | null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame } from "@react-three/fiber";
|
|
3
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
function clamp01(v) {
|
|
6
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
7
|
+
}
|
|
8
|
+
export function DataObjects({ data, position, height, color, cellSize = 0.28, hovered = null, hoverColor = "#ffffff", onHover, grow, castShadow = true, receiveShadow = true, renderItem, }) {
|
|
9
|
+
const meshRef = useRef(null);
|
|
10
|
+
const dummy = useMemo(() => new THREE.Object3D(), []);
|
|
11
|
+
const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
|
|
12
|
+
const material = useMemo(() => new THREE.MeshStandardMaterial({ roughness: 0.62, metalness: 0.04 }), []);
|
|
13
|
+
const hover = useMemo(() => new THREE.Color(hoverColor), [hoverColor]);
|
|
14
|
+
const startRef = useRef(null);
|
|
15
|
+
const capacity = data.length;
|
|
16
|
+
const layout = useMemo(() => {
|
|
17
|
+
const positions = new Float32Array(capacity * 2);
|
|
18
|
+
const targets = new Float32Array(capacity);
|
|
19
|
+
const delays = new Float32Array(capacity);
|
|
20
|
+
const colors = [];
|
|
21
|
+
data.forEach((item, i) => {
|
|
22
|
+
const [x, z] = position(item, i);
|
|
23
|
+
positions[i * 2] = x;
|
|
24
|
+
positions[i * 2 + 1] = z;
|
|
25
|
+
targets[i] = height(item, i);
|
|
26
|
+
delays[i] = grow?.delay?.(item, i) ?? 0;
|
|
27
|
+
colors.push(new THREE.Color(color(item, i)));
|
|
28
|
+
});
|
|
29
|
+
return { positions, targets, delays, colors };
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
}, [data]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
startRef.current = null;
|
|
34
|
+
}, [data]);
|
|
35
|
+
useEffect(() => () => {
|
|
36
|
+
geometry.dispose();
|
|
37
|
+
material.dispose();
|
|
38
|
+
}, [geometry, material]);
|
|
39
|
+
const duration = grow?.duration ?? 0;
|
|
40
|
+
useFrame((state) => {
|
|
41
|
+
const mesh = meshRef.current;
|
|
42
|
+
if (mesh === null)
|
|
43
|
+
return;
|
|
44
|
+
if (startRef.current === null)
|
|
45
|
+
startRef.current = state.clock.elapsedTime;
|
|
46
|
+
const elapsed = state.clock.elapsedTime - startRef.current;
|
|
47
|
+
const { positions, targets, delays, colors } = layout;
|
|
48
|
+
for (let i = 0; i < capacity; i += 1) {
|
|
49
|
+
const grown = duration <= 0 ? 1 : clamp01((elapsed - delays[i]) / duration);
|
|
50
|
+
const eased = 1 - Math.pow(1 - grown, 3);
|
|
51
|
+
const h = Math.max(targets[i] * eased, 0.001);
|
|
52
|
+
dummy.position.set(positions[i * 2], h / 2, positions[i * 2 + 1]);
|
|
53
|
+
dummy.scale.set(cellSize, h, cellSize);
|
|
54
|
+
dummy.updateMatrix();
|
|
55
|
+
mesh.setMatrixAt(i, dummy.matrix);
|
|
56
|
+
mesh.setColorAt(i, i === hovered ? hover : colors[i]);
|
|
57
|
+
}
|
|
58
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
59
|
+
if (mesh.instanceColor !== null)
|
|
60
|
+
mesh.instanceColor.needsUpdate = true;
|
|
61
|
+
});
|
|
62
|
+
function handleMove(event) {
|
|
63
|
+
if (onHover === undefined)
|
|
64
|
+
return;
|
|
65
|
+
event.stopPropagation();
|
|
66
|
+
onHover(event.instanceId ?? null);
|
|
67
|
+
}
|
|
68
|
+
if (renderItem !== undefined) {
|
|
69
|
+
return (_jsx("group", { children: data.map((item, i) => {
|
|
70
|
+
const [x, z] = position(item, i);
|
|
71
|
+
return (_jsx("group", { position: [x, 0, z], children: renderItem(item, i, { x, z, height: height(item, i) }) }, i));
|
|
72
|
+
}) }));
|
|
73
|
+
}
|
|
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));
|
|
77
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorldFeature } from "@jgengine/core/world/features";
|
|
2
|
+
export interface GridWorldSceneProps {
|
|
3
|
+
feature: WorldFeature;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Data-driven renderer for the `biomes()`/`voxel()`/`plots()`/`tilemap()` world-feature kinds
|
|
7
|
+
* (#207.1): one `InstancedMesh` of extruded, colored boxes built from each feature's declared
|
|
8
|
+
* `cells`, following the same direct-buffer pattern as `InstancedBodies`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function GridWorldScene({ feature }: GridWorldSceneProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
import { resolveGridInstances } from "@jgengine/core/world/gridInstances";
|
|
5
|
+
function isGridWorldFeature(feature) {
|
|
6
|
+
return (feature.kind === "biomes" || feature.kind === "voxel" || feature.kind === "plots" || feature.kind === "tilemap");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Data-driven renderer for the `biomes()`/`voxel()`/`plots()`/`tilemap()` world-feature kinds
|
|
10
|
+
* (#207.1): one `InstancedMesh` of extruded, colored boxes built from each feature's declared
|
|
11
|
+
* `cells`, following the same direct-buffer pattern as `InstancedBodies`.
|
|
12
|
+
*/
|
|
13
|
+
export function GridWorldScene({ feature }) {
|
|
14
|
+
const instances = useMemo(() => (isGridWorldFeature(feature) ? resolveGridInstances(feature) : []), [feature]);
|
|
15
|
+
const meshRef = useRef(null);
|
|
16
|
+
const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
|
|
17
|
+
const material = useMemo(() => new THREE.MeshStandardMaterial({ roughness: 0.9, metalness: 0 }), []);
|
|
18
|
+
const instanceColor = useMemo(() => new THREE.InstancedBufferAttribute(new Float32Array(Math.max(instances.length, 1) * 3), 3), [instances.length]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const mesh = meshRef.current;
|
|
21
|
+
if (mesh === null)
|
|
22
|
+
return;
|
|
23
|
+
mesh.instanceColor = instanceColor;
|
|
24
|
+
mesh.count = instances.length;
|
|
25
|
+
const m = mesh.instanceMatrix.array;
|
|
26
|
+
const c = instanceColor.array;
|
|
27
|
+
const color = new THREE.Color();
|
|
28
|
+
for (let i = 0; i < instances.length; i += 1) {
|
|
29
|
+
const instance = instances[i];
|
|
30
|
+
const o = i * 16;
|
|
31
|
+
m[o] = instance.scale[0];
|
|
32
|
+
m[o + 5] = instance.scale[1];
|
|
33
|
+
m[o + 10] = instance.scale[2];
|
|
34
|
+
m[o + 12] = instance.position[0];
|
|
35
|
+
m[o + 13] = instance.position[1];
|
|
36
|
+
m[o + 14] = instance.position[2];
|
|
37
|
+
m[o + 15] = 1;
|
|
38
|
+
color.set(instance.color);
|
|
39
|
+
const co = i * 3;
|
|
40
|
+
c[co] = color.r;
|
|
41
|
+
c[co + 1] = color.g;
|
|
42
|
+
c[co + 2] = color.b;
|
|
43
|
+
}
|
|
44
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
45
|
+
instanceColor.needsUpdate = true;
|
|
46
|
+
}, [instances, instanceColor]);
|
|
47
|
+
useEffect(() => () => {
|
|
48
|
+
geometry.dispose();
|
|
49
|
+
material.dispose();
|
|
50
|
+
}, [geometry, material]);
|
|
51
|
+
if (instances.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, material, instances.length], castShadow: true, receiveShadow: true }));
|
|
54
|
+
}
|
|
@@ -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;
|