@plasius/gpu-shared 0.1.1 → 0.1.3
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 +35 -0
- package/README.md +19 -1
- package/dist/index.cjs +469 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{showcase-runtime-5H44EZXD.js → showcase-runtime-V72XV55N.js} +469 -49
- package/dist/showcase-runtime-V72XV55N.js.map +1 -0
- package/package.json +7 -7
- package/src/index.d.ts +1 -0
- package/src/showcase-runtime.js +420 -52
- package/dist/showcase-runtime-5H44EZXD.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -4619,6 +4619,40 @@ function normalizeDependencyUnlockSample(sample) {
|
|
|
4619
4619
|
frameId: sample.frameId === void 0 ? void 0 : assertIdentifier4("dependencyUnlock.frameId", sample.frameId)
|
|
4620
4620
|
};
|
|
4621
4621
|
}
|
|
4622
|
+
function normalizePipelinePhaseSample(sample) {
|
|
4623
|
+
if (sample.signal !== void 0 && !isAbortSignalLike2(sample.signal)) {
|
|
4624
|
+
throw new Error("pipelinePhase.signal must be an AbortSignal when provided.");
|
|
4625
|
+
}
|
|
4626
|
+
const snapshotAgeFrames = readNonNegativeNumber2(
|
|
4627
|
+
"pipelinePhase.snapshotAgeFrames",
|
|
4628
|
+
sample.snapshotAgeFrames
|
|
4629
|
+
);
|
|
4630
|
+
if (snapshotAgeFrames !== void 0 && !Number.isInteger(snapshotAgeFrames)) {
|
|
4631
|
+
throw new Error(
|
|
4632
|
+
"pipelinePhase.snapshotAgeFrames must be an integer greater than or equal to zero."
|
|
4633
|
+
);
|
|
4634
|
+
}
|
|
4635
|
+
return {
|
|
4636
|
+
owner: assertIdentifier4("pipelinePhase.owner", sample.owner),
|
|
4637
|
+
pipeline: assertEnumValue4(
|
|
4638
|
+
"pipelinePhase.pipeline",
|
|
4639
|
+
sample.pipeline,
|
|
4640
|
+
gpuPipelinePhases
|
|
4641
|
+
),
|
|
4642
|
+
stage: assertIdentifier4("pipelinePhase.stage", sample.stage),
|
|
4643
|
+
frameId: sample.frameId === void 0 ? void 0 : assertIdentifier4("pipelinePhase.frameId", sample.frameId),
|
|
4644
|
+
durationMs: readNonNegativeNumber2(
|
|
4645
|
+
"pipelinePhase.durationMs",
|
|
4646
|
+
sample.durationMs
|
|
4647
|
+
),
|
|
4648
|
+
snapshotFrameId: sample.snapshotFrameId === void 0 ? void 0 : assertIdentifier4("pipelinePhase.snapshotFrameId", sample.snapshotFrameId),
|
|
4649
|
+
snapshotAgeFrames,
|
|
4650
|
+
snapshotAgeMs: readNonNegativeNumber2(
|
|
4651
|
+
"pipelinePhase.snapshotAgeMs",
|
|
4652
|
+
sample.snapshotAgeMs
|
|
4653
|
+
)
|
|
4654
|
+
};
|
|
4655
|
+
}
|
|
4622
4656
|
function createGpuDebugSession(options = {}) {
|
|
4623
4657
|
const settings = {
|
|
4624
4658
|
enabled: options.enabled ?? DEFAULT_OPTIONS.enabled,
|
|
@@ -4638,6 +4672,10 @@ function createGpuDebugSession(options = {}) {
|
|
|
4638
4672
|
options.maxRetainedDependencyUnlockSamples,
|
|
4639
4673
|
DEFAULT_OPTIONS.maxRetainedDependencyUnlockSamples
|
|
4640
4674
|
),
|
|
4675
|
+
maxRetainedPipelinePhaseSamples: clampCount(
|
|
4676
|
+
options.maxRetainedPipelinePhaseSamples,
|
|
4677
|
+
DEFAULT_OPTIONS.maxRetainedPipelinePhaseSamples
|
|
4678
|
+
),
|
|
4641
4679
|
maxRetainedFrameSamples: clampCount(
|
|
4642
4680
|
options.maxRetainedFrameSamples,
|
|
4643
4681
|
DEFAULT_OPTIONS.maxRetainedFrameSamples
|
|
@@ -4655,6 +4693,7 @@ function createGpuDebugSession(options = {}) {
|
|
|
4655
4693
|
const readyLaneSamples = [];
|
|
4656
4694
|
const dispatchSamples = [];
|
|
4657
4695
|
const dependencyUnlockSamples = [];
|
|
4696
|
+
const pipelinePhaseSamples = [];
|
|
4658
4697
|
const frameSamples = [];
|
|
4659
4698
|
let peakTrackedBytes = 0;
|
|
4660
4699
|
const totalTrackedBytes = () => [...allocations.values()].reduce((total, allocation) => total + allocation.sizeBytes, 0);
|
|
@@ -4806,6 +4845,66 @@ function createGpuDebugSession(options = {}) {
|
|
|
4806
4845
|
)
|
|
4807
4846
|
};
|
|
4808
4847
|
};
|
|
4848
|
+
const buildPipelineSnapshot = () => {
|
|
4849
|
+
const durations = pipelinePhaseSamples.map((sample) => sample.durationMs).filter((value) => value !== void 0);
|
|
4850
|
+
const snapshotAgeMsValues = pipelinePhaseSamples.map((sample) => sample.snapshotAgeMs).filter((value) => value !== void 0);
|
|
4851
|
+
const snapshotAgeFrameValues = pipelinePhaseSamples.map((sample) => sample.snapshotAgeFrames).filter((value) => value !== void 0);
|
|
4852
|
+
const byPipeline = /* @__PURE__ */ new Map();
|
|
4853
|
+
for (const sample of pipelinePhaseSamples) {
|
|
4854
|
+
const bucket = byPipeline.get(sample.pipeline) ?? {
|
|
4855
|
+
pipeline: sample.pipeline,
|
|
4856
|
+
sampleCount: 0,
|
|
4857
|
+
totalDurationMs: 0,
|
|
4858
|
+
durationValues: [],
|
|
4859
|
+
snapshotAgeMsValues: [],
|
|
4860
|
+
snapshotAgeFramesValues: []
|
|
4861
|
+
};
|
|
4862
|
+
bucket.sampleCount += 1;
|
|
4863
|
+
bucket.totalDurationMs += sample.durationMs ?? 0;
|
|
4864
|
+
if (sample.durationMs !== void 0) {
|
|
4865
|
+
bucket.durationValues.push(sample.durationMs);
|
|
4866
|
+
}
|
|
4867
|
+
if (sample.snapshotAgeMs !== void 0) {
|
|
4868
|
+
bucket.snapshotAgeMsValues.push(sample.snapshotAgeMs);
|
|
4869
|
+
}
|
|
4870
|
+
if (sample.snapshotAgeFrames !== void 0) {
|
|
4871
|
+
bucket.snapshotAgeFramesValues.push(sample.snapshotAgeFrames);
|
|
4872
|
+
}
|
|
4873
|
+
byPipeline.set(sample.pipeline, bucket);
|
|
4874
|
+
}
|
|
4875
|
+
const hottestStages = pipelinePhaseSamples.map((sample) => ({
|
|
4876
|
+
owner: sample.owner,
|
|
4877
|
+
pipeline: sample.pipeline,
|
|
4878
|
+
stage: sample.stage,
|
|
4879
|
+
frameId: sample.frameId,
|
|
4880
|
+
durationMs: sample.durationMs,
|
|
4881
|
+
snapshotFrameId: sample.snapshotFrameId,
|
|
4882
|
+
snapshotAgeFrames: sample.snapshotAgeFrames,
|
|
4883
|
+
snapshotAgeMs: sample.snapshotAgeMs
|
|
4884
|
+
})).sort((left, right) => {
|
|
4885
|
+
const leftScore = left.durationMs ?? left.snapshotAgeMs ?? left.snapshotAgeFrames ?? 0;
|
|
4886
|
+
const rightScore = right.durationMs ?? right.snapshotAgeMs ?? right.snapshotAgeFrames ?? 0;
|
|
4887
|
+
return rightScore - leftScore;
|
|
4888
|
+
}).slice(0, 5);
|
|
4889
|
+
return {
|
|
4890
|
+
sampleCount: pipelinePhaseSamples.length,
|
|
4891
|
+
totalDurationMs: durations.reduce((total, value) => total + value, 0),
|
|
4892
|
+
averageDurationMs: average(durations),
|
|
4893
|
+
averageSnapshotAgeMs: average(snapshotAgeMsValues),
|
|
4894
|
+
maxSnapshotAgeMs: snapshotAgeMsValues.length > 0 ? Math.max(...snapshotAgeMsValues) : void 0,
|
|
4895
|
+
maxSnapshotAgeFrames: snapshotAgeFrameValues.length > 0 ? Math.max(...snapshotAgeFrameValues) : void 0,
|
|
4896
|
+
byPipeline: [...byPipeline.values()].map((bucket) => ({
|
|
4897
|
+
pipeline: bucket.pipeline,
|
|
4898
|
+
sampleCount: bucket.sampleCount,
|
|
4899
|
+
totalDurationMs: bucket.totalDurationMs,
|
|
4900
|
+
averageDurationMs: average(bucket.durationValues),
|
|
4901
|
+
averageSnapshotAgeMs: average(bucket.snapshotAgeMsValues),
|
|
4902
|
+
maxSnapshotAgeMs: bucket.snapshotAgeMsValues.length > 0 ? Math.max(...bucket.snapshotAgeMsValues) : void 0,
|
|
4903
|
+
maxSnapshotAgeFrames: bucket.snapshotAgeFramesValues.length > 0 ? Math.max(...bucket.snapshotAgeFramesValues) : void 0
|
|
4904
|
+
})).sort((left, right) => right.totalDurationMs - left.totalDurationMs),
|
|
4905
|
+
hottestStages
|
|
4906
|
+
};
|
|
4907
|
+
};
|
|
4809
4908
|
return {
|
|
4810
4909
|
isEnabled() {
|
|
4811
4910
|
return enabled;
|
|
@@ -4876,6 +4975,17 @@ function createGpuDebugSession(options = {}) {
|
|
|
4876
4975
|
);
|
|
4877
4976
|
return true;
|
|
4878
4977
|
},
|
|
4978
|
+
recordPipelinePhase(sample) {
|
|
4979
|
+
if (!enabled || sample.signal?.aborted === true) {
|
|
4980
|
+
return false;
|
|
4981
|
+
}
|
|
4982
|
+
pipelinePhaseSamples.push(normalizePipelinePhaseSample(sample));
|
|
4983
|
+
trimHistory(
|
|
4984
|
+
pipelinePhaseSamples,
|
|
4985
|
+
settings.maxRetainedPipelinePhaseSamples
|
|
4986
|
+
);
|
|
4987
|
+
return true;
|
|
4988
|
+
},
|
|
4879
4989
|
recordFrame(sample) {
|
|
4880
4990
|
if (!enabled || sample.signal?.aborted === true) {
|
|
4881
4991
|
return false;
|
|
@@ -4917,6 +5027,7 @@ function createGpuDebugSession(options = {}) {
|
|
|
4917
5027
|
averageGpuBusyMs: average(gpuBusyTimes)
|
|
4918
5028
|
},
|
|
4919
5029
|
dag: buildDagSnapshot(),
|
|
5030
|
+
pipeline: buildPipelineSnapshot(),
|
|
4920
5031
|
limitations: LIMITATIONS
|
|
4921
5032
|
};
|
|
4922
5033
|
return snapshot;
|
|
@@ -4928,12 +5039,13 @@ function createGpuDebugSession(options = {}) {
|
|
|
4928
5039
|
readyLaneSamples.splice(0, readyLaneSamples.length);
|
|
4929
5040
|
dispatchSamples.splice(0, dispatchSamples.length);
|
|
4930
5041
|
dependencyUnlockSamples.splice(0, dependencyUnlockSamples.length);
|
|
5042
|
+
pipelinePhaseSamples.splice(0, pipelinePhaseSamples.length);
|
|
4931
5043
|
frameSamples.splice(0, frameSamples.length);
|
|
4932
5044
|
peakTrackedBytes = 0;
|
|
4933
5045
|
}
|
|
4934
5046
|
};
|
|
4935
5047
|
}
|
|
4936
|
-
var IDENTIFIER_PATTERN2, gpuDebugQueueClasses, gpuResourceCategories, DEFAULT_OPTIONS, LIMITATIONS;
|
|
5048
|
+
var IDENTIFIER_PATTERN2, gpuDebugQueueClasses, gpuResourceCategories, gpuPipelinePhases, DEFAULT_OPTIONS, LIMITATIONS;
|
|
4937
5049
|
var init_dist5 = __esm({
|
|
4938
5050
|
"node_modules/@plasius/gpu-debug/dist/index.js"() {
|
|
4939
5051
|
IDENTIFIER_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/u;
|
|
@@ -4953,12 +5065,19 @@ var init_dist5 = __esm({
|
|
|
4953
5065
|
"pipeline",
|
|
4954
5066
|
"custom"
|
|
4955
5067
|
]);
|
|
5068
|
+
gpuPipelinePhases = Object.freeze([
|
|
5069
|
+
"simulation",
|
|
5070
|
+
"secondary-simulation",
|
|
5071
|
+
"scene-preparation",
|
|
5072
|
+
"render"
|
|
5073
|
+
]);
|
|
4956
5074
|
DEFAULT_OPTIONS = Object.freeze({
|
|
4957
5075
|
enabled: false,
|
|
4958
5076
|
maxRetainedDispatches: 240,
|
|
4959
5077
|
maxRetainedQueueSamples: 240,
|
|
4960
5078
|
maxRetainedReadyLaneSamples: 240,
|
|
4961
5079
|
maxRetainedDependencyUnlockSamples: 240,
|
|
5080
|
+
maxRetainedPipelinePhaseSamples: 240,
|
|
4962
5081
|
maxRetainedFrameSamples: 240,
|
|
4963
5082
|
maxTrackedAllocations: 512
|
|
4964
5083
|
});
|
|
@@ -4966,7 +5085,8 @@ var init_dist5 = __esm({
|
|
|
4966
5085
|
"Tracked memory reflects only allocations reported to this debug session.",
|
|
4967
5086
|
"Portable WebGPU does not expose authoritative live GPU core-count or total-memory counters.",
|
|
4968
5087
|
"Hardware hints are optional caller-supplied metadata and may be platform-specific.",
|
|
4969
|
-
"Ready-lane and dependency-unlock diagnostics are caller-reported integration samples, not automatic WebGPU counters."
|
|
5088
|
+
"Ready-lane and dependency-unlock diagnostics are caller-reported integration samples, not automatic WebGPU counters.",
|
|
5089
|
+
"Pipeline phase and snapshot-lag diagnostics are caller-reported integration samples, not automatic WebGPU counters."
|
|
4970
5090
|
]);
|
|
4971
5091
|
}
|
|
4972
5092
|
});
|
|
@@ -6642,7 +6762,145 @@ function buildDemoDom(root, options) {
|
|
|
6642
6762
|
sceneNotes: root.querySelector("#sceneNotes")
|
|
6643
6763
|
};
|
|
6644
6764
|
}
|
|
6645
|
-
function
|
|
6765
|
+
function buildSceneSnapshot(state, shipModel) {
|
|
6766
|
+
return Object.freeze({
|
|
6767
|
+
focus: state.focus,
|
|
6768
|
+
frame: state.frame,
|
|
6769
|
+
time: state.time,
|
|
6770
|
+
stress: state.stress,
|
|
6771
|
+
collisions: state.contactCount,
|
|
6772
|
+
collisionCount: state.collisionCount,
|
|
6773
|
+
collisionFlash: state.collisionFlash,
|
|
6774
|
+
sprays: Object.freeze(
|
|
6775
|
+
state.sprays.map(
|
|
6776
|
+
(spray) => Object.freeze({
|
|
6777
|
+
life: spray.life,
|
|
6778
|
+
position: Object.freeze({ ...spray.position }),
|
|
6779
|
+
velocity: Object.freeze({ ...spray.velocity })
|
|
6780
|
+
})
|
|
6781
|
+
)
|
|
6782
|
+
),
|
|
6783
|
+
ships: Object.freeze(
|
|
6784
|
+
state.ships.map(
|
|
6785
|
+
(ship) => Object.freeze({
|
|
6786
|
+
id: ship.id,
|
|
6787
|
+
position: Object.freeze({ ...ship.position }),
|
|
6788
|
+
velocity: Object.freeze({ ...ship.velocity }),
|
|
6789
|
+
rotationY: ship.rotationY,
|
|
6790
|
+
angularVelocity: ship.angularVelocity,
|
|
6791
|
+
tint: Object.freeze({ ...ship.tint })
|
|
6792
|
+
})
|
|
6793
|
+
)
|
|
6794
|
+
),
|
|
6795
|
+
waveImpulses: Object.freeze(
|
|
6796
|
+
state.waveImpulses.map(
|
|
6797
|
+
(impulse) => Object.freeze({
|
|
6798
|
+
x: impulse.x,
|
|
6799
|
+
z: impulse.z,
|
|
6800
|
+
strength: impulse.strength,
|
|
6801
|
+
radius: impulse.radius,
|
|
6802
|
+
life: impulse.life
|
|
6803
|
+
})
|
|
6804
|
+
)
|
|
6805
|
+
),
|
|
6806
|
+
physics: Object.freeze({
|
|
6807
|
+
profile: state.physics.profile,
|
|
6808
|
+
plan: state.physics.plan,
|
|
6809
|
+
manifest: state.physics.manifest,
|
|
6810
|
+
snapshot: state.physics.snapshot,
|
|
6811
|
+
shipPhysics: shipModel?.physics ?? null
|
|
6812
|
+
})
|
|
6813
|
+
});
|
|
6814
|
+
}
|
|
6815
|
+
function resolveSceneDescription(state, options, shipModel) {
|
|
6816
|
+
const scene = buildSceneSnapshot(state, shipModel);
|
|
6817
|
+
if (typeof options.describeState !== "function") {
|
|
6818
|
+
return { scene, description: null };
|
|
6819
|
+
}
|
|
6820
|
+
const description = options.describeState(state.packageState, scene) ?? null;
|
|
6821
|
+
return { scene, description };
|
|
6822
|
+
}
|
|
6823
|
+
function updatePackageState(state, options, shipModel, dt) {
|
|
6824
|
+
if (typeof options.updateState !== "function") {
|
|
6825
|
+
return;
|
|
6826
|
+
}
|
|
6827
|
+
const scene = buildSceneSnapshot(state, shipModel);
|
|
6828
|
+
const nextState = options.updateState(state.packageState, scene, dt);
|
|
6829
|
+
if (typeof nextState !== "undefined") {
|
|
6830
|
+
state.packageState = nextState;
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
function normalizeColorOverride(color, fallback) {
|
|
6834
|
+
if (!color || typeof color !== "object") {
|
|
6835
|
+
return fallback;
|
|
6836
|
+
}
|
|
6837
|
+
return {
|
|
6838
|
+
r: typeof color.r === "number" ? color.r : fallback.r,
|
|
6839
|
+
g: typeof color.g === "number" ? color.g : fallback.g,
|
|
6840
|
+
b: typeof color.b === "number" ? color.b : fallback.b
|
|
6841
|
+
};
|
|
6842
|
+
}
|
|
6843
|
+
function readVisualNumber(value, fallback) {
|
|
6844
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
6845
|
+
}
|
|
6846
|
+
function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {}) {
|
|
6847
|
+
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
6848
|
+
const defaults = {
|
|
6849
|
+
skyTop: premiumShadows ? "#f0f7fb" : "#e8f1f7",
|
|
6850
|
+
skyMid: premiumShadows ? "#c7d9e5" : "#b9ceda",
|
|
6851
|
+
skyBottom: premiumShadows ? "#84a7bd" : "#7b9bb0",
|
|
6852
|
+
seaTop: premiumShadows ? "#235064" : "#264c5f",
|
|
6853
|
+
seaMid: premiumShadows ? "#153e53" : "#173d4f",
|
|
6854
|
+
seaBottom: "#0b2433",
|
|
6855
|
+
sunCore: "rgba(255, 244, 210, 0.9)",
|
|
6856
|
+
reflectionStrength: lightingSnapshot.currentLevel.config.reflectionStrength,
|
|
6857
|
+
shadowAccent: lightingSnapshot.currentLevel.config.shadowStrength,
|
|
6858
|
+
waveAmplitude: 1,
|
|
6859
|
+
waveDirection: { x: 0.86, z: 0.34 },
|
|
6860
|
+
wavePhaseSpeed: 1,
|
|
6861
|
+
wakeStrength: 0.24,
|
|
6862
|
+
wakeLength: 15,
|
|
6863
|
+
collisionRippleStrength: 0.34,
|
|
6864
|
+
waterNear: { r: 0.12, g: 0.36, b: 0.46 },
|
|
6865
|
+
waterFar: { r: 0.28, g: 0.56, b: 0.68 },
|
|
6866
|
+
harborWall: { r: 0.48, g: 0.4, b: 0.32 },
|
|
6867
|
+
harborDeck: { r: 0.5, g: 0.34, b: 0.22 },
|
|
6868
|
+
harborTower: { r: 0.34, g: 0.32, b: 0.36 },
|
|
6869
|
+
flagColor: { r: 0.76, g: 0.24, b: 0.18 },
|
|
6870
|
+
flagMotion: 1
|
|
6871
|
+
};
|
|
6872
|
+
return {
|
|
6873
|
+
skyTop: typeof customVisuals.skyTop === "string" ? customVisuals.skyTop : defaults.skyTop,
|
|
6874
|
+
skyMid: typeof customVisuals.skyMid === "string" ? customVisuals.skyMid : defaults.skyMid,
|
|
6875
|
+
skyBottom: typeof customVisuals.skyBottom === "string" ? customVisuals.skyBottom : defaults.skyBottom,
|
|
6876
|
+
seaTop: typeof customVisuals.seaTop === "string" ? customVisuals.seaTop : defaults.seaTop,
|
|
6877
|
+
seaMid: typeof customVisuals.seaMid === "string" ? customVisuals.seaMid : defaults.seaMid,
|
|
6878
|
+
seaBottom: typeof customVisuals.seaBottom === "string" ? customVisuals.seaBottom : defaults.seaBottom,
|
|
6879
|
+
sunCore: typeof customVisuals.sunCore === "string" ? customVisuals.sunCore : defaults.sunCore,
|
|
6880
|
+
reflectionStrength: readVisualNumber(
|
|
6881
|
+
customVisuals.reflectionStrength,
|
|
6882
|
+
defaults.reflectionStrength
|
|
6883
|
+
),
|
|
6884
|
+
shadowAccent: readVisualNumber(customVisuals.shadowAccent, defaults.shadowAccent),
|
|
6885
|
+
waveAmplitude: readVisualNumber(customVisuals.waveAmplitude, defaults.waveAmplitude),
|
|
6886
|
+
waveDirection: customVisuals.waveDirection && typeof customVisuals.waveDirection.x === "number" && typeof customVisuals.waveDirection.z === "number" ? { x: customVisuals.waveDirection.x, z: customVisuals.waveDirection.z } : defaults.waveDirection,
|
|
6887
|
+
wavePhaseSpeed: readVisualNumber(customVisuals.wavePhaseSpeed, defaults.wavePhaseSpeed),
|
|
6888
|
+
wakeStrength: readVisualNumber(customVisuals.wakeStrength, defaults.wakeStrength),
|
|
6889
|
+
wakeLength: readVisualNumber(customVisuals.wakeLength, defaults.wakeLength),
|
|
6890
|
+
collisionRippleStrength: readVisualNumber(
|
|
6891
|
+
customVisuals.collisionRippleStrength,
|
|
6892
|
+
defaults.collisionRippleStrength
|
|
6893
|
+
),
|
|
6894
|
+
waterNear: normalizeColorOverride(customVisuals.waterNear, defaults.waterNear),
|
|
6895
|
+
waterFar: normalizeColorOverride(customVisuals.waterFar, defaults.waterFar),
|
|
6896
|
+
harborWall: normalizeColorOverride(customVisuals.harborWall, defaults.harborWall),
|
|
6897
|
+
harborDeck: normalizeColorOverride(customVisuals.harborDeck, defaults.harborDeck),
|
|
6898
|
+
harborTower: normalizeColorOverride(customVisuals.harborTower, defaults.harborTower),
|
|
6899
|
+
flagColor: normalizeColorOverride(customVisuals.flagColor, defaults.flagColor),
|
|
6900
|
+
flagMotion: readVisualNumber(customVisuals.flagMotion, defaults.flagMotion)
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6903
|
+
function buildClothSurface(model, state, meshDetail, visuals) {
|
|
6646
6904
|
const clothPlan = createClothRepresentationPlan({
|
|
6647
6905
|
garmentId: "shore-flag",
|
|
6648
6906
|
kind: state.focus === "cloth" ? "flag" : clothGarmentKinds[0],
|
|
@@ -6668,12 +6926,12 @@ function buildClothSurface(model, state, meshDetail) {
|
|
|
6668
6926
|
for (let column = 0; column < cols; column += 1) {
|
|
6669
6927
|
const u = column / (cols - 1);
|
|
6670
6928
|
const v = row / (rows - 1);
|
|
6671
|
-
const gust = Math.sin(time * 1.9 + v * 3.2 + u * 2.1) * continuity.broadMotionFloor;
|
|
6672
|
-
const wrinkle = Math.sin(time * 4.4 + u * 9.2 + v * 5.6) * continuity.wrinkleFloor * 0.22;
|
|
6929
|
+
const gust = Math.sin(time * 1.9 + v * 3.2 + u * 2.1) * continuity.broadMotionFloor * visuals.flagMotion;
|
|
6930
|
+
const wrinkle = Math.sin(time * 4.4 + u * 9.2 + v * 5.6) * continuity.wrinkleFloor * 0.22 * Math.max(0.55, visuals.flagMotion);
|
|
6673
6931
|
const x = origin.x + u * 1.8 + gust * 0.55 * (u * 0.9);
|
|
6674
6932
|
const y = origin.y - height * v + wrinkle * 0.2;
|
|
6675
6933
|
const z = origin.z + width * u + gust * 0.72 * (u * 0.85);
|
|
6676
|
-
const flap = Math.cos(time * 2.7 + u * 7.4 + v * 3.8) * continuity.broadMotionFloor * 0.28;
|
|
6934
|
+
const flap = Math.cos(time * 2.7 + u * 7.4 + v * 3.8) * continuity.broadMotionFloor * 0.28 * visuals.flagMotion;
|
|
6677
6935
|
positions.push(vec3(x + flap, y, z));
|
|
6678
6936
|
}
|
|
6679
6937
|
}
|
|
@@ -6691,15 +6949,74 @@ function buildClothSurface(model, state, meshDetail) {
|
|
|
6691
6949
|
band,
|
|
6692
6950
|
representation,
|
|
6693
6951
|
continuity,
|
|
6952
|
+
color: visuals.flagColor,
|
|
6694
6953
|
positions,
|
|
6695
6954
|
indices,
|
|
6696
6955
|
grid: { rows, cols }
|
|
6697
6956
|
};
|
|
6698
6957
|
}
|
|
6699
|
-
function
|
|
6700
|
-
|
|
6958
|
+
function resolveWaveDirection(state) {
|
|
6959
|
+
const direction = state.demoVisuals?.waveDirection;
|
|
6960
|
+
if (direction && typeof direction === "object" && typeof direction.x === "number" && typeof direction.z === "number") {
|
|
6961
|
+
return normalizeVec3(vec3(direction.x, 0, direction.z));
|
|
6962
|
+
}
|
|
6963
|
+
return normalizeVec3(vec3(0.86, 0, 0.34));
|
|
6701
6964
|
}
|
|
6702
|
-
function
|
|
6965
|
+
function sampleShipWake(state, x, z, time) {
|
|
6966
|
+
const wakeStrength = readVisualNumber(state.demoVisuals?.wakeStrength, 0.24);
|
|
6967
|
+
const wakeLength = readVisualNumber(state.demoVisuals?.wakeLength, 15);
|
|
6968
|
+
let total = 0;
|
|
6969
|
+
for (const ship of state.ships) {
|
|
6970
|
+
const speed = Math.hypot(ship.velocity.x, ship.velocity.z);
|
|
6971
|
+
if (speed <= 0.05) {
|
|
6972
|
+
continue;
|
|
6973
|
+
}
|
|
6974
|
+
const direction = normalizeVec3(vec3(ship.velocity.x, 0, ship.velocity.z));
|
|
6975
|
+
const behind = scaleVec3(direction, -1);
|
|
6976
|
+
const lateral = vec3(-direction.z, 0, direction.x);
|
|
6977
|
+
const delta = vec3(x - ship.position.x, 0, z - ship.position.z);
|
|
6978
|
+
const along = dotVec3(delta, behind);
|
|
6979
|
+
if (along < 0 || along > wakeLength) {
|
|
6980
|
+
continue;
|
|
6981
|
+
}
|
|
6982
|
+
const cross = Math.abs(dotVec3(delta, lateral));
|
|
6983
|
+
const width = 0.9 + along * 0.2;
|
|
6984
|
+
if (cross > width * 3.2) {
|
|
6985
|
+
continue;
|
|
6986
|
+
}
|
|
6987
|
+
const envelope = Math.exp(-along * 0.14) * Math.exp(-(cross * cross / Math.max(0.4, width * width * 2.4)));
|
|
6988
|
+
total += Math.sin(along * 1.6 - time * 4.2) * speed * wakeStrength * envelope;
|
|
6989
|
+
}
|
|
6990
|
+
return total;
|
|
6991
|
+
}
|
|
6992
|
+
function sampleWaveImpulses(state, x, z, time) {
|
|
6993
|
+
const rippleStrength = readVisualNumber(state.demoVisuals?.collisionRippleStrength, 0.34);
|
|
6994
|
+
let total = 0;
|
|
6995
|
+
for (const impulse of state.waveImpulses) {
|
|
6996
|
+
const dx = x - impulse.x;
|
|
6997
|
+
const dz = z - impulse.z;
|
|
6998
|
+
const distance = Math.hypot(dx, dz);
|
|
6999
|
+
const radius = impulse.radius + (1 - impulse.life) * 4.8;
|
|
7000
|
+
if (distance > radius * 2.8) {
|
|
7001
|
+
continue;
|
|
7002
|
+
}
|
|
7003
|
+
const phase = distance * 1.8 - (1 - impulse.life) * 10 - time * 0.4;
|
|
7004
|
+
const envelope = Math.exp(-distance / Math.max(0.1, radius)) * impulse.life;
|
|
7005
|
+
total += Math.sin(phase) * impulse.strength * rippleStrength * envelope * 0.18;
|
|
7006
|
+
}
|
|
7007
|
+
return total;
|
|
7008
|
+
}
|
|
7009
|
+
function sampleWave(state, x, z, time) {
|
|
7010
|
+
const direction = resolveWaveDirection(state);
|
|
7011
|
+
const lateral = vec3(-direction.z, 0, direction.x);
|
|
7012
|
+
const along = x * direction.x + z * direction.z;
|
|
7013
|
+
const cross = x * lateral.x + z * lateral.z;
|
|
7014
|
+
const phaseSpeed = readVisualNumber(state.demoVisuals?.wavePhaseSpeed, 1);
|
|
7015
|
+
const amplitude = readVisualNumber(state.demoVisuals?.waveAmplitude, 1);
|
|
7016
|
+
const base = Math.sin(along * 0.22 - time * 1.12 * phaseSpeed) * 0.42 + Math.cos(along * 0.11 + cross * 0.07 - time * 0.78 * phaseSpeed) * 0.26 + Math.sin(cross * 0.19 - time * 1.34 * phaseSpeed) * 0.16;
|
|
7017
|
+
return base * amplitude + sampleShipWake(state, x, z, time) + sampleWaveImpulses(state, x, z, time);
|
|
7018
|
+
}
|
|
7019
|
+
function buildWaterBands(state, fluidDetail, visuals) {
|
|
6703
7020
|
const fluidPlan = createFluidRepresentationPlan({
|
|
6704
7021
|
fluidBodyId: "harbor",
|
|
6705
7022
|
kind: state.focus === "fluid" ? "ocean" : fluidBodyKinds[0],
|
|
@@ -6732,7 +7049,7 @@ function buildWaterBands(state, fluidDetail) {
|
|
|
6732
7049
|
const v = row / (rows - 1);
|
|
6733
7050
|
const x = originX + bandSpec.width * u;
|
|
6734
7051
|
const z = originZ + bandSpec.depth * v;
|
|
6735
|
-
const y = bandSpec.y + sampleWave(x, z, state.time) * continuity.amplitudeFloor * (bandSpec.band === "near" ? 0.9 : bandSpec.band === "mid" ? 0.55 : 0.3);
|
|
7052
|
+
const y = bandSpec.y + sampleWave(state, x, z, state.time) * continuity.amplitudeFloor * (bandSpec.band === "near" ? 0.9 : bandSpec.band === "mid" ? 0.55 : 0.3);
|
|
6736
7053
|
positions.push(vec3(x, y, z));
|
|
6737
7054
|
}
|
|
6738
7055
|
}
|
|
@@ -6753,7 +7070,15 @@ function buildWaterBands(state, fluidDetail) {
|
|
|
6753
7070
|
cols,
|
|
6754
7071
|
positions,
|
|
6755
7072
|
indices,
|
|
6756
|
-
color: bandSpec.band === "near" ?
|
|
7073
|
+
color: bandSpec.band === "near" ? visuals.waterNear : bandSpec.band === "mid" ? {
|
|
7074
|
+
r: mix(visuals.waterNear.r, visuals.waterFar.r, 0.4),
|
|
7075
|
+
g: mix(visuals.waterNear.g, visuals.waterFar.g, 0.4),
|
|
7076
|
+
b: mix(visuals.waterNear.b, visuals.waterFar.b, 0.4)
|
|
7077
|
+
} : bandSpec.band === "far" ? visuals.waterFar : {
|
|
7078
|
+
r: mix(visuals.waterFar.r, 0.76, 0.2),
|
|
7079
|
+
g: mix(visuals.waterFar.g, 0.78, 0.2),
|
|
7080
|
+
b: mix(visuals.waterFar.b, 0.82, 0.2)
|
|
7081
|
+
}
|
|
6757
7082
|
});
|
|
6758
7083
|
}
|
|
6759
7084
|
return { fluidPlan, bandMeshes };
|
|
@@ -6792,6 +7117,9 @@ function createSceneState(options) {
|
|
|
6792
7117
|
clothDetail,
|
|
6793
7118
|
lightingDetail,
|
|
6794
7119
|
debugSession,
|
|
7120
|
+
packageState: void 0,
|
|
7121
|
+
demoDescription: null,
|
|
7122
|
+
demoVisuals: null,
|
|
6795
7123
|
time: 0,
|
|
6796
7124
|
lastTimeMs: null,
|
|
6797
7125
|
paused: false,
|
|
@@ -6819,7 +7147,9 @@ function createSceneState(options) {
|
|
|
6819
7147
|
}
|
|
6820
7148
|
],
|
|
6821
7149
|
sprays: [],
|
|
7150
|
+
waveImpulses: [],
|
|
6822
7151
|
frame: 0,
|
|
7152
|
+
contactCount: 0,
|
|
6823
7153
|
collisionCount: 0,
|
|
6824
7154
|
collisionFlash: 0,
|
|
6825
7155
|
physics: {
|
|
@@ -6834,24 +7164,24 @@ function createSceneState(options) {
|
|
|
6834
7164
|
function setListContent(element, values) {
|
|
6835
7165
|
element.innerHTML = values.map((value) => `<li>${value}</li>`).join("");
|
|
6836
7166
|
}
|
|
6837
|
-
function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, shadowStrength) {
|
|
7167
|
+
function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, shadowStrength, visuals) {
|
|
6838
7168
|
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
6839
7169
|
const sky = ctx.createLinearGradient(0, 0, 0, canvas.height * 0.5);
|
|
6840
|
-
sky.addColorStop(0,
|
|
6841
|
-
sky.addColorStop(0.6,
|
|
6842
|
-
sky.addColorStop(1,
|
|
7170
|
+
sky.addColorStop(0, visuals.skyTop);
|
|
7171
|
+
sky.addColorStop(0.6, visuals.skyMid);
|
|
7172
|
+
sky.addColorStop(1, visuals.skyBottom);
|
|
6843
7173
|
ctx.fillStyle = sky;
|
|
6844
7174
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
6845
7175
|
const shoreline = ctx.createLinearGradient(0, canvas.height * 0.45, 0, canvas.height);
|
|
6846
|
-
shoreline.addColorStop(0,
|
|
6847
|
-
shoreline.addColorStop(0.52,
|
|
6848
|
-
shoreline.addColorStop(1,
|
|
7176
|
+
shoreline.addColorStop(0, visuals.seaTop);
|
|
7177
|
+
shoreline.addColorStop(0.52, visuals.seaMid);
|
|
7178
|
+
shoreline.addColorStop(1, visuals.seaBottom);
|
|
6849
7179
|
ctx.fillStyle = shoreline;
|
|
6850
7180
|
ctx.fillRect(0, canvas.height * 0.45, canvas.width, canvas.height * 0.55);
|
|
6851
7181
|
const sunX = mix(canvas.width * 0.16, canvas.width * 0.84, (Math.sin(state.time * 0.12) + 1) * 0.5);
|
|
6852
7182
|
const sunY = canvas.height * 0.14 + Math.cos(state.time * 0.12) * 22;
|
|
6853
7183
|
const sun = ctx.createRadialGradient(sunX, sunY, 10, sunX, sunY, 90);
|
|
6854
|
-
sun.addColorStop(0,
|
|
7184
|
+
sun.addColorStop(0, visuals.sunCore);
|
|
6855
7185
|
sun.addColorStop(1, "rgba(255, 244, 210, 0)");
|
|
6856
7186
|
ctx.fillStyle = sun;
|
|
6857
7187
|
ctx.beginPath();
|
|
@@ -6927,27 +7257,27 @@ function renderProjectedShadow(ctx, worldPoints, camera, viewport, lightDir, opt
|
|
|
6927
7257
|
ctx.fill();
|
|
6928
7258
|
ctx.restore();
|
|
6929
7259
|
}
|
|
6930
|
-
function pushHarborGeometry(camera, viewport, triangles) {
|
|
7260
|
+
function pushHarborGeometry(camera, viewport, triangles, visuals) {
|
|
6931
7261
|
const harborObjects = [
|
|
6932
7262
|
{
|
|
6933
7263
|
position: vec3(-8.2, 1.1, -0.9),
|
|
6934
7264
|
rotationY: -0.16,
|
|
6935
7265
|
scale: { x: 5.4, y: 2.4, z: 4.2 },
|
|
6936
|
-
color:
|
|
7266
|
+
color: visuals.harborWall,
|
|
6937
7267
|
accent: 0.06
|
|
6938
7268
|
},
|
|
6939
7269
|
{
|
|
6940
7270
|
position: vec3(-5.7, 0.45, 1.4),
|
|
6941
7271
|
rotationY: -0.08,
|
|
6942
7272
|
scale: { x: 6.8, y: 0.3, z: 2.1 },
|
|
6943
|
-
color:
|
|
7273
|
+
color: visuals.harborDeck,
|
|
6944
7274
|
accent: 0.04
|
|
6945
7275
|
},
|
|
6946
7276
|
{
|
|
6947
7277
|
position: vec3(-10.4, 0.28, 0.8),
|
|
6948
7278
|
rotationY: 0.22,
|
|
6949
7279
|
scale: { x: 1.2, y: 0.9, z: 1.2 },
|
|
6950
|
-
color:
|
|
7280
|
+
color: visuals.harborTower,
|
|
6951
7281
|
accent: 0.02
|
|
6952
7282
|
}
|
|
6953
7283
|
];
|
|
@@ -7027,7 +7357,7 @@ function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
|
7027
7357
|
cloth.grid.rows * cloth.grid.cols - 1,
|
|
7028
7358
|
(cloth.grid.rows - 1) * cloth.grid.cols
|
|
7029
7359
|
];
|
|
7030
|
-
ctx.fillStyle =
|
|
7360
|
+
ctx.fillStyle = colorToRgba(cloth.color, 0.95);
|
|
7031
7361
|
for (const index of borderIndices) {
|
|
7032
7362
|
const point = projected[index];
|
|
7033
7363
|
if (!point) {
|
|
@@ -7088,12 +7418,13 @@ function updateShips(state, dt, shipModel) {
|
|
|
7088
7418
|
const physics = shipModel.physics;
|
|
7089
7419
|
const halfExtents = physics.halfExtents ?? [1.35, 0.95, 3.9];
|
|
7090
7420
|
let collided = false;
|
|
7421
|
+
state.contactCount = 0;
|
|
7091
7422
|
for (const ship of state.ships) {
|
|
7092
7423
|
ship.position = addVec3(ship.position, scaleVec3(ship.velocity, dt));
|
|
7093
7424
|
ship.rotationY += ship.angularVelocity * dt;
|
|
7094
7425
|
ship.velocity = scaleVec3(ship.velocity, 1 - (physics.linearDamping ?? 0.04) * dt);
|
|
7095
7426
|
ship.angularVelocity *= 1 - (physics.angularDamping ?? 0.08) * dt;
|
|
7096
|
-
ship.position.y = sampleWave(ship.position.x, ship.position.z, state.time) * 0.22 + (physics.waterline ?? 0.42);
|
|
7427
|
+
ship.position.y = sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.22 + (physics.waterline ?? 0.42);
|
|
7097
7428
|
if (Math.abs(ship.position.x) > 10) {
|
|
7098
7429
|
ship.velocity.x *= -1;
|
|
7099
7430
|
ship.angularVelocity *= -1;
|
|
@@ -7120,11 +7451,25 @@ function updateShips(state, dt, shipModel) {
|
|
|
7120
7451
|
b.angularVelocity -= 0.55;
|
|
7121
7452
|
const contactPoint = vec3((a.position.x + b.position.x) * 0.5, (a.position.y + b.position.y) * 0.5 + 0.1, (a.position.z + b.position.z) * 0.5);
|
|
7122
7453
|
spawnSpray(state, contactPoint, Math.abs(dx) + Math.abs(dz));
|
|
7454
|
+
state.waveImpulses.push({
|
|
7455
|
+
x: contactPoint.x,
|
|
7456
|
+
z: contactPoint.z,
|
|
7457
|
+
strength: Math.min(1.4, 0.2 + (Math.abs(dx) + Math.abs(dz)) * 0.18),
|
|
7458
|
+
radius: 0.8,
|
|
7459
|
+
life: 1
|
|
7460
|
+
});
|
|
7123
7461
|
state.collisionCount += 1;
|
|
7462
|
+
state.contactCount = 1;
|
|
7124
7463
|
collided = true;
|
|
7125
7464
|
}
|
|
7126
7465
|
state.collisionFlash = collided ? 1 : Math.max(0, state.collisionFlash - dt * 1.8);
|
|
7127
7466
|
}
|
|
7467
|
+
function updateWaveImpulses(state, dt) {
|
|
7468
|
+
state.waveImpulses = state.waveImpulses.map((impulse) => ({
|
|
7469
|
+
...impulse,
|
|
7470
|
+
life: impulse.life - dt * 0.55
|
|
7471
|
+
})).filter((impulse) => impulse.life > 0);
|
|
7472
|
+
}
|
|
7128
7473
|
function updateSpray(state, dt) {
|
|
7129
7474
|
state.sprays = state.sprays.map((particle) => {
|
|
7130
7475
|
const nextVelocity = vec3(particle.velocity.x, particle.velocity.y - 4.2 * dt, particle.velocity.z);
|
|
@@ -7238,7 +7583,7 @@ function renderShipShadow(ctx, shipModel, ship, state, camera, viewport, lightDi
|
|
|
7238
7583
|
vec3(bounds.min[0], keelY, bounds.max[2])
|
|
7239
7584
|
].map((point) => transformPoint(point, transform));
|
|
7240
7585
|
renderProjectedShadow(ctx, hullCorners, camera, viewport, lightDir, {
|
|
7241
|
-
planeY: sampleWave(ship.position.x, ship.position.z, state.time) * 0.24 - 0.03,
|
|
7586
|
+
planeY: sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.24 - 0.03,
|
|
7242
7587
|
alpha: 0.08 + shadowStrength * 0.2,
|
|
7243
7588
|
blur: 14 + shadowStrength * 24
|
|
7244
7589
|
});
|
|
@@ -7267,12 +7612,30 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7267
7612
|
const nearLighting = lightingPlan.bands.find((entry) => entry.band === "near") ?? lightingPlan.bands[0];
|
|
7268
7613
|
const lightDir = normalizeVec3(vec3(-0.45, 0.85, -0.24));
|
|
7269
7614
|
const lightingSnapshot = state.lightingDetail.getSnapshot();
|
|
7270
|
-
const
|
|
7271
|
-
|
|
7272
|
-
|
|
7615
|
+
const visuals = resolveVisualConfig(
|
|
7616
|
+
nearLighting,
|
|
7617
|
+
lightingSnapshot,
|
|
7618
|
+
state.demoDescription?.visuals
|
|
7619
|
+
);
|
|
7620
|
+
state.demoVisuals = visuals;
|
|
7621
|
+
const reflectionStrength = visuals.reflectionStrength;
|
|
7622
|
+
const shadowStrength = visuals.shadowAccent;
|
|
7623
|
+
drawSkyAndShore(
|
|
7624
|
+
ctx,
|
|
7625
|
+
canvas,
|
|
7626
|
+
state,
|
|
7627
|
+
nearLighting,
|
|
7628
|
+
reflectionStrength,
|
|
7629
|
+
shadowStrength,
|
|
7630
|
+
visuals
|
|
7631
|
+
);
|
|
7273
7632
|
const triangles = [];
|
|
7274
|
-
pushHarborGeometry(camera, viewport, triangles);
|
|
7275
|
-
const water = buildWaterBands(
|
|
7633
|
+
pushHarborGeometry(camera, viewport, triangles, visuals);
|
|
7634
|
+
const water = buildWaterBands(
|
|
7635
|
+
state,
|
|
7636
|
+
state.fluidDetail.getSnapshot().currentLevel.config,
|
|
7637
|
+
visuals
|
|
7638
|
+
);
|
|
7276
7639
|
for (const bandMesh of water.bandMeshes) {
|
|
7277
7640
|
const bandAccent = bandMesh.band === "near" ? 0.06 : bandMesh.band === "mid" ? 0.04 : 0;
|
|
7278
7641
|
for (let index = 0; index < bandMesh.indices.length; index += 3) {
|
|
@@ -7294,7 +7657,12 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7294
7657
|
});
|
|
7295
7658
|
}
|
|
7296
7659
|
}
|
|
7297
|
-
const cloth = buildClothSurface(
|
|
7660
|
+
const cloth = buildClothSurface(
|
|
7661
|
+
state,
|
|
7662
|
+
state,
|
|
7663
|
+
state.clothDetail.getSnapshot().currentLevel.config,
|
|
7664
|
+
visuals
|
|
7665
|
+
);
|
|
7298
7666
|
for (let index = 0; index < cloth.indices.length; index += 3) {
|
|
7299
7667
|
const a = cloth.positions[cloth.indices[index]];
|
|
7300
7668
|
const b = cloth.positions[cloth.indices[index + 1]];
|
|
@@ -7309,7 +7677,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7309
7677
|
depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
|
|
7310
7678
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
7311
7679
|
normal,
|
|
7312
|
-
baseColor:
|
|
7680
|
+
baseColor: cloth.color,
|
|
7313
7681
|
accent: cloth.band === "near" ? 0.1 : 0.04
|
|
7314
7682
|
});
|
|
7315
7683
|
}
|
|
@@ -7372,15 +7740,26 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7372
7740
|
"The ships collide on GLTF-derived hull volumes while cloth and fluid remain downstream visual consumers.",
|
|
7373
7741
|
"Near-field lighting keeps the ray-traced-primary shadow impression so the collision read stays crisp."
|
|
7374
7742
|
] : SCENE_NOTES;
|
|
7375
|
-
|
|
7376
|
-
setListContent(
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7743
|
+
const custom = state.demoDescription ?? null;
|
|
7744
|
+
setListContent(
|
|
7745
|
+
dom.sceneMetrics,
|
|
7746
|
+
Array.isArray(custom?.sceneMetrics) ? custom.sceneMetrics : sceneMetrics
|
|
7747
|
+
);
|
|
7748
|
+
setListContent(
|
|
7749
|
+
dom.qualityMetrics,
|
|
7750
|
+
Array.isArray(custom?.qualityMetrics) ? custom.qualityMetrics : qualityMetrics
|
|
7751
|
+
);
|
|
7752
|
+
setListContent(
|
|
7753
|
+
dom.debugMetrics,
|
|
7754
|
+
Array.isArray(custom?.debugMetrics) ? custom.debugMetrics : debugMetrics
|
|
7755
|
+
);
|
|
7756
|
+
setListContent(dom.sceneNotes, Array.isArray(custom?.notes) ? custom.notes : sceneNotes);
|
|
7757
|
+
dom.status.textContent = typeof custom?.status === "string" ? custom.status : `3D scene live \xB7 ${state.lastDecision.metrics.fps.toFixed(1)} FPS`;
|
|
7758
|
+
dom.details.textContent = typeof custom?.details === "string" ? custom.details : state.focus === "physics" ? `Stable world snapshots are emitted from ${state.physics.plan.snapshotStageId} after the authoritative solver; GLTF ships collide on ${shipModel.physics.shape ?? "box"} volumes while visual follow-up remains downstream.` : `GLTF ships are colliding with ${shipModel.physics.shape ?? "box"} physics volumes; cloth and fluid remain continuous while the governor pressure is ${state.lastDecision.pressureLevel}.`;
|
|
7381
7759
|
}
|
|
7382
7760
|
function updateSceneState(state, dt, shipModel) {
|
|
7383
7761
|
updateShips(state, dt, shipModel);
|
|
7762
|
+
updateWaveImpulses(state, dt);
|
|
7384
7763
|
updateSpray(state, dt);
|
|
7385
7764
|
updatePhysicsSnapshot(state, shipModel);
|
|
7386
7765
|
}
|
|
@@ -7399,13 +7778,15 @@ function syncTextState(state, shipModel) {
|
|
|
7399
7778
|
})),
|
|
7400
7779
|
shipPhysics: shipModel.physics,
|
|
7401
7780
|
sprays: state.sprays.length,
|
|
7781
|
+
waveImpulses: state.waveImpulses.length,
|
|
7402
7782
|
pressure: state.lastDecision?.pressureLevel ?? "stable",
|
|
7403
7783
|
physics: {
|
|
7404
7784
|
profile: state.physics.profile,
|
|
7405
7785
|
snapshotStageId: state.physics.plan.snapshotStageId,
|
|
7406
7786
|
workerJobCount: state.physics.manifest.jobs.length,
|
|
7407
7787
|
snapshot: state.physics.snapshot
|
|
7408
|
-
}
|
|
7788
|
+
},
|
|
7789
|
+
package: state.demoDescription?.textState ?? null
|
|
7409
7790
|
};
|
|
7410
7791
|
window.render_game_to_text = () => JSON.stringify(snapshot);
|
|
7411
7792
|
window.advanceTime = (ms) => {
|
|
@@ -7421,6 +7802,9 @@ function syncTextState(state, shipModel) {
|
|
|
7421
7802
|
async function mountGpuShowcase(options = {}) {
|
|
7422
7803
|
injectStyles();
|
|
7423
7804
|
const root = options.root ?? document.body;
|
|
7805
|
+
const previousMarkup = root.innerHTML;
|
|
7806
|
+
const previousRenderGameToText = window.render_game_to_text;
|
|
7807
|
+
const previousAdvanceTime = window.advanceTime;
|
|
7424
7808
|
const focus = options.focus ?? new URLSearchParams(window.location.search).get("focus") ?? "integrated";
|
|
7425
7809
|
const dom = buildDemoDom(root, {
|
|
7426
7810
|
packageName: options.packageName ?? "@plasius/gpu-demo-viewer",
|
|
@@ -7431,11 +7815,18 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7431
7815
|
const state = createSceneState({ focus });
|
|
7432
7816
|
const shipModel = await loadGltfModel(resolveShowcaseAssetUrl());
|
|
7433
7817
|
state.shipModel = shipModel;
|
|
7818
|
+
state.packageState = typeof options.createState === "function" ? options.createState() : void 0;
|
|
7434
7819
|
updatePhysicsSnapshot(state, shipModel);
|
|
7435
7820
|
state.lastDecision = recordTelemetry(state, 16.4);
|
|
7821
|
+
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
7436
7822
|
syncTextState(state, shipModel);
|
|
7437
7823
|
const ctx = dom.canvas.getContext("2d");
|
|
7824
|
+
let destroyed = false;
|
|
7825
|
+
let frameHandle = null;
|
|
7438
7826
|
const renderFrame = (nowMs) => {
|
|
7827
|
+
if (destroyed) {
|
|
7828
|
+
return;
|
|
7829
|
+
}
|
|
7439
7830
|
if (!state.paused) {
|
|
7440
7831
|
if (state.lastTimeMs == null) {
|
|
7441
7832
|
state.lastTimeMs = nowMs;
|
|
@@ -7445,32 +7836,60 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7445
7836
|
state.time += dt;
|
|
7446
7837
|
state.frame += 1;
|
|
7447
7838
|
updateSceneState(state, dt, shipModel);
|
|
7839
|
+
updatePackageState(state, options, shipModel, dt);
|
|
7448
7840
|
const syntheticFrame = 14.2 + state.sprays.length * 0.1 + (state.stress ? 6.4 : 0);
|
|
7449
7841
|
state.lastDecision = recordTelemetry(state, syntheticFrame);
|
|
7450
7842
|
}
|
|
7843
|
+
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
7451
7844
|
renderScene(ctx, dom.canvas, state, shipModel, dom);
|
|
7452
7845
|
syncTextState(state, shipModel);
|
|
7453
|
-
requestAnimationFrame(renderFrame);
|
|
7846
|
+
frameHandle = requestAnimationFrame(renderFrame);
|
|
7454
7847
|
};
|
|
7455
|
-
|
|
7848
|
+
const handlePauseClick = () => {
|
|
7456
7849
|
state.paused = !state.paused;
|
|
7457
7850
|
dom.pauseButton.textContent = state.paused ? "Resume" : "Pause";
|
|
7458
|
-
}
|
|
7459
|
-
|
|
7851
|
+
};
|
|
7852
|
+
const handleStressChange = () => {
|
|
7460
7853
|
state.stress = dom.stressToggle.checked;
|
|
7461
|
-
}
|
|
7462
|
-
|
|
7854
|
+
};
|
|
7855
|
+
const handleFocusChange = () => {
|
|
7463
7856
|
state.focus = dom.focusMode.value;
|
|
7464
7857
|
Object.assign(state.camera, {
|
|
7465
7858
|
...CAMERA_PRESETS[state.focus],
|
|
7466
7859
|
target: vec3(...CAMERA_PRESETS[state.focus].target)
|
|
7467
7860
|
});
|
|
7468
|
-
}
|
|
7469
|
-
|
|
7861
|
+
};
|
|
7862
|
+
dom.pauseButton.addEventListener("click", handlePauseClick);
|
|
7863
|
+
dom.stressToggle.addEventListener("change", handleStressChange);
|
|
7864
|
+
dom.focusMode.addEventListener("change", handleFocusChange);
|
|
7865
|
+
frameHandle = requestAnimationFrame(renderFrame);
|
|
7470
7866
|
return {
|
|
7471
7867
|
state,
|
|
7472
7868
|
shipModel,
|
|
7473
|
-
canvas: dom.canvas
|
|
7869
|
+
canvas: dom.canvas,
|
|
7870
|
+
destroy() {
|
|
7871
|
+
if (destroyed) {
|
|
7872
|
+
return;
|
|
7873
|
+
}
|
|
7874
|
+
destroyed = true;
|
|
7875
|
+
if (frameHandle != null) {
|
|
7876
|
+
cancelAnimationFrame(frameHandle);
|
|
7877
|
+
}
|
|
7878
|
+
dom.pauseButton.removeEventListener("click", handlePauseClick);
|
|
7879
|
+
dom.stressToggle.removeEventListener("change", handleStressChange);
|
|
7880
|
+
dom.focusMode.removeEventListener("change", handleFocusChange);
|
|
7881
|
+
root.innerHTML = previousMarkup;
|
|
7882
|
+
if (typeof previousRenderGameToText === "function") {
|
|
7883
|
+
window.render_game_to_text = previousRenderGameToText;
|
|
7884
|
+
} else {
|
|
7885
|
+
delete window.render_game_to_text;
|
|
7886
|
+
}
|
|
7887
|
+
if (typeof previousAdvanceTime === "function") {
|
|
7888
|
+
window.advanceTime = previousAdvanceTime;
|
|
7889
|
+
} else {
|
|
7890
|
+
delete window.advanceTime;
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7474
7893
|
};
|
|
7475
7894
|
}
|
|
7476
7895
|
function updatePhysicsSnapshot(state, shipModel) {
|
|
@@ -7487,6 +7906,7 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
7487
7906
|
contactCount: state.collisionFlash > 0.02 ? 1 : 0,
|
|
7488
7907
|
metadata: {
|
|
7489
7908
|
collisionCount: state.collisionCount,
|
|
7909
|
+
contactCount: state.contactCount,
|
|
7490
7910
|
snapshotStageId: state.physics.plan.snapshotStageId,
|
|
7491
7911
|
rigidBodyShape: shipModel.physics.shape ?? "box"
|
|
7492
7912
|
}
|