@jgengine/shell 0.9.0 → 0.11.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 +34 -3
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/GamePhaseStamp.d.ts +1 -0
- package/dist/GamePhaseStamp.js +9 -0
- package/dist/GamePlayerShell.d.ts +4 -31
- package/dist/GamePlayerShell.js +162 -932
- package/dist/GameUiPreview.js +0 -2
- package/dist/audio/audioEngine.d.ts +8 -0
- package/dist/audio/audioEngine.js +63 -8
- package/dist/audio/audioWire.d.ts +22 -0
- package/dist/audio/audioWire.js +19 -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 +2 -1
- package/dist/behaviour.js +2 -1
- package/dist/behaviourAttach.d.ts +1 -0
- package/dist/behaviourAttach.js +1 -0
- package/dist/behaviourDriver.d.ts +1 -0
- package/dist/behaviourDriver.js +1 -0
- package/dist/boundActionDispatch.d.ts +13 -0
- package/dist/boundActionDispatch.js +37 -0
- package/dist/camera/GameCameraRig.d.ts +5 -2
- package/dist/camera/GameCameraRig.js +2 -2
- package/dist/camera/GameFirstPersonCamera.d.ts +12 -2
- package/dist/camera/GameFirstPersonCamera.js +29 -7
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +13 -2
- package/dist/camera/GameOrbitCamera.js +59 -10
- package/dist/camera/cameraBlendMath.d.ts +3 -0
- package/dist/camera/cameraBlendMath.js +3 -0
- package/dist/camera/fovPreference.d.ts +6 -1
- package/dist/camera/fovPreference.js +6 -1
- package/dist/camera/index.d.ts +4 -7
- package/dist/camera/index.js +4 -7
- package/dist/camera/inspectionCameraMath.d.ts +7 -2
- package/dist/camera/inspectionCameraMath.js +7 -2
- package/dist/camera/orbitCameraMath.d.ts +54 -6
- package/dist/camera/orbitCameraMath.js +61 -6
- package/dist/camera/rigMath.d.ts +94 -24
- package/dist/camera/rigMath.js +100 -24
- package/dist/camera/rigResolve.d.ts +4 -2
- package/dist/camera/rigResolve.js +4 -2
- package/dist/camera/shakeChannel.d.ts +6 -2
- package/dist/camera/shakeChannel.js +6 -2
- package/dist/camera/shakeChannelMath.d.ts +1 -0
- package/dist/camera/shakeChannelMath.js +1 -0
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/cartridge.d.ts +1 -1
- package/dist/cartridge.js +4 -3
- package/dist/commandSink.d.ts +25 -0
- package/dist/commandSink.js +32 -0
- package/dist/defineGame.js +26 -5
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/CollisionDebugWorld.js +1 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +7 -3
- package/dist/devtools/DevtoolsOverlay.js +19 -450
- package/dist/devtools/KeysPanel.d.ts +4 -0
- package/dist/devtools/KeysPanel.js +31 -0
- package/dist/devtools/LogsPanel.d.ts +1 -0
- package/dist/devtools/LogsPanel.js +12 -0
- package/dist/devtools/NetPanel.d.ts +4 -0
- package/dist/devtools/NetPanel.js +10 -0
- package/dist/devtools/PerfPanel.d.ts +4 -0
- package/dist/devtools/PerfPanel.js +53 -0
- package/dist/devtools/TunePanel.d.ts +3 -0
- package/dist/devtools/TunePanel.js +309 -0
- package/dist/devtools/agentBridge.d.ts +41 -0
- package/dist/devtools/agentBridge.js +262 -0
- package/dist/devtools/collisionDebug.d.ts +6 -0
- package/dist/devtools/collisionDebug.js +6 -0
- package/dist/devtools/collisionDebugMath.d.ts +9 -2
- package/dist/devtools/collisionDebugMath.js +25 -5
- package/dist/devtools/devtoolsOverrides.d.ts +6 -0
- package/dist/devtools/devtoolsOverrides.js +48 -0
- package/dist/devtools/panelAtoms.d.ts +9 -0
- package/dist/devtools/panelAtoms.js +10 -0
- package/dist/devtools/perfDiagnose.d.ts +2 -0
- package/dist/devtools/perfDiagnose.js +43 -0
- package/dist/diagnostics/RuntimeDiagnostics.d.ts +29 -0
- package/dist/diagnostics/RuntimeDiagnostics.js +69 -0
- package/dist/drivers/FrameDriver.d.ts +37 -0
- package/dist/drivers/FrameDriver.js +232 -0
- package/dist/drivers/HudOnlyDriver.d.ts +16 -0
- package/dist/drivers/HudOnlyDriver.js +60 -0
- package/dist/environment/Daylight.d.ts +29 -7
- package/dist/environment/Daylight.js +153 -13
- package/dist/environment/EnvironmentScene.js +42 -7
- package/dist/environment/RoadRibbons.d.ts +7 -0
- package/dist/environment/RoadRibbons.js +57 -0
- package/dist/environment/VolumetricClouds.d.ts +17 -0
- package/dist/environment/VolumetricClouds.js +202 -0
- package/dist/environment/daylightCycle.d.ts +3 -1
- package/dist/environment/daylightCycle.js +3 -1
- package/dist/environment/groundPadMath.d.ts +3 -0
- package/dist/environment/groundPadMath.js +3 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +2 -0
- package/dist/environment/skyLightingPolicy.js +2 -0
- package/dist/hotbarActions.d.ts +26 -0
- package/dist/hotbarActions.js +27 -0
- package/dist/inputSink.d.ts +30 -0
- package/dist/inputSink.js +85 -0
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +14 -4
- package/dist/postfx/PostProcessing.d.ts +10 -0
- package/dist/postfx/PostProcessing.js +120 -0
- package/dist/postfx/gradeShader.d.ts +4 -0
- package/dist/postfx/gradeShader.js +72 -0
- package/dist/registry.d.ts +3 -2
- package/dist/render/EnvironmentLighting.d.ts +10 -0
- package/dist/render/EnvironmentLighting.js +34 -0
- package/dist/render/SceneLighting.d.ts +7 -0
- package/dist/render/SceneLighting.js +18 -0
- package/dist/render/SceneModels.d.ts +14 -0
- package/dist/render/SceneModels.js +337 -0
- package/dist/render/defaultSurface.d.ts +29 -0
- package/dist/render/defaultSurface.js +101 -0
- package/dist/render/modelLoad.d.ts +24 -0
- package/dist/render/modelLoad.js +73 -0
- package/dist/render/modelRender.d.ts +11 -0
- package/dist/render/modelRender.js +15 -0
- package/dist/render/resolveModel.d.ts +39 -3
- package/dist/render/resolveModel.js +79 -2
- package/dist/render/useEntityRenderCues.d.ts +13 -0
- package/dist/render/useEntityRenderCues.js +62 -0
- package/dist/scatter/InstancedScatter.d.ts +24 -0
- package/dist/scatter/InstancedScatter.js +130 -0
- package/dist/scatter/scatterModels.d.ts +25 -0
- package/dist/scatter/scatterModels.js +61 -0
- package/dist/scatter/scatterProxies.d.ts +6 -0
- package/dist/scatter/scatterProxies.js +94 -0
- package/dist/scatter.d.ts +1 -0
- package/dist/scatter.js +1 -0
- package/dist/scene/AuthoredScene.d.ts +82 -0
- package/dist/scene/AuthoredScene.js +173 -0
- package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
- package/dist/scene/GeneratedAssetRenderer.js +59 -0
- package/dist/scene/GrassFieldRenderer.d.ts +2 -0
- package/dist/scene/GrassFieldRenderer.js +25 -0
- package/dist/scene/SoilPatchRenderer.d.ts +2 -0
- package/dist/scene/SoilPatchRenderer.js +21 -0
- package/dist/scene/StudioStage.d.ts +37 -0
- package/dist/scene/StudioStage.js +42 -0
- package/dist/scene/WaterRenderer.d.ts +2 -0
- package/dist/scene/WaterRenderer.js +37 -0
- package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
- package/dist/scene/builtinSceneKindRenderers.js +22 -0
- package/dist/scene/sceneKindRenderers.d.ts +28 -0
- package/dist/scene/sceneKindRenderers.js +17 -0
- package/dist/scene.d.ts +1 -0
- package/dist/scene.js +1 -0
- package/dist/settings/appliedSettings.d.ts +5 -0
- package/dist/settings/appliedSettings.js +9 -0
- package/dist/settings/settingsController.d.ts +2 -0
- package/dist/settings/settingsController.js +22 -1
- package/dist/shellConstants.d.ts +4 -0
- package/dist/shellConstants.js +4 -0
- package/dist/shellMovement.d.ts +8 -0
- package/dist/shellMovement.js +11 -0
- package/dist/shellPointer.d.ts +20 -0
- package/dist/shellPointer.js +28 -0
- package/dist/structures/PlacementGhost.d.ts +4 -0
- package/dist/structures/PlacementGhost.js +4 -0
- package/dist/structures/TransformGizmo.d.ts +43 -0
- package/dist/structures/TransformGizmo.js +55 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +1 -0
- package/dist/terrain/CarvedTerrain.d.ts +4 -1
- package/dist/terrain/CarvedTerrain.js +4 -3
- package/dist/terrain/GrassField.js +1 -1
- package/dist/terrain/TerraformBrushCursor.js +3 -0
- package/dist/terrain/grassBudget.d.ts +11 -2
- package/dist/terrain/grassBudget.js +20 -4
- package/dist/terrain/grassGeometry.d.ts +3 -0
- package/dist/terrain/grassGeometry.js +3 -0
- package/dist/terrain/grassMaterial.d.ts +2 -0
- package/dist/terrain/grassMaterial.js +2 -0
- package/dist/terrain/random.d.ts +3 -0
- package/dist/terrain/random.js +3 -0
- package/dist/terrain/soilPatchMaterial.d.ts +10 -0
- package/dist/terrain/soilPatchMaterial.js +109 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +31 -0
- package/dist/terrain/terrainDetailMaterial.js +157 -0
- package/dist/terrain/terrainMath.d.ts +8 -1
- package/dist/terrain/terrainMath.js +8 -1
- package/dist/touch/TouchControlsOverlay.d.ts +9 -5
- package/dist/touch/TouchControlsOverlay.js +211 -22
- package/dist/useShellMultiplayerSync.d.ts +9 -0
- package/dist/useShellMultiplayerSync.js +98 -0
- package/dist/visibility/CullingProvider.js +1 -1
- package/dist/water/OceanConfig.d.ts +2 -0
- package/dist/water/OceanConfig.js +2 -0
- package/dist/weather/fireSpreadPose.d.ts +1 -0
- package/dist/weather/fireSpreadPose.js +1 -0
- package/dist/weather/weatherGeometry.d.ts +1 -0
- package/dist/weather/weatherGeometry.js +1 -0
- package/dist/weather/weatherMath.d.ts +3 -0
- package/dist/weather/weatherMath.js +3 -0
- package/dist/weather/weatherUniforms.d.ts +3 -0
- package/dist/weather/weatherUniforms.js +3 -0
- package/dist/world/RetainedVfx.d.ts +10 -0
- package/dist/world/RetainedVfx.js +136 -0
- package/dist/world/WorldHud.d.ts +52 -3
- package/dist/world/WorldHud.js +94 -9
- package/dist/world/WorldScene.d.ts +21 -0
- package/dist/world/WorldScene.js +159 -0
- package/dist/world/WorldVfx.d.ts +9 -0
- package/dist/world/WorldVfx.js +184 -0
- package/dist/world/entityPose.d.ts +2 -0
- package/dist/world/entityPose.js +2 -0
- package/dist/world/floatTextStyle.d.ts +1 -0
- package/dist/world/floatTextStyle.js +1 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +1 -0
- package/dist/world/worldBarSamples.d.ts +26 -1
- package/dist/world/worldBarSamples.js +45 -1
- package/dist/worldSky.d.ts +5 -0
- package/dist/worldSky.js +11 -0
- package/dist/worldSync.d.ts +11 -0
- package/dist/worldSync.js +20 -0
- package/package.json +6 -6
- package/skills/jgengine/SKILL.md +51 -0
- package/skills/jgengine/api.md +434 -0
- package/skills/jgengine/capabilities.md +29 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine-ui/SKILL.md +36 -0
- package/skills/jgengine-ui/api.md +1852 -0
- package/skills/jgengine-ui/capabilities.md +142 -0
- package/skills/jgengine-ui/reference.md +500 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/skills/jgengine-world/SKILL.md +38 -0
- package/skills/jgengine-world/api.md +2652 -0
- package/skills/jgengine-world/capabilities.md +370 -0
- package/skills/jgengine-world/reference.md +222 -0
- package/llms.txt +0 -2643
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { mtof } from "@jgengine/core/audio/music";
|
|
2
|
+
function adsr(ctx, when, dur, peak, attack, release) {
|
|
3
|
+
const g = ctx.createGain();
|
|
4
|
+
g.gain.setValueAtTime(0.0001, when);
|
|
5
|
+
g.gain.linearRampToValueAtTime(peak, when + attack);
|
|
6
|
+
g.gain.setValueAtTime(peak, Math.max(when + attack, when + dur - release));
|
|
7
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + dur + release);
|
|
8
|
+
return g;
|
|
9
|
+
}
|
|
10
|
+
function stack(ctx, when, freq, dur, out, type, dets, tail) {
|
|
11
|
+
for (const det of dets) {
|
|
12
|
+
const o = ctx.createOscillator();
|
|
13
|
+
o.type = type;
|
|
14
|
+
o.frequency.value = freq;
|
|
15
|
+
o.detune.value = det;
|
|
16
|
+
o.connect(out);
|
|
17
|
+
o.start(when);
|
|
18
|
+
o.stop(when + dur + tail);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function strings(ctx, when, freq, dur, vel, out, attack = 0.3) {
|
|
22
|
+
const g = adsr(ctx, when, dur, vel * 0.16, attack, 0.7);
|
|
23
|
+
const lp = ctx.createBiquadFilter();
|
|
24
|
+
lp.type = "lowpass";
|
|
25
|
+
lp.frequency.value = 750 + freq * 2;
|
|
26
|
+
lp.connect(g).connect(out);
|
|
27
|
+
stack(ctx, when, freq, dur, lp, "sawtooth", [-6, 5], 0.9);
|
|
28
|
+
}
|
|
29
|
+
function pad(ctx, when, freq, dur, vel, out) {
|
|
30
|
+
const g = adsr(ctx, when, dur, vel * 0.11, 0.6, 1.1);
|
|
31
|
+
const lp = ctx.createBiquadFilter();
|
|
32
|
+
lp.type = "lowpass";
|
|
33
|
+
lp.frequency.value = 520 + freq * 1.4;
|
|
34
|
+
lp.connect(g).connect(out);
|
|
35
|
+
stack(ctx, when, freq, dur, lp, "sawtooth", [-9, -3, 4, 10], 1.2);
|
|
36
|
+
}
|
|
37
|
+
function reed(ctx, when, freq, dur, vel, out) {
|
|
38
|
+
const g = adsr(ctx, when, dur, vel * 0.14, 0.08, 0.25);
|
|
39
|
+
const lp = ctx.createBiquadFilter();
|
|
40
|
+
lp.type = "lowpass";
|
|
41
|
+
lp.frequency.value = 1600 + freq;
|
|
42
|
+
lp.connect(g).connect(out);
|
|
43
|
+
stack(ctx, when, freq, dur, lp, "sawtooth", [-3, 4], 0.35);
|
|
44
|
+
}
|
|
45
|
+
function horn(ctx, when, freq, dur, vel, out) {
|
|
46
|
+
const g = adsr(ctx, when, dur, vel * 0.15, 0.09, 0.3);
|
|
47
|
+
const lp = ctx.createBiquadFilter();
|
|
48
|
+
lp.type = "lowpass";
|
|
49
|
+
lp.frequency.value = 900 + freq * 1.5;
|
|
50
|
+
lp.Q.value = 0.6;
|
|
51
|
+
lp.connect(g).connect(out);
|
|
52
|
+
stack(ctx, when, freq, dur, lp, "sawtooth", [-4, 3], 0.4);
|
|
53
|
+
const sub = ctx.createOscillator();
|
|
54
|
+
sub.type = "sine";
|
|
55
|
+
sub.frequency.value = freq;
|
|
56
|
+
const sg = ctx.createGain();
|
|
57
|
+
sg.gain.value = 0.4;
|
|
58
|
+
sub.connect(sg).connect(lp);
|
|
59
|
+
sub.start(when);
|
|
60
|
+
sub.stop(when + dur + 0.4);
|
|
61
|
+
}
|
|
62
|
+
function brassStab(ctx, when, freq, dur, vel, out) {
|
|
63
|
+
const g = adsr(ctx, when, Math.min(dur, 0.4), vel * 0.17, 0.02, 0.14);
|
|
64
|
+
const lp = ctx.createBiquadFilter();
|
|
65
|
+
lp.type = "lowpass";
|
|
66
|
+
lp.frequency.value = 1400 + freq * 2;
|
|
67
|
+
lp.connect(g).connect(out);
|
|
68
|
+
stack(ctx, when, freq, Math.min(dur, 0.4), lp, "sawtooth", [-5, 6], 0.2);
|
|
69
|
+
}
|
|
70
|
+
function flute(ctx, when, freq, dur, vel, out) {
|
|
71
|
+
const g = adsr(ctx, when, dur, vel * 0.15, 0.06, 0.2);
|
|
72
|
+
const lp = ctx.createBiquadFilter();
|
|
73
|
+
lp.type = "lowpass";
|
|
74
|
+
lp.frequency.value = 2600;
|
|
75
|
+
lp.connect(g).connect(out);
|
|
76
|
+
const o = ctx.createOscillator();
|
|
77
|
+
o.type = "sine";
|
|
78
|
+
o.frequency.value = freq;
|
|
79
|
+
o.connect(lp);
|
|
80
|
+
o.start(when);
|
|
81
|
+
o.stop(when + dur + 0.25);
|
|
82
|
+
const breath = ctx.createBufferSource();
|
|
83
|
+
const len = Math.floor(ctx.sampleRate * Math.min(dur, 0.4));
|
|
84
|
+
const buf = ctx.createBuffer(1, len, ctx.sampleRate);
|
|
85
|
+
const data = buf.getChannelData(0);
|
|
86
|
+
for (let i = 0; i < len; i += 1)
|
|
87
|
+
data[i] = (Math.random() * 2 - 1) * 0.5;
|
|
88
|
+
breath.buffer = buf;
|
|
89
|
+
const bp = ctx.createBiquadFilter();
|
|
90
|
+
bp.type = "bandpass";
|
|
91
|
+
bp.frequency.value = freq * 2;
|
|
92
|
+
const bg = ctx.createGain();
|
|
93
|
+
bg.gain.value = vel * 0.02;
|
|
94
|
+
breath.connect(bp).connect(bg).connect(out);
|
|
95
|
+
breath.start(when);
|
|
96
|
+
}
|
|
97
|
+
function pipe(ctx, when, freq, dur, vel, out) {
|
|
98
|
+
const g = adsr(ctx, when, dur, vel * 0.13, 0.03, 0.15);
|
|
99
|
+
const lp = ctx.createBiquadFilter();
|
|
100
|
+
lp.type = "lowpass";
|
|
101
|
+
lp.frequency.value = 3000;
|
|
102
|
+
lp.connect(g).connect(out);
|
|
103
|
+
stack(ctx, when, freq, dur, lp, "triangle", [0, 7], 0.2);
|
|
104
|
+
}
|
|
105
|
+
function squareLead(ctx, when, freq, dur, vel, out) {
|
|
106
|
+
const g = adsr(ctx, when, dur, vel * 0.1, 0.02, 0.12);
|
|
107
|
+
const lp = ctx.createBiquadFilter();
|
|
108
|
+
lp.type = "lowpass";
|
|
109
|
+
lp.frequency.value = 2200 + freq;
|
|
110
|
+
lp.connect(g).connect(out);
|
|
111
|
+
const o = ctx.createOscillator();
|
|
112
|
+
o.type = "square";
|
|
113
|
+
o.frequency.value = freq;
|
|
114
|
+
o.connect(lp);
|
|
115
|
+
o.start(when);
|
|
116
|
+
o.stop(when + dur + 0.2);
|
|
117
|
+
}
|
|
118
|
+
function choir(ctx, when, freq, dur, vel, out) {
|
|
119
|
+
const g = adsr(ctx, when, dur, vel * 0.12, 0.25, 0.6);
|
|
120
|
+
const formant = ctx.createBiquadFilter();
|
|
121
|
+
formant.type = "bandpass";
|
|
122
|
+
formant.frequency.value = Math.min(1800, 500 + freq * 1.6);
|
|
123
|
+
formant.Q.value = 0.7;
|
|
124
|
+
formant.connect(g).connect(out);
|
|
125
|
+
stack(ctx, when, freq, dur, formant, "sawtooth", [-8, -2, 6], 0.8);
|
|
126
|
+
stack(ctx, when, freq * 2, dur, formant, "triangle", [3], 0.8);
|
|
127
|
+
}
|
|
128
|
+
function oboe(ctx, when, freq, dur, vel, out) {
|
|
129
|
+
const g = adsr(ctx, when, dur, vel * 0.17, 0.055, 0.22);
|
|
130
|
+
const formant = ctx.createBiquadFilter();
|
|
131
|
+
formant.type = "bandpass";
|
|
132
|
+
formant.frequency.value = Math.min(2400, 600 + freq * 2.2);
|
|
133
|
+
formant.Q.value = 0.9;
|
|
134
|
+
const lp = ctx.createBiquadFilter();
|
|
135
|
+
lp.type = "lowpass";
|
|
136
|
+
lp.frequency.value = 2800;
|
|
137
|
+
formant.connect(lp).connect(g).connect(out);
|
|
138
|
+
const vib = ctx.createOscillator();
|
|
139
|
+
vib.frequency.value = 5.2;
|
|
140
|
+
const vibGain = ctx.createGain();
|
|
141
|
+
vibGain.gain.setValueAtTime(0, when);
|
|
142
|
+
vibGain.gain.linearRampToValueAtTime(freq * 0.004, when + 0.3);
|
|
143
|
+
vib.connect(vibGain);
|
|
144
|
+
for (const det of [-5, 4]) {
|
|
145
|
+
const o = ctx.createOscillator();
|
|
146
|
+
o.type = "sawtooth";
|
|
147
|
+
o.frequency.value = freq;
|
|
148
|
+
o.detune.value = det;
|
|
149
|
+
vibGain.connect(o.frequency);
|
|
150
|
+
o.connect(formant);
|
|
151
|
+
o.start(when);
|
|
152
|
+
o.stop(when + dur + 0.4);
|
|
153
|
+
}
|
|
154
|
+
vib.start(when);
|
|
155
|
+
vib.stop(when + dur + 0.4);
|
|
156
|
+
}
|
|
157
|
+
function pluck(ctx, when, freq, vel, out, decay, bassy = false) {
|
|
158
|
+
const g = ctx.createGain();
|
|
159
|
+
const peak = vel * (bassy ? 0.26 : 0.2);
|
|
160
|
+
g.gain.setValueAtTime(0.0001, when);
|
|
161
|
+
g.gain.linearRampToValueAtTime(peak, when + 0.004);
|
|
162
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + decay);
|
|
163
|
+
const lp = ctx.createBiquadFilter();
|
|
164
|
+
lp.type = "lowpass";
|
|
165
|
+
lp.frequency.value = bassy ? 900 : 2600 + freq;
|
|
166
|
+
lp.connect(g).connect(out);
|
|
167
|
+
const o = ctx.createOscillator();
|
|
168
|
+
o.type = bassy ? "triangle" : "sawtooth";
|
|
169
|
+
o.frequency.value = freq;
|
|
170
|
+
o.connect(lp);
|
|
171
|
+
o.start(when);
|
|
172
|
+
o.stop(when + decay + 0.1);
|
|
173
|
+
if (!bassy) {
|
|
174
|
+
const o2 = ctx.createOscillator();
|
|
175
|
+
o2.type = "triangle";
|
|
176
|
+
o2.frequency.value = freq * 2.001;
|
|
177
|
+
const g2 = ctx.createGain();
|
|
178
|
+
g2.gain.setValueAtTime(peak * 0.4, when);
|
|
179
|
+
g2.gain.exponentialRampToValueAtTime(0.0001, when + decay * 0.6);
|
|
180
|
+
o2.connect(g2).connect(out);
|
|
181
|
+
o2.start(when);
|
|
182
|
+
o2.stop(when + decay);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function bell(ctx, when, freq, vel, out, decay = 1.8) {
|
|
186
|
+
for (const [ratio, amp] of [[1, 1], [2.76, 0.5], [5.4, 0.25], [8.9, 0.12]]) {
|
|
187
|
+
const g = ctx.createGain();
|
|
188
|
+
g.gain.setValueAtTime(0.0001, when);
|
|
189
|
+
g.gain.linearRampToValueAtTime(vel * 0.14 * amp, when + 0.005);
|
|
190
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + decay * (1 - (ratio - 1) * 0.06));
|
|
191
|
+
const o = ctx.createOscillator();
|
|
192
|
+
o.type = "sine";
|
|
193
|
+
o.frequency.value = freq * ratio;
|
|
194
|
+
o.connect(g).connect(out);
|
|
195
|
+
o.start(when);
|
|
196
|
+
o.stop(when + decay + 0.2);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function piano(ctx, when, freq, dur, vel, out) {
|
|
200
|
+
const naturalDecay = Math.min(5.2, Math.max(1.2, 380 / freq));
|
|
201
|
+
const body = ctx.createBiquadFilter();
|
|
202
|
+
body.type = "lowpass";
|
|
203
|
+
body.frequency.value = Math.min(5600, 1400 + freq * 4);
|
|
204
|
+
body.Q.value = 0.35;
|
|
205
|
+
body.connect(out);
|
|
206
|
+
const partials = [
|
|
207
|
+
[1, 0.62, 1, -3],
|
|
208
|
+
[1.0005, 0.62, 1, 3],
|
|
209
|
+
[2.003, 0.5, 0.58, 2],
|
|
210
|
+
[3.006, 0.2, 0.36, -4],
|
|
211
|
+
[4.012, 0.09, 0.24, 5],
|
|
212
|
+
[5.02, 0.05, 0.17, -6],
|
|
213
|
+
[7.03, 0.025, 0.12, 4],
|
|
214
|
+
];
|
|
215
|
+
for (const [ratio, amp, decayMul, cents] of partials) {
|
|
216
|
+
const decay = Math.min(naturalDecay * decayMul, dur + 0.35);
|
|
217
|
+
const g = ctx.createGain();
|
|
218
|
+
const peak = vel * 0.24 * amp;
|
|
219
|
+
g.gain.setValueAtTime(0.0001, when);
|
|
220
|
+
g.gain.linearRampToValueAtTime(peak, when + 0.004);
|
|
221
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + Math.max(0.14, decay));
|
|
222
|
+
const o = ctx.createOscillator();
|
|
223
|
+
o.type = ratio < 1.01 ? "triangle" : "sine";
|
|
224
|
+
o.frequency.value = freq * ratio;
|
|
225
|
+
o.detune.value = cents;
|
|
226
|
+
o.connect(g).connect(body);
|
|
227
|
+
o.start(when);
|
|
228
|
+
o.stop(when + Math.max(0.14, decay) + 0.1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function noiseHit(ctx, when, vel, out, hp, len, curve) {
|
|
232
|
+
const n = Math.floor(ctx.sampleRate * len);
|
|
233
|
+
const buf = ctx.createBuffer(1, n, ctx.sampleRate);
|
|
234
|
+
const data = buf.getChannelData(0);
|
|
235
|
+
for (let i = 0; i < n; i += 1)
|
|
236
|
+
data[i] = (Math.random() * 2 - 1) * (1 - i / n) ** curve;
|
|
237
|
+
const src = ctx.createBufferSource();
|
|
238
|
+
src.buffer = buf;
|
|
239
|
+
const f = ctx.createBiquadFilter();
|
|
240
|
+
f.type = "highpass";
|
|
241
|
+
f.frequency.value = hp;
|
|
242
|
+
const g = ctx.createGain();
|
|
243
|
+
g.gain.setValueAtTime(vel * 0.22, when);
|
|
244
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + len);
|
|
245
|
+
src.connect(f).connect(g).connect(out);
|
|
246
|
+
src.start(when);
|
|
247
|
+
}
|
|
248
|
+
function drum(ctx, when, freq, vel, out, decay, punch) {
|
|
249
|
+
const o = ctx.createOscillator();
|
|
250
|
+
o.type = "sine";
|
|
251
|
+
o.frequency.setValueAtTime(freq * punch, when);
|
|
252
|
+
o.frequency.exponentialRampToValueAtTime(freq, when + 0.06);
|
|
253
|
+
const g = ctx.createGain();
|
|
254
|
+
g.gain.setValueAtTime(vel * 0.5, when);
|
|
255
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + decay);
|
|
256
|
+
o.connect(g).connect(out);
|
|
257
|
+
o.start(when);
|
|
258
|
+
o.stop(when + decay + 0.05);
|
|
259
|
+
noiseHit(ctx, when, vel * 0.4, out, 200, Math.min(decay, 0.12), 2);
|
|
260
|
+
}
|
|
261
|
+
function woodBlock(ctx, when, vel, out) {
|
|
262
|
+
const o = ctx.createOscillator();
|
|
263
|
+
o.type = "square";
|
|
264
|
+
o.frequency.value = 1100;
|
|
265
|
+
const g = ctx.createGain();
|
|
266
|
+
g.gain.setValueAtTime(vel * 0.2, when);
|
|
267
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + 0.05);
|
|
268
|
+
const bp = ctx.createBiquadFilter();
|
|
269
|
+
bp.type = "bandpass";
|
|
270
|
+
bp.frequency.value = 1100;
|
|
271
|
+
o.connect(bp).connect(g).connect(out);
|
|
272
|
+
o.start(when);
|
|
273
|
+
o.stop(when + 0.07);
|
|
274
|
+
}
|
|
275
|
+
function cymSwell(ctx, when, dur, vel, out) {
|
|
276
|
+
const n = Math.floor(ctx.sampleRate * (dur + 0.3));
|
|
277
|
+
const buf = ctx.createBuffer(1, n, ctx.sampleRate);
|
|
278
|
+
const data = buf.getChannelData(0);
|
|
279
|
+
for (let i = 0; i < n; i += 1)
|
|
280
|
+
data[i] = Math.random() * 2 - 1;
|
|
281
|
+
const src = ctx.createBufferSource();
|
|
282
|
+
src.buffer = buf;
|
|
283
|
+
const hp = ctx.createBiquadFilter();
|
|
284
|
+
hp.type = "highpass";
|
|
285
|
+
hp.frequency.value = 6000;
|
|
286
|
+
const g = ctx.createGain();
|
|
287
|
+
g.gain.setValueAtTime(0.0001, when);
|
|
288
|
+
g.gain.linearRampToValueAtTime(vel * 0.1, when + dur);
|
|
289
|
+
g.gain.exponentialRampToValueAtTime(0.0001, when + dur + 0.3);
|
|
290
|
+
src.connect(hp).connect(g).connect(out);
|
|
291
|
+
src.start(when);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Play one theme note through its instrument voice into `out` (a per-layer gain
|
|
295
|
+
* node the director wires to master + reverb). `spb` is seconds-per-beat and
|
|
296
|
+
* `transpose` shifts the note in semitones for per-zone key changes. This is the
|
|
297
|
+
* engine's reusable instrument library; unknown instruments fall back to a sine.
|
|
298
|
+
*/
|
|
299
|
+
export function playMusicNote(ctx, event, when, spb, out, transpose = 0) {
|
|
300
|
+
const freq = mtof(event.midi + transpose);
|
|
301
|
+
const dur = Math.max(0.1, event.dur * spb);
|
|
302
|
+
const vel = event.vel;
|
|
303
|
+
const inst = event.inst;
|
|
304
|
+
switch (inst) {
|
|
305
|
+
case "strings": return strings(ctx, when, freq, dur, vel, out);
|
|
306
|
+
case "stacc": return strings(ctx, when, freq, Math.min(dur, 0.22), vel, out, 0.02);
|
|
307
|
+
case "pad": return pad(ctx, when, freq, dur, vel, out);
|
|
308
|
+
case "reed": return reed(ctx, when, freq, dur, vel, out);
|
|
309
|
+
case "horn": return horn(ctx, when, freq, dur, vel, out);
|
|
310
|
+
case "brassStab": return brassStab(ctx, when, freq, dur, vel, out);
|
|
311
|
+
case "flute": return flute(ctx, when, freq, dur, vel, out);
|
|
312
|
+
case "pipe": return pipe(ctx, when, freq, dur, vel, out);
|
|
313
|
+
case "squareLead": return squareLead(ctx, when, freq, dur, vel, out);
|
|
314
|
+
case "choir": return choir(ctx, when, freq, dur, vel, out);
|
|
315
|
+
case "oboe": return oboe(ctx, when, freq, dur, vel, out);
|
|
316
|
+
case "harp": return pluck(ctx, when, freq, vel, out, 1.4);
|
|
317
|
+
case "lute": return pluck(ctx, when, freq, vel, out, 0.7);
|
|
318
|
+
case "dulcimer": return pluck(ctx, when, freq, vel, out, 1.0);
|
|
319
|
+
case "bass": return pluck(ctx, when, freq, vel, out, 0.9, true);
|
|
320
|
+
case "bell": return bell(ctx, when, freq, vel, out);
|
|
321
|
+
case "tinyBell": return bell(ctx, when, freq, vel, out, 0.7);
|
|
322
|
+
case "piano": return piano(ctx, when, freq, dur, vel, out);
|
|
323
|
+
case "timpani": return drum(ctx, when, freq, vel, out, 0.6, 1.5);
|
|
324
|
+
case "warDrum": return drum(ctx, when, Math.max(50, freq * 0.5), vel, out, 0.35, 1.8);
|
|
325
|
+
case "frameDrum": return drum(ctx, when, Math.max(70, freq * 0.6), vel, out, 0.25, 1.6);
|
|
326
|
+
case "shaker": return noiseHit(ctx, when, vel, out, 6800, 0.055, 1.8);
|
|
327
|
+
case "woodBlock": return woodBlock(ctx, when, vel, out);
|
|
328
|
+
case "cymSwell": return cymSwell(ctx, when, dur, vel, out);
|
|
329
|
+
default: {
|
|
330
|
+
const o = ctx.createOscillator();
|
|
331
|
+
o.frequency.value = freq;
|
|
332
|
+
const g = adsr(ctx, when, dur, vel * 0.12, 0.02, 0.2);
|
|
333
|
+
o.connect(g).connect(out);
|
|
334
|
+
o.start(when);
|
|
335
|
+
o.stop(when + dur + 0.3);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SynthPatch } from "@jgengine/core/audio/synth";
|
|
2
|
+
/** Build the shared 1-second mono white-noise buffer every noise voice samples from. */
|
|
3
|
+
export declare function createNoiseBuffer(ctx: BaseAudioContext): AudioBuffer;
|
|
4
|
+
/**
|
|
5
|
+
* Realise a procedural cue on Web Audio: every voice is scheduled at
|
|
6
|
+
* `ctx.currentTime + delay` into `out`, summed into one one-shot. `noiseBuf` is
|
|
7
|
+
* the shared buffer from {@link createNoiseBuffer}.
|
|
8
|
+
*/
|
|
9
|
+
export declare function realizeSynthPatch(ctx: BaseAudioContext, out: AudioNode, noiseBuf: AudioBuffer, patch: SynthPatch): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Build the shared 1-second mono white-noise buffer every noise voice samples from. */
|
|
2
|
+
export function createNoiseBuffer(ctx) {
|
|
3
|
+
const len = Math.floor(ctx.sampleRate);
|
|
4
|
+
const buf = ctx.createBuffer(1, len, ctx.sampleRate);
|
|
5
|
+
const data = buf.getChannelData(0);
|
|
6
|
+
for (let i = 0; i < len; i += 1)
|
|
7
|
+
data[i] = Math.random() * 2 - 1;
|
|
8
|
+
return buf;
|
|
9
|
+
}
|
|
10
|
+
function realizeTone(ctx, out, voice, cueGain, at) {
|
|
11
|
+
const t = at + (voice.delay ?? 0);
|
|
12
|
+
const peak = (voice.gain ?? 1) * cueGain;
|
|
13
|
+
const attack = voice.attack ?? 0.012;
|
|
14
|
+
const osc = ctx.createOscillator();
|
|
15
|
+
osc.type = voice.wave ?? "sine";
|
|
16
|
+
osc.frequency.setValueAtTime(voice.freq, t);
|
|
17
|
+
if (voice.slideTo !== undefined)
|
|
18
|
+
osc.frequency.exponentialRampToValueAtTime(Math.max(1, voice.slideTo), t + voice.duration);
|
|
19
|
+
const g = ctx.createGain();
|
|
20
|
+
g.gain.setValueAtTime(0, t);
|
|
21
|
+
g.gain.linearRampToValueAtTime(peak, t + attack);
|
|
22
|
+
g.gain.exponentialRampToValueAtTime(0.001, t + voice.duration);
|
|
23
|
+
osc.connect(g).connect(out);
|
|
24
|
+
osc.start(t);
|
|
25
|
+
osc.stop(t + voice.duration + 0.05);
|
|
26
|
+
}
|
|
27
|
+
function realizeNoise(ctx, out, noiseBuf, voice, cueGain, at) {
|
|
28
|
+
const t = at + (voice.delay ?? 0);
|
|
29
|
+
const peak = (voice.gain ?? 1) * cueGain;
|
|
30
|
+
const decay = voice.decay ?? 0.9;
|
|
31
|
+
const src = ctx.createBufferSource();
|
|
32
|
+
src.buffer = noiseBuf;
|
|
33
|
+
src.playbackRate.value = 0.8 + Math.random() * 0.4;
|
|
34
|
+
const filter = ctx.createBiquadFilter();
|
|
35
|
+
filter.type = voice.filterType ?? "lowpass";
|
|
36
|
+
filter.frequency.value = voice.filterFreq;
|
|
37
|
+
const g = ctx.createGain();
|
|
38
|
+
g.gain.setValueAtTime(peak, t);
|
|
39
|
+
g.gain.exponentialRampToValueAtTime(0.001, t + voice.duration * decay);
|
|
40
|
+
src.connect(filter).connect(g).connect(out);
|
|
41
|
+
src.start(t, Math.random() * 0.5, voice.duration);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Realise a procedural cue on Web Audio: every voice is scheduled at
|
|
45
|
+
* `ctx.currentTime + delay` into `out`, summed into one one-shot. `noiseBuf` is
|
|
46
|
+
* the shared buffer from {@link createNoiseBuffer}.
|
|
47
|
+
*/
|
|
48
|
+
export function realizeSynthPatch(ctx, out, noiseBuf, patch) {
|
|
49
|
+
const cueGain = patch.gain ?? 1;
|
|
50
|
+
const at = ctx.currentTime;
|
|
51
|
+
for (const voice of patch.voices) {
|
|
52
|
+
if (voice.kind === "tone")
|
|
53
|
+
realizeTone(ctx, out, voice, cueGain, at);
|
|
54
|
+
else
|
|
55
|
+
realizeNoise(ctx, out, noiseBuf, voice, cueGain, at);
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/behaviour.d.ts
CHANGED
|
@@ -8,5 +8,6 @@ export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
|
|
|
8
8
|
* receive — pass the game clock's scaling to keep behaviours on game time, or
|
|
9
9
|
* omit for real-time seconds. Games driving `world.update` themselves from
|
|
10
10
|
* `loop.onTick` should not also render this.
|
|
11
|
-
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
12
13
|
export declare function useBehaviourWorld(world: BehaviourWorld, scaleDt?: (rawDt: number) => number): void;
|
package/dist/behaviour.js
CHANGED
|
@@ -10,7 +10,8 @@ export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
|
|
|
10
10
|
* receive — pass the game clock's scaling to keep behaviours on game time, or
|
|
11
11
|
* omit for real-time seconds. Games driving `world.update` themselves from
|
|
12
12
|
* `loop.onTick` should not also render this.
|
|
13
|
-
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
14
15
|
export function useBehaviourWorld(world, scaleDt) {
|
|
15
16
|
const driverRef = useRef(createBehaviourWorldDriver(world));
|
|
16
17
|
useEffect(() => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Object3D } from "three";
|
|
2
2
|
import { Behaviour, type BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
|
|
3
|
+
/** @internal */
|
|
3
4
|
export declare class Object3DBehaviour extends Behaviour {
|
|
4
5
|
/** @internal */ _object: Object3D | null;
|
|
5
6
|
get object(): Object3D;
|
package/dist/behaviourAttach.js
CHANGED
package/dist/behaviourDriver.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ActionStateTracker } from "@jgengine/core/input/actionBindings";
|
|
2
|
+
import type { Aim } from "@jgengine/core/scene/spatial";
|
|
3
|
+
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
4
|
+
import type { ActionCodesMap } from "@jgengine/core/input/actionBindings";
|
|
5
|
+
import { type CommandSink } from "./commandSink.js";
|
|
6
|
+
/** Actions the shell consumes natively and never routes to a same-named command. @internal */
|
|
7
|
+
export declare const RESERVED_INPUT_ACTIONS: ReadonlySet<string>;
|
|
8
|
+
/** Actions from `input` currently held down, for `ctx.input.publish` (#164.1); includes reserved movement/jump actions. @internal */
|
|
9
|
+
export declare function heldActionsFor(tracker: Pick<ActionStateTracker<string>, "isDown">, actions: readonly string[]): string[];
|
|
10
|
+
/** Whether a bound action should fire this frame: on press, or on repeat interval while held (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
|
|
11
|
+
export declare function shouldFireBoundAction(tracker: Pick<ActionStateTracker<string>, "isDown" | "wasPressed">, action: string, input: ActionCodesMap | undefined, repeatFiredAt: ReadonlyMap<string, number>, now: number): boolean;
|
|
12
|
+
/** Resolves and runs the command bound to `action` via the shell's action→command convention (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
|
|
13
|
+
export declare function dispatchBoundAction(ctx: GameContext, action: string, yaw: number, pitch: number, aim: Aim, reserved?: ReadonlySet<string>, sink?: CommandSink): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { actionRepeatMs, resolveActionCommand, shouldDispatchAction, } from "@jgengine/core/input/actionBindings";
|
|
2
|
+
import { localCommandSink } from "./commandSink.js";
|
|
3
|
+
/** Actions the shell consumes natively and never routes to a same-named command. @internal */
|
|
4
|
+
export const RESERVED_INPUT_ACTIONS = new Set([
|
|
5
|
+
"moveForward",
|
|
6
|
+
"moveBack",
|
|
7
|
+
"moveLeft",
|
|
8
|
+
"moveRight",
|
|
9
|
+
"turnLeft",
|
|
10
|
+
"turnRight",
|
|
11
|
+
"sprint",
|
|
12
|
+
"jump",
|
|
13
|
+
"tabTarget",
|
|
14
|
+
"clearTarget",
|
|
15
|
+
"useAbility",
|
|
16
|
+
"interact",
|
|
17
|
+
]);
|
|
18
|
+
/** Actions from `input` currently held down, for `ctx.input.publish` (#164.1); includes reserved movement/jump actions. @internal */
|
|
19
|
+
export function heldActionsFor(tracker, actions) {
|
|
20
|
+
return actions.filter((action) => tracker.isDown(action));
|
|
21
|
+
}
|
|
22
|
+
/** Whether a bound action should fire this frame: on press, or on repeat interval while held (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
|
|
23
|
+
export function shouldFireBoundAction(tracker, action, input, repeatFiredAt, now) {
|
|
24
|
+
return shouldDispatchAction({
|
|
25
|
+
pressed: tracker.wasPressed(action),
|
|
26
|
+
down: tracker.isDown(action),
|
|
27
|
+
repeatMs: actionRepeatMs(input?.[action]),
|
|
28
|
+
lastFiredAt: repeatFiredAt.get(action) ?? null,
|
|
29
|
+
now,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Resolves and runs the command bound to `action` via the shell's action→command convention (shared by `FrameDriver` and `HudOnlyDriver`). @internal */
|
|
33
|
+
export function dispatchBoundAction(ctx, action, yaw, pitch, aim, reserved = RESERVED_INPUT_ACTIONS, sink = localCommandSink(ctx)) {
|
|
34
|
+
const command = resolveActionCommand(action, (name) => ctx.game.commands.has(name), reserved);
|
|
35
|
+
if (command !== null)
|
|
36
|
+
sink.run(command, { yaw, pitch, aim });
|
|
37
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type MutableRefObject } from "react";
|
|
1
|
+
import { type ComponentType, type MutableRefObject } from "react";
|
|
2
2
|
import type { GameCameraConfig } from "@jgengine/core/game/playableGame";
|
|
3
3
|
import type { CameraDirector } from "@jgengine/core/runtime/cameraDirector";
|
|
4
|
+
import { type ViewmodelProps } from "./GameFirstPersonCamera.js";
|
|
4
5
|
export { resolveRigKind } from "./rigResolve.js";
|
|
5
6
|
export interface GameCameraRigProps {
|
|
6
7
|
yawRef: MutableRefObject<number>;
|
|
@@ -12,5 +13,7 @@ export interface GameCameraRigProps {
|
|
|
12
13
|
panKeysEnabled?: boolean;
|
|
13
14
|
/** Runtime follow/cinematic override (#196.2). While present it wins over the static `config` for any field it reports non-`undefined`/non-`null`; absent (or an all-`undefined` snapshot) is a no-op. */
|
|
14
15
|
director?: CameraDirector;
|
|
16
|
+
/** Custom first-person viewmodel (#542); read only when the resolved rig is `"first"`. */
|
|
17
|
+
viewmodel?: ComponentType<ViewmodelProps>;
|
|
15
18
|
}
|
|
16
|
-
export declare function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, }: GameCameraRigProps): import("react").JSX.Element;
|
|
19
|
+
export declare function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, viewmodel, }: GameCameraRigProps): import("react").JSX.Element;
|
|
@@ -8,7 +8,7 @@ import { resolveDirectedCamera } from "./rigMath.js";
|
|
|
8
8
|
import { resolveRigKind, turntableAsObserver } from "./rigResolve.js";
|
|
9
9
|
import { CameraShakeContext, createCameraShakeChannel } from "./shakeChannel.js";
|
|
10
10
|
export { resolveRigKind } from "./rigResolve.js";
|
|
11
|
-
export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, }) {
|
|
11
|
+
export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, viewmodel, }) {
|
|
12
12
|
const channel = useMemo(() => createCameraShakeChannel(config?.shake?.decayPerSecond), [config?.shake?.decayPerSecond]);
|
|
13
13
|
const [, notifyDirectorChange] = useReducer((count) => count + 1, 0);
|
|
14
14
|
useEffect(() => {
|
|
@@ -27,7 +27,7 @@ export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerC
|
|
|
27
27
|
const kind = resolveRigKind(config);
|
|
28
28
|
switch (kind) {
|
|
29
29
|
case "first":
|
|
30
|
-
return (_jsx(GameFirstPersonCamera, { yawRef: yawRef, pitchRef: pitchRef, config: config?.firstPerson, followEntityId: followEntityId ?? undefined }));
|
|
30
|
+
return (_jsx(GameFirstPersonCamera, { yawRef: yawRef, pitchRef: pitchRef, config: config?.firstPerson, followEntityId: followEntityId ?? undefined, viewmodel: viewmodel }));
|
|
31
31
|
case "topDown":
|
|
32
32
|
return _jsx(TopDownRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
33
33
|
case "rts":
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import { type MutableRefObject } from "react";
|
|
1
|
+
import { type ComponentType, type MutableRefObject } from "react";
|
|
2
|
+
import * as THREE from "three";
|
|
2
3
|
import type { FirstPersonCameraConfig } from "@jgengine/core/game/playableGame";
|
|
4
|
+
import type { EntityRenderCues } from "@jgengine/core/combat/renderCues";
|
|
5
|
+
/** World position of the first-person weapon muzzle, or false when no viewmodel is mounted. */
|
|
6
|
+
export declare function readFirstPersonMuzzle(target: THREE.Vector3): boolean;
|
|
7
|
+
/** Props handed to a custom viewmodel component (#542): a live cue ref (velocity/bob/firing/reloading/recoil/hit) for the followed entity, driven from your own `useFrame` — read `cuesRef.current` there rather than storing it as render state. */
|
|
8
|
+
export interface ViewmodelProps {
|
|
9
|
+
cuesRef: MutableRefObject<EntityRenderCues>;
|
|
10
|
+
}
|
|
3
11
|
export interface GameFirstPersonCameraProps {
|
|
4
12
|
yawRef: MutableRefObject<number>;
|
|
5
13
|
pitchRef: MutableRefObject<number>;
|
|
6
14
|
config?: FirstPersonCameraConfig;
|
|
7
15
|
followEntityId?: string;
|
|
16
|
+
/** Custom viewmodel component replacing the built-in three-mesh gun, rendered inside the same camera-locked, muzzle-tracked anchor. Ignored when `config.viewmodel === false`. */
|
|
17
|
+
viewmodel?: ComponentType<ViewmodelProps>;
|
|
8
18
|
}
|
|
9
|
-
export declare function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, }: GameFirstPersonCameraProps): import("react").JSX.Element | null;
|
|
19
|
+
export declare function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, viewmodel, }: GameFirstPersonCameraProps): import("react").JSX.Element | null;
|
|
@@ -2,14 +2,27 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
3
3
|
import { useEffect, useRef } from "react";
|
|
4
4
|
import * as THREE from "three";
|
|
5
|
+
import { DEFAULT_EYE_HEIGHT } from "@jgengine/core/combat/shotOrigin";
|
|
5
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
6
7
|
import { usePlayer } from "@jgengine/react/hooks";
|
|
8
|
+
import { useEntityRenderCues } from "../render/useEntityRenderCues.js";
|
|
7
9
|
import { usePlayerFov } from "./PlayerFov.js";
|
|
8
10
|
import { GAME_SIM_FRAME_PRIORITY, ORBIT_CAMERA_FRAME_PRIORITY } from "./orbitCameraMath.js";
|
|
9
|
-
const DEFAULT_EYE_HEIGHT = 1.6;
|
|
10
11
|
const DEFAULT_SENSITIVITY = 0.0025;
|
|
11
12
|
const DEFAULT_MAX_PITCH = 1.45;
|
|
12
|
-
|
|
13
|
+
const VIEWMODEL_ORIGIN = new THREE.Vector3(0.34, -0.26, -0.72);
|
|
14
|
+
const MUZZLE_TIP_LOCAL = new THREE.Vector3(0, 0.03, -0.61);
|
|
15
|
+
const MUZZLE_OFFSET = VIEWMODEL_ORIGIN.clone().add(MUZZLE_TIP_LOCAL);
|
|
16
|
+
const muzzleWorld = new THREE.Vector3();
|
|
17
|
+
let muzzleTracked = false;
|
|
18
|
+
/** World position of the first-person weapon muzzle, or false when no viewmodel is mounted. */
|
|
19
|
+
export function readFirstPersonMuzzle(target) {
|
|
20
|
+
if (!muzzleTracked)
|
|
21
|
+
return false;
|
|
22
|
+
target.copy(muzzleWorld);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, viewmodel, }) {
|
|
13
26
|
const eyeHeight = config?.eyeHeight ?? DEFAULT_EYE_HEIGHT;
|
|
14
27
|
const sensitivity = config?.sensitivity ?? DEFAULT_SENSITIVITY;
|
|
15
28
|
const maxPitch = config?.maxPitch ?? DEFAULT_MAX_PITCH;
|
|
@@ -20,6 +33,7 @@ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId
|
|
|
20
33
|
const domElement = useThree((state) => state.gl.domElement);
|
|
21
34
|
const followId = followEntityId ?? userId;
|
|
22
35
|
const seededRef = useRef(false);
|
|
36
|
+
const cuesRef = useEntityRenderCues(followId);
|
|
23
37
|
useEffect(() => {
|
|
24
38
|
const requestLock = () => {
|
|
25
39
|
if (window.matchMedia?.("(pointer: coarse)").matches)
|
|
@@ -61,19 +75,27 @@ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId
|
|
|
61
75
|
}, ORBIT_CAMERA_FRAME_PRIORITY);
|
|
62
76
|
if (config?.viewmodel === false)
|
|
63
77
|
return null;
|
|
64
|
-
return _jsx(FirstPersonViewmodel, { camera: camera });
|
|
78
|
+
return _jsx(FirstPersonViewmodel, { camera: camera, viewmodel: viewmodel, cuesRef: cuesRef });
|
|
65
79
|
}
|
|
66
|
-
function FirstPersonViewmodel({ camera }) {
|
|
80
|
+
function FirstPersonViewmodel({ camera, viewmodel: Viewmodel, cuesRef, }) {
|
|
67
81
|
const groupRef = useRef(null);
|
|
82
|
+
useEffect(() => () => {
|
|
83
|
+
muzzleTracked = false;
|
|
84
|
+
}, []);
|
|
68
85
|
useFrame(() => {
|
|
69
86
|
const group = groupRef.current;
|
|
70
87
|
if (group === null)
|
|
71
88
|
return;
|
|
72
89
|
group.position.copy(camera.position);
|
|
73
90
|
group.quaternion.copy(camera.quaternion);
|
|
74
|
-
group.translateX(
|
|
75
|
-
group.translateY(
|
|
76
|
-
group.translateZ(
|
|
91
|
+
group.translateX(VIEWMODEL_ORIGIN.x);
|
|
92
|
+
group.translateY(VIEWMODEL_ORIGIN.y);
|
|
93
|
+
group.translateZ(VIEWMODEL_ORIGIN.z);
|
|
94
|
+
muzzleWorld.copy(MUZZLE_OFFSET).applyQuaternion(camera.quaternion).add(camera.position);
|
|
95
|
+
muzzleTracked = true;
|
|
77
96
|
}, GAME_SIM_FRAME_PRIORITY);
|
|
97
|
+
if (Viewmodel !== undefined) {
|
|
98
|
+
return (_jsx("group", { ref: groupRef, children: _jsx(Viewmodel, { cuesRef: cuesRef }) }));
|
|
99
|
+
}
|
|
78
100
|
return (_jsxs("group", { ref: groupRef, children: [_jsxs("mesh", { position: [0, 0, -0.22], children: [_jsx("boxGeometry", { args: [0.09, 0.11, 0.55] }), _jsx("meshStandardMaterial", { color: "#22262d", metalness: 0.6, roughness: 0.35 })] }), _jsxs("mesh", { position: [0, -0.13, 0.04], rotation: [0.35, 0, 0], children: [_jsx("boxGeometry", { args: [0.08, 0.18, 0.11] }), _jsx("meshStandardMaterial", { color: "#33373f", metalness: 0.4, roughness: 0.5 })] }), _jsxs("mesh", { position: [0, 0.03, -0.52], rotation: [Math.PI / 2, 0, 0], children: [_jsx("cylinderGeometry", { args: [0.022, 0.022, 0.18, 10] }), _jsx("meshStandardMaterial", { color: "#0e0f12", metalness: 0.7, roughness: 0.3 })] })] }));
|
|
79
101
|
}
|
|
@@ -2,5 +2,5 @@ import { type InspectionCameraConfig } from "./inspectionCameraMath.js";
|
|
|
2
2
|
export interface GameInspectionCameraProps {
|
|
3
3
|
config?: InspectionCameraConfig;
|
|
4
4
|
}
|
|
5
|
-
/** Model-viewer
|
|
5
|
+
/** Model-viewer / editor rig (#207.7, #866): left-click selects (editor), middle-drag pans, right-drag orbits, scroll zooms toward a configurable anchor. Orbits a fixed `target`; never reads player/entity state. */
|
|
6
6
|
export declare function GameInspectionCamera({ config: configPatch }: GameInspectionCameraProps): import("react").JSX.Element;
|