@aura3d/engine 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -14
- package/dist/aura3d-cli/asset-core-types.d.ts +18 -2
- package/dist/aura3d-cli/asset-core-types.d.ts.map +1 -1
- package/dist/create-aura3d/index.js +1 -1
- package/dist/create-aura3d/showcase-spec-artifacts.d.ts +1 -0
- package/dist/create-aura3d/showcase-spec-artifacts.d.ts.map +1 -1
- package/dist/create-aura3d/showcase-spec-artifacts.js +1 -0
- package/dist/create-aura3d/showcase-spec-artifacts.js.map +1 -1
- package/dist/create-aura3d/showcase-spec-evidence.d.ts.map +1 -1
- package/dist/create-aura3d/showcase-spec-evidence.js +66 -0
- package/dist/create-aura3d/showcase-spec-evidence.js.map +1 -1
- package/dist/create-aura3d/showcase-spec-parser.d.ts.map +1 -1
- package/dist/create-aura3d/showcase-spec-parser.js +28 -0
- package/dist/create-aura3d/showcase-spec-parser.js.map +1 -1
- package/dist/create-aura3d/showcase-spec-platformer-artifacts.d.ts.map +1 -1
- package/dist/create-aura3d/showcase-spec-platformer-artifacts.js +2 -4
- package/dist/create-aura3d/showcase-spec-platformer-artifacts.js.map +1 -1
- package/dist/create-aura3d/showcase-spec-types.d.ts +15 -0
- package/dist/create-aura3d/showcase-spec-types.d.ts.map +1 -1
- package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts +1 -0
- package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts.map +1 -1
- package/dist/engine/agent-api/GameSceneGeometryBindings.js +1 -1
- package/dist/engine/agent-api/GameSceneGeometryBindings.js.map +1 -1
- package/dist/engine/agent-api/PublicGameGeometry.d.ts +117 -0
- package/dist/engine/agent-api/PublicGameGeometry.d.ts.map +1 -0
- package/dist/engine/agent-api/PublicGameGeometry.js +118 -0
- package/dist/engine/agent-api/PublicGameGeometry.js.map +1 -0
- package/dist/engine/agent-api/index.d.ts +119 -2
- package/dist/engine/agent-api/index.d.ts.map +1 -1
- package/dist/engine/agent-api/index.js +608 -56
- package/dist/engine/agent-api/index.js.map +1 -1
- package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
- package/package.json +29 -31
|
@@ -54,8 +54,10 @@ import { createRuntimeNodeSpec } from "./GameSceneBridge.js";
|
|
|
54
54
|
import { createFightingGameKit, fighting as fightingGameKit } from "./game-kits/fighting.js";
|
|
55
55
|
import { GAME_FALLING_BLOCK_PIECES, createGameAssetBoundPlatformerLevel, createGameAssetBoundRacingRoute, createGameFallingBlocksKit, createGameLocomotionKit, createGamePlatformerKit, createGameRacingKit } from "./GameGenreKits.js";
|
|
56
56
|
import { createGamePlatformerSceneBinding, createGamePlatformerPresentationCamera, createGameRacingPresentationCamera, createGameRacingSceneBinding } from "./GameSceneGeometryBindings.js";
|
|
57
|
+
import { certifyPublicPlatformerGeometry, certifyPublicRacingGeometry } from "./PublicGameGeometry.js";
|
|
57
58
|
export { GAME_FALLING_BLOCK_PIECES, createGameAssetBoundPlatformerLevel, createGameAssetBoundRacingRoute, createGameFallingBlocksKit, createGameLocomotionKit, createGamePlatformerKit, createGameRacingKit } from "./GameGenreKits.js";
|
|
58
59
|
export { createGamePlatformerSceneBinding, createGamePlatformerPresentationCamera, createGameRacingPresentationCamera, createGameRacingSceneBinding } from "./GameSceneGeometryBindings.js";
|
|
60
|
+
export { certifyPublicPlatformerGeometry, certifyPublicRacingGeometry } from "./PublicGameGeometry.js";
|
|
59
61
|
import { createPromptAnimationEpisodePlan, createPromptAnimationStoryBible, definePromptAnimationStoryboard } from "./PromptAnimationContract.js";
|
|
60
62
|
import { applyShotPlaybackFrame, createShotPlaybackPlan, createShotTimeline, installShotPlayback, sampleShotPlaybackPlan } from "./ShotTimeline.js";
|
|
61
63
|
import { captionCueAtTime, createCaptionTimingProof, deriveCaptionTrackFromDialogue } from "./DialoguePerformance.js";
|
|
@@ -3626,19 +3628,19 @@ export function createAuraGameRuntime(options = {}) {
|
|
|
3626
3628
|
bodies: []
|
|
3627
3629
|
};
|
|
3628
3630
|
}
|
|
3629
|
-
export function
|
|
3631
|
+
export function createGameRacingRoadMeshNodes(options) {
|
|
3630
3632
|
const mode = options.mode ?? "standalone";
|
|
3631
|
-
const routePoints = options.
|
|
3633
|
+
const routePoints = racingPresentationRoutePoints(options.sceneBinding, options.route, options.roadY);
|
|
3632
3634
|
if (routePoints.length < 2)
|
|
3633
3635
|
return [];
|
|
3634
|
-
const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
|
|
3635
|
-
if (mode === "asset-overlay" && guideVisibility === "public")
|
|
3636
|
-
return [];
|
|
3637
3636
|
const isCircuitStage = mode === "game-circuit";
|
|
3638
|
-
const roadWidth =
|
|
3637
|
+
const roadWidth = racingPresentationRoadWidth(options.sceneBinding, options.route, mode);
|
|
3638
|
+
const markingVisibility = options.markingVisibility ?? "full";
|
|
3639
3639
|
const nodes = [];
|
|
3640
|
-
|
|
3641
|
-
|
|
3640
|
+
const includeTerrain = options.includeTerrain ?? (mode === "standalone" || isCircuitStage);
|
|
3641
|
+
if (includeTerrain && mode !== "asset-overlay") {
|
|
3642
|
+
const terrainPaddingScale = options.terrainPaddingScale ?? (isCircuitStage ? 4.5 : 3.2);
|
|
3643
|
+
const bounds = boundsForPresentationPoints(routePoints, roadWidth * terrainPaddingScale);
|
|
3642
3644
|
nodes.push(primitives.box({
|
|
3643
3645
|
name: "scene-bound racing terrain pad",
|
|
3644
3646
|
material: material.pbr({ color: options.terrainColor ?? (isCircuitStage ? "#1f342e" : "#263b35"), roughness: 0.92, metallic: 0 })
|
|
@@ -3649,7 +3651,7 @@ export function createGameRacingPresentationTrackNodes(options) {
|
|
|
3649
3651
|
if (isCircuitStage) {
|
|
3650
3652
|
nodes.push(primitives.box({
|
|
3651
3653
|
name: "scene-bound racing pit lane apron",
|
|
3652
|
-
material: material.pbr({ color: "#
|
|
3654
|
+
material: material.pbr({ color: options.pitLaneColor ?? "#121c1e", roughness: 0.86, metallic: 0 })
|
|
3653
3655
|
})
|
|
3654
3656
|
.position(roundGamePresentation(bounds.centerX - bounds.width * 0.12), 0.002, roundGamePresentation(bounds.centerZ + bounds.depth * 0.23))
|
|
3655
3657
|
.rotate(0, -0.16, 0)
|
|
@@ -3686,69 +3688,484 @@ export function createGameRacingPresentationTrackNodes(options) {
|
|
|
3686
3688
|
const y = segment.midY;
|
|
3687
3689
|
nodes.push(primitives.box({
|
|
3688
3690
|
name: `scene-bound racing road segment ${index + 1}`,
|
|
3689
|
-
material: material.pbr({ color: options.roadColor ?? "#343b3f", roughness: 0.
|
|
3691
|
+
material: material.pbr({ color: options.roadColor ?? (isCircuitStage ? "#11191d" : "#343b3f"), roughness: 0.82, metallic: 0 })
|
|
3690
3692
|
})
|
|
3691
3693
|
.position(segment.midX, y, segment.midZ)
|
|
3692
3694
|
.rotate(0, -segment.angle, 0)
|
|
3693
|
-
.scale([segment.length + roadWidth * 0.
|
|
3694
|
-
.toJSON()
|
|
3695
|
-
|
|
3695
|
+
.scale([segment.length + roadWidth * 0.44, 0.028, roadWidth])
|
|
3696
|
+
.toJSON());
|
|
3697
|
+
if (markingVisibility === "none")
|
|
3698
|
+
continue;
|
|
3699
|
+
const isSubtleMark = markingVisibility === "subtle";
|
|
3700
|
+
if (!isSubtleMark || index % 3 === 0) {
|
|
3701
|
+
nodes.push(primitives.box({
|
|
3702
|
+
name: `scene-bound racing center stripe ${index + 1}`,
|
|
3703
|
+
material: material.emissive({
|
|
3704
|
+
color: options.laneColor ?? "#d8f6ff",
|
|
3705
|
+
emissive: options.laneColor ?? "#8df4ff",
|
|
3706
|
+
emissiveIntensity: isSubtleMark ? 0.34 : 0.7,
|
|
3707
|
+
roughness: 0.4
|
|
3708
|
+
})
|
|
3709
|
+
})
|
|
3710
|
+
.position(segment.midX, y + 0.022, segment.midZ)
|
|
3711
|
+
.rotate(0, -segment.angle, 0)
|
|
3712
|
+
.scale([Math.max(0.08, segment.length * (isCircuitStage ? 0.28 : 0.72)), 0.009, 0.01])
|
|
3713
|
+
.toJSON());
|
|
3714
|
+
}
|
|
3715
|
+
if (!isSubtleMark || index % 4 === 0) {
|
|
3716
|
+
nodes.push(primitives.box({
|
|
3717
|
+
name: `scene-bound racing left curb ${index + 1}`,
|
|
3718
|
+
material: material.pbr({ color: index % 2 === 0 ? "#d4ded8" : options.curbColor ?? "#df3550", roughness: 0.66 })
|
|
3719
|
+
})
|
|
3720
|
+
.position(roundGamePresentation(segment.midX + sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ + sideZ * roadWidth * 0.54))
|
|
3721
|
+
.rotate(0, -segment.angle, 0)
|
|
3722
|
+
.scale([Math.max(0.08, segment.length * 0.38), 0.012, 0.016])
|
|
3723
|
+
.toJSON(), primitives.box({
|
|
3724
|
+
name: `scene-bound racing right curb ${index + 1}`,
|
|
3725
|
+
material: material.pbr({ color: index % 2 === 0 ? options.curbColor ?? "#df3550" : "#d4ded8", roughness: 0.66 })
|
|
3726
|
+
})
|
|
3727
|
+
.position(roundGamePresentation(segment.midX - sideX * roadWidth * 0.54), y + 0.024, roundGamePresentation(segment.midZ - sideZ * roadWidth * 0.54))
|
|
3728
|
+
.rotate(0, -segment.angle, 0)
|
|
3729
|
+
.scale([Math.max(0.08, segment.length * 0.38), 0.012, 0.016])
|
|
3730
|
+
.toJSON());
|
|
3731
|
+
}
|
|
3732
|
+
if (isCircuitStage && (!isSubtleMark || index % 2 === 0)) {
|
|
3733
|
+
const railLength = Math.max(0.1, segment.length * (isSubtleMark ? 0.5 : 0.64));
|
|
3734
|
+
nodes.push(primitives.box({
|
|
3735
|
+
name: `scene-bound racing left guardrail ${index + 1}`,
|
|
3736
|
+
material: material.pbr({ color: "#141d20", roughness: 0.74, metallic: 0 })
|
|
3737
|
+
})
|
|
3738
|
+
.position(roundGamePresentation(segment.midX + sideX * roadWidth * 0.62), y + 0.034, roundGamePresentation(segment.midZ + sideZ * roadWidth * 0.62))
|
|
3739
|
+
.rotate(0, -segment.angle, 0)
|
|
3740
|
+
.scale([railLength, 0.018, 0.014])
|
|
3741
|
+
.toJSON(), primitives.box({
|
|
3742
|
+
name: `scene-bound racing right guardrail ${index + 1}`,
|
|
3743
|
+
material: material.pbr({ color: "#141d20", roughness: 0.74, metallic: 0 })
|
|
3744
|
+
})
|
|
3745
|
+
.position(roundGamePresentation(segment.midX - sideX * roadWidth * 0.62), y + 0.034, roundGamePresentation(segment.midZ - sideZ * roadWidth * 0.62))
|
|
3746
|
+
.rotate(0, -segment.angle, 0)
|
|
3747
|
+
.scale([railLength, 0.018, 0.014])
|
|
3748
|
+
.toJSON());
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
return nodes;
|
|
3752
|
+
}
|
|
3753
|
+
export function createGameRacingCheckpointGateNodes(options) {
|
|
3754
|
+
const mode = options.mode ?? "scene-bound";
|
|
3755
|
+
const sample = racingPresentationSceneSample(options.sceneBinding, options.route, options.progress, options.roadY);
|
|
3756
|
+
if (!sample)
|
|
3757
|
+
return [];
|
|
3758
|
+
const roadWidth = options.roadWidth ?? racingPresentationRoadWidth(options.sceneBinding, options.route, "game-circuit");
|
|
3759
|
+
const label = options.index === undefined ? "racing checkpoint gate" : `racing checkpoint gate ${options.index + 1}`;
|
|
3760
|
+
const sideX = -Math.sin(sample.angle);
|
|
3761
|
+
const sideZ = Math.cos(sample.angle);
|
|
3762
|
+
const crossAngle = -sample.angle + Math.PI / 2;
|
|
3763
|
+
const prefix = mode === "asset-bound" ? "asset-bound" : "scene-bound";
|
|
3764
|
+
if (mode === "asset-bound") {
|
|
3765
|
+
return [
|
|
3766
|
+
primitives.box({
|
|
3767
|
+
name: `${prefix} ${label}`,
|
|
3768
|
+
material: material.emissive({ color: options.gateColor ?? "#f8f1bf", emissive: options.accentColor ?? "#f6e27a", emissiveIntensity: 0.38, roughness: 0.46 })
|
|
3769
|
+
})
|
|
3770
|
+
.position(sample.point[0], sample.point[1] + 0.045, sample.point[2])
|
|
3771
|
+
.rotate(0, crossAngle, 0)
|
|
3772
|
+
.scale([roadWidth * 0.9, 0.01, 0.026])
|
|
3773
|
+
.toJSON()
|
|
3774
|
+
];
|
|
3775
|
+
}
|
|
3776
|
+
const postOffset = roadWidth * 0.72;
|
|
3777
|
+
const bannerY = sample.point[1] + 0.096;
|
|
3778
|
+
const lightY = sample.point[1] + 0.072;
|
|
3779
|
+
const leftX = roundGamePresentation(sample.point[0] + sideX * postOffset);
|
|
3780
|
+
const leftZ = roundGamePresentation(sample.point[2] + sideZ * postOffset);
|
|
3781
|
+
const rightX = roundGamePresentation(sample.point[0] - sideX * postOffset);
|
|
3782
|
+
const rightZ = roundGamePresentation(sample.point[2] - sideZ * postOffset);
|
|
3783
|
+
const bannerX = roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.76);
|
|
3784
|
+
const bannerZ = roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.76);
|
|
3785
|
+
return [
|
|
3786
|
+
primitives.box({
|
|
3787
|
+
name: `${prefix} ${label} left post`,
|
|
3788
|
+
material: material.metal({ color: options.gateColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3789
|
+
})
|
|
3790
|
+
.position(leftX, sample.point[1] + 0.041, leftZ)
|
|
3791
|
+
.scale([0.028, 0.082, 0.028])
|
|
3792
|
+
.toJSON(),
|
|
3793
|
+
primitives.box({
|
|
3794
|
+
name: `${prefix} ${label} right post`,
|
|
3795
|
+
material: material.metal({ color: options.gateColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3796
|
+
})
|
|
3797
|
+
.position(rightX, sample.point[1] + 0.041, rightZ)
|
|
3798
|
+
.scale([0.028, 0.082, 0.028])
|
|
3799
|
+
.toJSON(),
|
|
3800
|
+
primitives.box({
|
|
3801
|
+
name: `${prefix} ${label} overhead banner`,
|
|
3696
3802
|
material: material.emissive({
|
|
3697
|
-
color: options.
|
|
3698
|
-
emissive: options.
|
|
3699
|
-
emissiveIntensity: 0.
|
|
3700
|
-
roughness: 0.
|
|
3803
|
+
color: options.accentColor ?? "#b66d33",
|
|
3804
|
+
emissive: options.accentColor ?? "#d88f4a",
|
|
3805
|
+
emissiveIntensity: 0.16,
|
|
3806
|
+
roughness: 0.42
|
|
3701
3807
|
})
|
|
3702
3808
|
})
|
|
3703
|
-
.position(
|
|
3704
|
-
.rotate(0,
|
|
3705
|
-
.scale([
|
|
3706
|
-
.toJSON(),
|
|
3707
|
-
|
|
3708
|
-
|
|
3809
|
+
.position(bannerX, bannerY, bannerZ)
|
|
3810
|
+
.rotate(0, crossAngle, 0)
|
|
3811
|
+
.scale([roadWidth * 0.26, 0.024, 0.024])
|
|
3812
|
+
.toJSON(),
|
|
3813
|
+
primitives.sphere({
|
|
3814
|
+
name: `${prefix} ${label} left signal light`,
|
|
3815
|
+
material: material.emissive({ color: options.lightColor ?? "#7ee8c4", emissive: options.lightColor ?? "#7ee8c4", emissiveIntensity: 0.64, roughness: 0.32 })
|
|
3709
3816
|
})
|
|
3710
|
-
.position(roundGamePresentation(
|
|
3711
|
-
.
|
|
3712
|
-
.
|
|
3713
|
-
|
|
3714
|
-
name:
|
|
3715
|
-
material: material.
|
|
3817
|
+
.position(roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.2), lightY, roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.2))
|
|
3818
|
+
.scale(0.018)
|
|
3819
|
+
.toJSON(),
|
|
3820
|
+
primitives.sphere({
|
|
3821
|
+
name: `${prefix} ${label} right signal light`,
|
|
3822
|
+
material: material.emissive({ color: options.lightColor ?? "#7ee8c4", emissive: options.lightColor ?? "#7ee8c4", emissiveIntensity: 0.64, roughness: 0.32 })
|
|
3716
3823
|
})
|
|
3717
|
-
.position(roundGamePresentation(
|
|
3718
|
-
.
|
|
3719
|
-
.
|
|
3720
|
-
|
|
3824
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.2), lightY, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.2))
|
|
3825
|
+
.scale(0.018)
|
|
3826
|
+
.toJSON(),
|
|
3827
|
+
primitives.cylinder({
|
|
3828
|
+
name: `${prefix} ${label} left cone marker`,
|
|
3829
|
+
material: material.emissive({ color: options.accentColor ?? "#c78344", emissive: options.accentColor ?? "#c78344", emissiveIntensity: 0.26, roughness: 0.52 })
|
|
3830
|
+
})
|
|
3831
|
+
.position(roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.78), sample.point[1] + 0.034, roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.78))
|
|
3832
|
+
.scale([0.038, 0.068, 0.038])
|
|
3833
|
+
.toJSON(),
|
|
3834
|
+
primitives.cylinder({
|
|
3835
|
+
name: `${prefix} ${label} right cone marker`,
|
|
3836
|
+
material: material.emissive({ color: options.accentColor ?? "#c78344", emissive: options.accentColor ?? "#c78344", emissiveIntensity: 0.26, roughness: 0.52 })
|
|
3837
|
+
})
|
|
3838
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.78), sample.point[1] + 0.034, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.78))
|
|
3839
|
+
.scale([0.038, 0.068, 0.038])
|
|
3840
|
+
.toJSON()
|
|
3841
|
+
];
|
|
3842
|
+
}
|
|
3843
|
+
export function createGameRacingStartFinishNodes(options) {
|
|
3844
|
+
const mode = options.mode ?? "scene-bound";
|
|
3845
|
+
const sample = racingPresentationSceneSample(options.sceneBinding, options.route, 0, options.roadY);
|
|
3846
|
+
if (!sample)
|
|
3847
|
+
return [];
|
|
3848
|
+
const roadWidth = options.roadWidth ?? racingPresentationRoadWidth(options.sceneBinding, options.route, "game-circuit");
|
|
3849
|
+
const crossAngle = -sample.angle + Math.PI / 2;
|
|
3850
|
+
const prefix = mode === "asset-bound" ? "asset-bound" : "scene-bound";
|
|
3851
|
+
if (mode === "asset-bound") {
|
|
3852
|
+
return [
|
|
3853
|
+
primitives.box({
|
|
3854
|
+
name: `${prefix} racing start finish band`,
|
|
3855
|
+
material: material.emissive({ color: "#ffffff", emissive: "#ffffff", emissiveIntensity: 0.36, roughness: 0.3 })
|
|
3856
|
+
})
|
|
3857
|
+
.position(sample.point[0], sample.point[1] + 0.052, sample.point[2])
|
|
3858
|
+
.rotate(0, crossAngle, 0)
|
|
3859
|
+
.scale([roadWidth * 1.05, 0.01, 0.035])
|
|
3860
|
+
.toJSON()
|
|
3861
|
+
];
|
|
3721
3862
|
}
|
|
3722
|
-
|
|
3863
|
+
const nodes = [];
|
|
3864
|
+
const sideX = -Math.sin(sample.angle);
|
|
3865
|
+
const sideZ = Math.cos(sample.angle);
|
|
3866
|
+
const forwardX = Math.cos(sample.angle);
|
|
3867
|
+
const forwardZ = Math.sin(sample.angle);
|
|
3868
|
+
nodes.push(primitives.box({
|
|
3869
|
+
name: "scene-bound racing start grid vehicle pad",
|
|
3870
|
+
material: material.pbr({ color: options.checkerColorB ?? "#0d1416", roughness: 0.78, metallic: 0 })
|
|
3871
|
+
})
|
|
3872
|
+
.position(roundGamePresentation(sample.point[0] - forwardX * roadWidth * 0.1), sample.point[1] + 0.028, roundGamePresentation(sample.point[2] - forwardZ * roadWidth * 0.1))
|
|
3873
|
+
.rotate(0, crossAngle, 0)
|
|
3874
|
+
.scale([roadWidth * 1.08, 0.01, roadWidth * 0.78])
|
|
3875
|
+
.toJSON(), primitives.box({
|
|
3876
|
+
name: "scene-bound racing start finish light strip",
|
|
3877
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.48, roughness: 0.38 })
|
|
3878
|
+
})
|
|
3879
|
+
.position(sample.point[0], sample.point[1] + 0.047, sample.point[2])
|
|
3880
|
+
.rotate(0, crossAngle, 0)
|
|
3881
|
+
.scale([roadWidth * 0.86, 0.012, 0.028])
|
|
3882
|
+
.toJSON());
|
|
3883
|
+
const tileCount = 10;
|
|
3884
|
+
for (let tile = 0; tile < tileCount; tile += 1) {
|
|
3885
|
+
const laneOffset = ((tile + 0.5) / tileCount - 0.5) * roadWidth * 1.18;
|
|
3723
3886
|
nodes.push(primitives.box({
|
|
3724
|
-
name:
|
|
3725
|
-
material: material.
|
|
3726
|
-
color: "#f8f1bf",
|
|
3727
|
-
emissive: "#f6e27a",
|
|
3728
|
-
emissiveIntensity: mode === "asset-overlay" ? 0.38 : 0.56,
|
|
3729
|
-
roughness: 0.46
|
|
3730
|
-
})
|
|
3887
|
+
name: `scene-bound racing start finish checker ${tile + 1}`,
|
|
3888
|
+
material: material.pbr({ color: tile % 2 === 0 ? options.checkerColorA ?? "#d4ded8" : options.checkerColorB ?? "#0d1416", roughness: 0.52, metallic: 0 })
|
|
3731
3889
|
})
|
|
3732
|
-
.position(point[0], point[1] +
|
|
3733
|
-
.
|
|
3890
|
+
.position(roundGamePresentation(sample.point[0] + sideX * laneOffset), sample.point[1] + 0.04, roundGamePresentation(sample.point[2] + sideZ * laneOffset))
|
|
3891
|
+
.rotate(0, crossAngle, 0)
|
|
3892
|
+
.scale([roadWidth / tileCount, 0.014, 0.07])
|
|
3734
3893
|
.toJSON());
|
|
3894
|
+
}
|
|
3895
|
+
const postOffset = roadWidth * 0.68;
|
|
3896
|
+
const leftX = roundGamePresentation(sample.point[0] + sideX * postOffset);
|
|
3897
|
+
const leftZ = roundGamePresentation(sample.point[2] + sideZ * postOffset);
|
|
3898
|
+
const rightX = roundGamePresentation(sample.point[0] - sideX * postOffset);
|
|
3899
|
+
const rightZ = roundGamePresentation(sample.point[2] - sideZ * postOffset);
|
|
3900
|
+
const bannerX = roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.84);
|
|
3901
|
+
const bannerZ = roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.84);
|
|
3902
|
+
nodes.push(primitives.box({
|
|
3903
|
+
name: "scene-bound racing launch grid bright marker",
|
|
3904
|
+
material: material.emissive({ color: options.checkerColorA ?? "#718980", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.16, roughness: 0.44 })
|
|
3905
|
+
})
|
|
3906
|
+
.position(roundGamePresentation(sample.point[0] - forwardX * roadWidth * 0.28), sample.point[1] + 0.038, roundGamePresentation(sample.point[2] - forwardZ * roadWidth * 0.28))
|
|
3907
|
+
.rotate(0, crossAngle, 0)
|
|
3908
|
+
.scale([roadWidth * 0.44, 0.012, 0.05])
|
|
3909
|
+
.toJSON(), primitives.box({
|
|
3910
|
+
name: "scene-bound racing launch grid dark marker",
|
|
3911
|
+
material: material.pbr({ color: options.checkerColorB ?? "#0d1416", roughness: 0.52, metallic: 0 })
|
|
3912
|
+
})
|
|
3913
|
+
.position(roundGamePresentation(sample.point[0] - forwardX * roadWidth * 0.42), sample.point[1] + 0.039, roundGamePresentation(sample.point[2] - forwardZ * roadWidth * 0.42))
|
|
3914
|
+
.rotate(0, crossAngle, 0)
|
|
3915
|
+
.scale([roadWidth * 0.44, 0.012, 0.05])
|
|
3916
|
+
.toJSON(), primitives.sphere({
|
|
3917
|
+
name: "scene-bound racing start left lane signal",
|
|
3918
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.72, roughness: 0.32 })
|
|
3919
|
+
})
|
|
3920
|
+
.position(roundGamePresentation(sample.point[0] + sideX * roadWidth * 0.34 - forwardX * roadWidth * 0.2), sample.point[1] + 0.06, roundGamePresentation(sample.point[2] + sideZ * roadWidth * 0.34 - forwardZ * roadWidth * 0.2))
|
|
3921
|
+
.scale(0.036)
|
|
3922
|
+
.toJSON(), primitives.sphere({
|
|
3923
|
+
name: "scene-bound racing start right lane signal",
|
|
3924
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.72, roughness: 0.32 })
|
|
3925
|
+
})
|
|
3926
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.34 - forwardX * roadWidth * 0.2), sample.point[1] + 0.06, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.34 - forwardZ * roadWidth * 0.2))
|
|
3927
|
+
.scale(0.036)
|
|
3928
|
+
.toJSON(), primitives.box({
|
|
3929
|
+
name: "scene-bound racing start gantry left post",
|
|
3930
|
+
material: material.metal({ color: options.gantryColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3931
|
+
})
|
|
3932
|
+
.position(leftX, sample.point[1] + 0.064, leftZ)
|
|
3933
|
+
.scale([0.032, 0.128, 0.032])
|
|
3934
|
+
.toJSON(), primitives.box({
|
|
3935
|
+
name: "scene-bound racing start gantry right post",
|
|
3936
|
+
material: material.metal({ color: options.gantryColor ?? "#26373a", roughness: 0.5, metallic: 0.28 })
|
|
3937
|
+
})
|
|
3938
|
+
.position(rightX, sample.point[1] + 0.064, rightZ)
|
|
3939
|
+
.scale([0.032, 0.128, 0.032])
|
|
3940
|
+
.toJSON(), primitives.box({
|
|
3941
|
+
name: "scene-bound racing start finish gantry banner",
|
|
3942
|
+
material: material.emissive({ color: options.gantryColor ?? "#2c4243", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.12, roughness: 0.42 })
|
|
3943
|
+
})
|
|
3944
|
+
.position(bannerX, sample.point[1] + 0.138, bannerZ)
|
|
3945
|
+
.rotate(0, crossAngle, 0)
|
|
3946
|
+
.scale([roadWidth * 0.34, 0.034, 0.034])
|
|
3947
|
+
.toJSON(), primitives.sphere({
|
|
3948
|
+
name: "scene-bound racing start green light",
|
|
3949
|
+
material: material.emissive({ color: options.lightColor ?? "#86e391", emissive: options.lightColor ?? "#86e391", emissiveIntensity: 0.72, roughness: 0.32 })
|
|
3950
|
+
})
|
|
3951
|
+
.position(roundGamePresentation(sample.point[0] - sideX * roadWidth * 0.84), sample.point[1] + 0.156, roundGamePresentation(sample.point[2] - sideZ * roadWidth * 0.84))
|
|
3952
|
+
.scale(0.023)
|
|
3953
|
+
.toJSON());
|
|
3954
|
+
return nodes;
|
|
3955
|
+
}
|
|
3956
|
+
export function createGamePublicRacingPresentationNodes(options) {
|
|
3957
|
+
const mode = "game-circuit";
|
|
3958
|
+
const roadWidth = racingPresentationRoadWidth(options.sceneBinding, options.route, mode);
|
|
3959
|
+
const nodes = [
|
|
3960
|
+
...createGameRacingRoadMeshNodes({ ...options, mode, includeTerrain: options.includeTerrain ?? true })
|
|
3961
|
+
];
|
|
3962
|
+
for (const [index, progress] of (options.route.checkpoints ?? []).entries()) {
|
|
3963
|
+
nodes.push(...createGameRacingCheckpointGateNodes({
|
|
3964
|
+
sceneBinding: options.sceneBinding,
|
|
3965
|
+
route: options.route,
|
|
3966
|
+
progress,
|
|
3967
|
+
index,
|
|
3968
|
+
roadY: options.roadY,
|
|
3969
|
+
roadWidth,
|
|
3970
|
+
gateColor: options.checkpointColor,
|
|
3971
|
+
accentColor: options.checkpointAccentColor,
|
|
3972
|
+
lightColor: options.startLightColor
|
|
3973
|
+
}));
|
|
3974
|
+
}
|
|
3975
|
+
nodes.push(...createGameRacingStartFinishNodes({
|
|
3976
|
+
sceneBinding: options.sceneBinding,
|
|
3977
|
+
route: options.route,
|
|
3978
|
+
roadY: options.roadY,
|
|
3979
|
+
roadWidth,
|
|
3980
|
+
lightColor: options.startLightColor
|
|
3981
|
+
}));
|
|
3982
|
+
return nodes;
|
|
3983
|
+
}
|
|
3984
|
+
export function createGameRacingCameraRig(options) {
|
|
3985
|
+
return createGameRacingPresentationCamera(options);
|
|
3986
|
+
}
|
|
3987
|
+
export function certifyPublicRacingPresentation(input) {
|
|
3988
|
+
const base = certifyPublicRacingGeometry(input);
|
|
3989
|
+
const blockers = [...base.blockers];
|
|
3990
|
+
const presentation = input.presentation;
|
|
3991
|
+
if (!presentation) {
|
|
3992
|
+
blockers.push("racing:presentation-missing");
|
|
3993
|
+
}
|
|
3994
|
+
else {
|
|
3995
|
+
if ((presentation.roadMeshNodes ?? 0) < 8)
|
|
3996
|
+
blockers.push(`racing:presentation-road-mesh-too-small:${presentation.roadMeshNodes ?? 0}`);
|
|
3997
|
+
if ((presentation.checkpointGateNodes ?? 0) < input.checkpoints.length) {
|
|
3998
|
+
blockers.push(`racing:presentation-checkpoint-gates-too-few:${presentation.checkpointGateNodes ?? 0}`);
|
|
3999
|
+
}
|
|
4000
|
+
if ((presentation.startFinishNodes ?? 0) < 4)
|
|
4001
|
+
blockers.push(`racing:presentation-start-finish-too-small:${presentation.startFinishNodes ?? 0}`);
|
|
4002
|
+
if (presentation.cameraMode !== "follow" && presentation.cameraMode !== "perspective") {
|
|
4003
|
+
blockers.push(`racing:presentation-camera-not-racing:${presentation.cameraMode ?? "missing"}`);
|
|
4004
|
+
}
|
|
4005
|
+
if ((presentation.debugMarkerCount ?? 0) > 0)
|
|
4006
|
+
blockers.push(`racing:presentation-debug-markers:${presentation.debugMarkerCount}`);
|
|
4007
|
+
}
|
|
4008
|
+
return {
|
|
4009
|
+
...base,
|
|
4010
|
+
publicReady: blockers.length === 0,
|
|
4011
|
+
blockers
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
export function createGameRacingPresentationTrackNodes(options) {
|
|
4015
|
+
const mode = options.mode ?? "standalone";
|
|
4016
|
+
const guideVisibility = options.guideVisibility ?? (mode === "asset-overlay" ? "evidence" : "full");
|
|
4017
|
+
if (mode === "asset-overlay" && guideVisibility === "public")
|
|
4018
|
+
return [];
|
|
4019
|
+
const roadWidth = racingPresentationRoadWidth(options.sceneBinding, options.route, mode);
|
|
4020
|
+
const nodes = [...createGameRacingRoadMeshNodes(options)];
|
|
4021
|
+
if (mode === "asset-overlay") {
|
|
4022
|
+
for (const [index, progress] of (options.route.checkpoints ?? []).entries()) {
|
|
4023
|
+
nodes.push(...createGameRacingCheckpointGateNodes({
|
|
4024
|
+
sceneBinding: options.sceneBinding,
|
|
4025
|
+
route: options.route,
|
|
4026
|
+
progress,
|
|
4027
|
+
index,
|
|
4028
|
+
mode: "asset-bound",
|
|
4029
|
+
roadY: options.roadY,
|
|
4030
|
+
roadWidth,
|
|
4031
|
+
gateColor: options.laneColor,
|
|
4032
|
+
accentColor: options.laneColor
|
|
4033
|
+
}));
|
|
4034
|
+
}
|
|
4035
|
+
nodes.push(...createGameRacingStartFinishNodes({
|
|
4036
|
+
sceneBinding: options.sceneBinding,
|
|
4037
|
+
route: options.route,
|
|
4038
|
+
mode: "asset-bound",
|
|
4039
|
+
roadY: options.roadY,
|
|
4040
|
+
roadWidth
|
|
4041
|
+
}));
|
|
4042
|
+
return nodes;
|
|
4043
|
+
}
|
|
4044
|
+
if (guideVisibility === "evidence" || guideVisibility === "full") {
|
|
4045
|
+
for (const [index, progress] of (options.route.checkpoints ?? []).entries()) {
|
|
4046
|
+
nodes.push(...createGameRacingCheckpointGateNodes({
|
|
4047
|
+
sceneBinding: options.sceneBinding,
|
|
4048
|
+
route: options.route,
|
|
4049
|
+
progress,
|
|
4050
|
+
index,
|
|
4051
|
+
roadY: options.roadY,
|
|
4052
|
+
roadWidth,
|
|
4053
|
+
gateColor: options.laneColor,
|
|
4054
|
+
accentColor: options.laneColor
|
|
4055
|
+
}));
|
|
4056
|
+
}
|
|
4057
|
+
nodes.push(...createGameRacingStartFinishNodes({
|
|
4058
|
+
sceneBinding: options.sceneBinding,
|
|
4059
|
+
route: options.route,
|
|
4060
|
+
roadY: options.roadY,
|
|
4061
|
+
roadWidth,
|
|
4062
|
+
lightColor: options.laneColor
|
|
4063
|
+
}));
|
|
4064
|
+
}
|
|
4065
|
+
return nodes;
|
|
4066
|
+
}
|
|
4067
|
+
export function createGamePublicPlatformerPresentationNodes(options) {
|
|
4068
|
+
return createGamePlatformerPresentationSurfaceNodes({
|
|
4069
|
+
...options,
|
|
4070
|
+
mode: "game-level",
|
|
4071
|
+
guideVisibility: "public"
|
|
3735
4072
|
});
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
4073
|
+
}
|
|
4074
|
+
export function createGamePlatformerGroundMeshNodes(options) {
|
|
4075
|
+
return createSinglePlatformerSurfaceMeshNodes("ground", options);
|
|
4076
|
+
}
|
|
4077
|
+
export function createGamePlatformerPlatformMeshNodes(options) {
|
|
4078
|
+
return createSinglePlatformerSurfaceMeshNodes("platform", options);
|
|
4079
|
+
}
|
|
4080
|
+
export function createGamePlatformerHazardNodes(options) {
|
|
4081
|
+
const mode = options.mode ?? "game-level";
|
|
4082
|
+
const isGameLevel = mode === "game-level";
|
|
4083
|
+
const rect = options.sceneBinding.surfaceToSceneRect(options.hazard);
|
|
4084
|
+
return [
|
|
4085
|
+
primitives.box({
|
|
4086
|
+
name: `scene-bound platformer hazard ${options.hazard.id}`,
|
|
3740
4087
|
material: material.emissive({
|
|
3741
|
-
color: "#
|
|
3742
|
-
emissive: "#
|
|
3743
|
-
emissiveIntensity:
|
|
3744
|
-
roughness: 0.
|
|
4088
|
+
color: options.color ?? "#ff6b6b",
|
|
4089
|
+
emissive: options.color ?? "#ff6b6b",
|
|
4090
|
+
emissiveIntensity: isGameLevel ? 0.7 : 0.54,
|
|
4091
|
+
roughness: 0.36
|
|
3745
4092
|
})
|
|
3746
4093
|
})
|
|
3747
|
-
.position(
|
|
3748
|
-
.scale([
|
|
3749
|
-
.toJSON()
|
|
4094
|
+
.position(rect.center[0], rect.center[1], rect.center[2] + (isGameLevel ? 0.39 : 0.31))
|
|
4095
|
+
.scale([Math.max(rect.size[0], 0.045), Math.max(rect.size[1], 0.045), 0.055])
|
|
4096
|
+
.toJSON()
|
|
4097
|
+
];
|
|
4098
|
+
}
|
|
4099
|
+
export function createGamePlatformerCheckpointNodes(options) {
|
|
4100
|
+
const point = options.sceneBinding.toScenePoint(options.checkpoint, 0.02);
|
|
4101
|
+
return [
|
|
4102
|
+
primitives.box({
|
|
4103
|
+
name: `scene-bound platformer checkpoint ${options.checkpoint.id}`,
|
|
4104
|
+
material: material.emissive({
|
|
4105
|
+
color: options.color ?? "#d7f9ff",
|
|
4106
|
+
emissive: options.color ?? "#a6f4ff",
|
|
4107
|
+
emissiveIntensity: 0.48,
|
|
4108
|
+
roughness: 0.4
|
|
4109
|
+
})
|
|
4110
|
+
})
|
|
4111
|
+
.position(point[0], point[1], point[2] + 0.29)
|
|
4112
|
+
.scale([0.026, 0.22, 0.034])
|
|
4113
|
+
.toJSON()
|
|
4114
|
+
];
|
|
4115
|
+
}
|
|
4116
|
+
export function createGamePlatformerFinishNodes(options) {
|
|
4117
|
+
const point = options.sceneBinding.toScenePoint(options.finish, 0.04);
|
|
4118
|
+
return [
|
|
4119
|
+
primitives.box({
|
|
4120
|
+
name: `scene-bound platformer finish marker${options.finish.id ? ` ${options.finish.id}` : ""}`,
|
|
4121
|
+
material: material.emissive({
|
|
4122
|
+
color: options.color ?? "#b6ffbd",
|
|
4123
|
+
emissive: options.color ?? "#83f58f",
|
|
4124
|
+
emissiveIntensity: 0.6,
|
|
4125
|
+
roughness: 0.42
|
|
4126
|
+
})
|
|
4127
|
+
})
|
|
4128
|
+
.position(point[0], point[1], point[2] + 0.34)
|
|
4129
|
+
.scale([0.06, 0.34, 0.05])
|
|
4130
|
+
.toJSON()
|
|
4131
|
+
];
|
|
4132
|
+
}
|
|
4133
|
+
export function createGamePlatformerCameraRig(options) {
|
|
4134
|
+
return createGamePlatformerPresentationCamera(options);
|
|
4135
|
+
}
|
|
4136
|
+
export function certifyPublicPlatformerPresentation(input) {
|
|
4137
|
+
const base = certifyPublicPlatformerGeometry(input);
|
|
4138
|
+
const blockers = [...base.blockers];
|
|
4139
|
+
const presentation = input.presentation;
|
|
4140
|
+
if (!presentation) {
|
|
4141
|
+
blockers.push("platformer:presentation-missing");
|
|
3750
4142
|
}
|
|
3751
|
-
|
|
4143
|
+
else {
|
|
4144
|
+
if ((presentation.groundMeshNodes ?? 0) < 2)
|
|
4145
|
+
blockers.push(`platformer:presentation-ground-mesh-too-small:${presentation.groundMeshNodes ?? 0}`);
|
|
4146
|
+
if ((presentation.platformMeshNodes ?? 0) < 6)
|
|
4147
|
+
blockers.push(`platformer:presentation-platform-mesh-too-small:${presentation.platformMeshNodes ?? 0}`);
|
|
4148
|
+
if ((presentation.hazardNodes ?? 0) < input.hazards.length) {
|
|
4149
|
+
blockers.push(`platformer:presentation-hazards-too-few:${presentation.hazardNodes ?? 0}`);
|
|
4150
|
+
}
|
|
4151
|
+
if ((presentation.checkpointNodes ?? 0) < input.checkpoints.length) {
|
|
4152
|
+
blockers.push(`platformer:presentation-checkpoints-too-few:${presentation.checkpointNodes ?? 0}`);
|
|
4153
|
+
}
|
|
4154
|
+
if ((presentation.finishNodes ?? 0) < 1)
|
|
4155
|
+
blockers.push(`platformer:presentation-finish-missing:${presentation.finishNodes ?? 0}`);
|
|
4156
|
+
if (presentation.cameraMode !== "follow" && presentation.cameraMode !== "perspective") {
|
|
4157
|
+
blockers.push(`platformer:presentation-camera-not-side-scroller:${presentation.cameraMode ?? "missing"}`);
|
|
4158
|
+
}
|
|
4159
|
+
if ((presentation.debugMarkerCount ?? 0) > 0)
|
|
4160
|
+
blockers.push(`platformer:presentation-debug-markers:${presentation.debugMarkerCount}`);
|
|
4161
|
+
if (presentation.characterGrounded !== true)
|
|
4162
|
+
blockers.push("platformer:presentation-character-not-grounded");
|
|
4163
|
+
}
|
|
4164
|
+
return {
|
|
4165
|
+
...base,
|
|
4166
|
+
publicReady: blockers.length === 0,
|
|
4167
|
+
blockers
|
|
4168
|
+
};
|
|
3752
4169
|
}
|
|
3753
4170
|
export function createGamePlatformerPresentationSurfaceNodes(options) {
|
|
3754
4171
|
const mode = options.mode ?? "standalone";
|
|
@@ -3758,7 +4175,7 @@ export function createGamePlatformerPresentationSurfaceNodes(options) {
|
|
|
3758
4175
|
return [];
|
|
3759
4176
|
const isGameLevel = mode === "game-level";
|
|
3760
4177
|
const nodes = [];
|
|
3761
|
-
if ((mode === "standalone" || mode === "game-level") && platforms.length > 0) {
|
|
4178
|
+
if ((mode === "standalone" || mode === "game-level") && platforms.length > 0 && options.includeBackdrop !== false) {
|
|
3762
4179
|
const rects = platforms.map((surface) => options.sceneBinding.surfaceToSceneRect(surface));
|
|
3763
4180
|
const bounds = boundsForPresentationRects(rects, isGameLevel ? 0.78 : 0.42);
|
|
3764
4181
|
nodes.push(primitives.box({
|
|
@@ -3865,6 +4282,54 @@ export function createGamePlatformerPresentationSurfaceNodes(options) {
|
|
|
3865
4282
|
}
|
|
3866
4283
|
return nodes;
|
|
3867
4284
|
}
|
|
4285
|
+
function createSinglePlatformerSurfaceMeshNodes(surfaceRole, options) {
|
|
4286
|
+
const mode = options.mode ?? "game-level";
|
|
4287
|
+
const isGameLevel = mode === "game-level";
|
|
4288
|
+
const rect = options.sceneBinding.surfaceToSceneRect(options.surface);
|
|
4289
|
+
const width = Math.max(rect.size[0], 0.08);
|
|
4290
|
+
const height = Math.max(rect.size[1], 0.055);
|
|
4291
|
+
if (mode === "asset-bound") {
|
|
4292
|
+
return [
|
|
4293
|
+
primitives.box({
|
|
4294
|
+
name: `asset-bound platformer ${surfaceRole} contact strip ${options.surface.id}`,
|
|
4295
|
+
material: material.emissive({
|
|
4296
|
+
color: options.trimColor ?? "#c9f7ff",
|
|
4297
|
+
emissive: options.trimColor ?? "#a8f4ff",
|
|
4298
|
+
emissiveIntensity: 0.28,
|
|
4299
|
+
roughness: 0.44
|
|
4300
|
+
})
|
|
4301
|
+
})
|
|
4302
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.026), rect.center[2] + 0.24)
|
|
4303
|
+
.scale([width * 0.78, 0.012, 0.024])
|
|
4304
|
+
.toJSON()
|
|
4305
|
+
];
|
|
4306
|
+
}
|
|
4307
|
+
return [
|
|
4308
|
+
primitives.box({
|
|
4309
|
+
name: `scene-bound platformer ${surfaceRole} mesh ${options.surface.id}`,
|
|
4310
|
+
material: material.pbr({
|
|
4311
|
+
color: options.color ?? (isGameLevel ? "#526972" : "#718994"),
|
|
4312
|
+
roughness: 0.72,
|
|
4313
|
+
metallic: 0
|
|
4314
|
+
})
|
|
4315
|
+
})
|
|
4316
|
+
.position(rect.center[0], rect.center[1], rect.center[2])
|
|
4317
|
+
.scale([width, height, isGameLevel ? 0.64 : 0.5])
|
|
4318
|
+
.toJSON(),
|
|
4319
|
+
primitives.box({
|
|
4320
|
+
name: `scene-bound platformer ${surfaceRole} trim ${options.surface.id}`,
|
|
4321
|
+
material: material.emissive({
|
|
4322
|
+
color: options.trimColor ?? "#c9f7ff",
|
|
4323
|
+
emissive: options.trimColor ?? "#a8f4ff",
|
|
4324
|
+
emissiveIntensity: 0.36,
|
|
4325
|
+
roughness: 0.38
|
|
4326
|
+
})
|
|
4327
|
+
})
|
|
4328
|
+
.position(rect.center[0], roundGamePresentation(rect.center[1] + height / 2 + 0.012), rect.center[2] + (isGameLevel ? 0.334 : 0.262))
|
|
4329
|
+
.scale([width * 0.92, 0.016, 0.035])
|
|
4330
|
+
.toJSON()
|
|
4331
|
+
];
|
|
4332
|
+
}
|
|
3868
4333
|
function boundsForPresentationPoints(points, padding) {
|
|
3869
4334
|
const xs = points.map((point) => point[0]);
|
|
3870
4335
|
const zs = points.map((point) => point[2]);
|
|
@@ -3904,6 +4369,77 @@ function segmentPresentation(start, end) {
|
|
|
3904
4369
|
angle: Math.atan2(dz, dx)
|
|
3905
4370
|
};
|
|
3906
4371
|
}
|
|
4372
|
+
function racingPresentationRoutePoints(sceneBinding, route, roadY) {
|
|
4373
|
+
return route.points.map((point) => sceneBinding.toScenePoint(point, roadY ?? 0.012));
|
|
4374
|
+
}
|
|
4375
|
+
function racingPresentationRoadWidth(sceneBinding, route, mode) {
|
|
4376
|
+
const isCircuitStage = mode === "game-circuit";
|
|
4377
|
+
return Math.max((route.width ?? 0.18) * sceneBinding.transform.scale * (isCircuitStage ? 1.55 : 1.5), isCircuitStage ? 0.34 : 0.18);
|
|
4378
|
+
}
|
|
4379
|
+
function racingPresentationSceneSample(sceneBinding, route, progress, roadY) {
|
|
4380
|
+
const sample = racingPresentationRouteSample(route, progress);
|
|
4381
|
+
if (!sample)
|
|
4382
|
+
return undefined;
|
|
4383
|
+
return {
|
|
4384
|
+
point: sceneBinding.toScenePoint(sample.point, roadY ?? 0.012),
|
|
4385
|
+
angle: sample.angle
|
|
4386
|
+
};
|
|
4387
|
+
}
|
|
4388
|
+
function racingPresentationRouteSample(route, progress) {
|
|
4389
|
+
const points = route.points;
|
|
4390
|
+
if (points.length < 2)
|
|
4391
|
+
return undefined;
|
|
4392
|
+
const clamped = Number.isFinite(progress) ? Math.min(1, Math.max(0, progress)) : 0;
|
|
4393
|
+
let totalLength = 0;
|
|
4394
|
+
const segmentLengths = [];
|
|
4395
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
4396
|
+
const start = points[index];
|
|
4397
|
+
const end = points[index + 1];
|
|
4398
|
+
if (!start || !end)
|
|
4399
|
+
continue;
|
|
4400
|
+
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
4401
|
+
segmentLengths[index] = length;
|
|
4402
|
+
totalLength += length;
|
|
4403
|
+
}
|
|
4404
|
+
if (totalLength <= 0) {
|
|
4405
|
+
const start = points[0];
|
|
4406
|
+
const end = points[1] ?? start;
|
|
4407
|
+
if (!start || !end)
|
|
4408
|
+
return undefined;
|
|
4409
|
+
return {
|
|
4410
|
+
point: start,
|
|
4411
|
+
angle: Math.atan2(end.y - start.y, end.x - start.x)
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4414
|
+
const targetLength = clamped * totalLength;
|
|
4415
|
+
let traversed = 0;
|
|
4416
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
4417
|
+
const start = points[index];
|
|
4418
|
+
const end = points[index + 1];
|
|
4419
|
+
const length = segmentLengths[index] ?? 0;
|
|
4420
|
+
if (!start || !end || length <= 0)
|
|
4421
|
+
continue;
|
|
4422
|
+
if (targetLength <= traversed + length || index === points.length - 2) {
|
|
4423
|
+
const localT = Math.min(1, Math.max(0, (targetLength - traversed) / length));
|
|
4424
|
+
return {
|
|
4425
|
+
point: {
|
|
4426
|
+
x: start.x + (end.x - start.x) * localT,
|
|
4427
|
+
y: start.y + (end.y - start.y) * localT
|
|
4428
|
+
},
|
|
4429
|
+
angle: Math.atan2(end.y - start.y, end.x - start.x)
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
traversed += length;
|
|
4433
|
+
}
|
|
4434
|
+
const last = points[points.length - 1];
|
|
4435
|
+
const previous = points[points.length - 2] ?? last;
|
|
4436
|
+
if (!last || !previous)
|
|
4437
|
+
return undefined;
|
|
4438
|
+
return {
|
|
4439
|
+
point: last,
|
|
4440
|
+
angle: Math.atan2(last.y - previous.y, last.x - previous.x)
|
|
4441
|
+
};
|
|
4442
|
+
}
|
|
3907
4443
|
function roundGamePresentation(value) {
|
|
3908
4444
|
return Math.round(value * 1000) / 1000;
|
|
3909
4445
|
}
|
|
@@ -3932,11 +4468,27 @@ export const game = {
|
|
|
3932
4468
|
platformerSceneBinding: createGamePlatformerSceneBinding,
|
|
3933
4469
|
platformerPresentationCamera: createGamePlatformerPresentationCamera,
|
|
3934
4470
|
platformerPresentationSurfaces: createGamePlatformerPresentationSurfaceNodes,
|
|
4471
|
+
publicPlatformerPresentation: createGamePublicPlatformerPresentationNodes,
|
|
4472
|
+
platformerGroundMesh: createGamePlatformerGroundMeshNodes,
|
|
4473
|
+
platformerPlatformMesh: createGamePlatformerPlatformMeshNodes,
|
|
4474
|
+
platformerHazard: createGamePlatformerHazardNodes,
|
|
4475
|
+
platformerCheckpoint: createGamePlatformerCheckpointNodes,
|
|
4476
|
+
platformerFinish: createGamePlatformerFinishNodes,
|
|
4477
|
+
platformerCameraRig: createGamePlatformerCameraRig,
|
|
4478
|
+
certifyPlatformerGeometry: certifyPublicPlatformerGeometry,
|
|
4479
|
+
certifyPlatformerPresentation: certifyPublicPlatformerPresentation,
|
|
3935
4480
|
platformer: createGamePlatformerKit,
|
|
3936
4481
|
assetBoundRacingRoute: createGameAssetBoundRacingRoute,
|
|
3937
4482
|
racingSceneBinding: createGameRacingSceneBinding,
|
|
3938
4483
|
racingPresentationCamera: createGameRacingPresentationCamera,
|
|
4484
|
+
racingCameraRig: createGameRacingCameraRig,
|
|
4485
|
+
racingRoadMesh: createGameRacingRoadMeshNodes,
|
|
4486
|
+
racingCheckpointGate: createGameRacingCheckpointGateNodes,
|
|
4487
|
+
racingStartFinish: createGameRacingStartFinishNodes,
|
|
3939
4488
|
racingPresentationTrack: createGameRacingPresentationTrackNodes,
|
|
4489
|
+
publicRacingPresentation: createGamePublicRacingPresentationNodes,
|
|
4490
|
+
certifyRacingGeometry: certifyPublicRacingGeometry,
|
|
4491
|
+
certifyRacingPresentation: certifyPublicRacingPresentation,
|
|
3940
4492
|
racing: createGameRacingKit,
|
|
3941
4493
|
fallingBlocks: createGameFallingBlocksKit,
|
|
3942
4494
|
fallingBlockPieces: GAME_FALLING_BLOCK_PIECES,
|