auto-git:

[change] src/app/App.tsx
 [change] src/assets/starter-environment-assets.ts
 [change] src/document/migrate-scene-document.ts
 [change] src/document/scene-document-validation.ts
 [change] src/document/scene-document.ts
 [change] src/document/world-settings.ts
 [change] src/rendering/world-background-renderer.ts
 [change] src/rendering/world-shader-sky.ts
 [change] src/runtime-three/runtime-host.ts
 [change] src/runtime-three/runtime-project-time.ts
 [change] src/shared-ui/world-background-style.ts
 [change] src/viewport-three/ViewportCanvas.tsx
 [change] src/viewport-three/viewport-host.ts
 [change] tests/domain/runtime-project-time.test.ts
 [change] tests/domain/scene-document-validation.test.ts
 [change] tests/domain/world-settings.test.ts
 [change] tests/serialization/project-document-json.test.ts
 [change] tests/serialization/scene-document-json.test.ts
 [change] tests/unit/world-shader-sky.test.ts
This commit is contained in:
2026-04-22 15:30:37 +02:00
parent a0f8f72c62
commit b2a4e1da7b
19 changed files with 1332 additions and 817 deletions

View File

@@ -235,9 +235,7 @@ import {
createProjectSequence,
type ProjectSequenceLibrary
} from "../sequencer/project-sequences";
import {
type SequenceClip
} from "../sequencer/project-sequence-steps";
import { type SequenceClip } from "../sequencer/project-sequence-steps";
import {
createScenePath,
createScenePathPoint,
@@ -375,10 +373,15 @@ function readNpcDialogues(
return createProjectDialogue({
id: expectString(dialogueValue.id, `${label}.${dialogueIndex}.id`),
title: expectString(dialogueValue.title, `${label}.${dialogueIndex}.title`),
title: expectString(
dialogueValue.title,
`${label}.${dialogueIndex}.title`
),
lines: linesValue.map((lineValue, lineIndex) => {
if (!isRecord(lineValue)) {
throw new Error(`${label}.${dialogueIndex}.lines.${lineIndex} must be an object.`);
throw new Error(
`${label}.${dialogueIndex}.lines.${lineIndex} must be an object.`
);
}
return createProjectDialogueLine({
@@ -1906,13 +1909,19 @@ function readTerrain(value: unknown, label: string): Terrain {
value.layers === undefined
? undefined
: (() => {
if (!Array.isArray(value.layers) || value.layers.some((layer) => !isRecord(layer))) {
throw new Error(`${label}.layers must be an array of layer objects.`);
if (
!Array.isArray(value.layers) ||
value.layers.some((layer) => !isRecord(layer))
) {
throw new Error(
`${label}.layers must be an array of layer objects.`
);
}
return value.layers.map((layerValue, layerIndex) => ({
materialId:
layerValue.materialId === undefined || layerValue.materialId === null
layerValue.materialId === undefined ||
layerValue.materialId === null
? null
: expectString(
layerValue.materialId,
@@ -2053,7 +2062,9 @@ function readMaterialRegistry(
);
if (legacyMaterialId !== materialId) {
throw new Error(`${label}.${materialId}.id must match the registry key.`);
throw new Error(
`${label}.${materialId}.id must match the registry key.`
);
}
if (starterRegistry[materialId] === undefined) {
@@ -2211,13 +2222,14 @@ function readBoxBrushFaces(
materials: SceneDocument["materials"],
allowMissingUvState: boolean
): BoxBrushFaces {
return readBrushFaces(
value,
label,
materials,
allowMissingUvState,
["posX", "negX", "posY", "negY", "posZ", "negZ"]
) as BoxBrushFaces;
return readBrushFaces(value, label, materials, allowMissingUvState, [
"posX",
"negX",
"posY",
"negY",
"posZ",
"negZ"
]) as BoxBrushFaces;
}
function readBrushFaces(
@@ -2232,7 +2244,9 @@ function readBrushFaces(
}
const supportedFaceIds = new Set(faceIds);
const extraFaceKeys = Object.keys(value).filter((faceId) => !supportedFaceIds.has(faceId));
const extraFaceKeys = Object.keys(value).filter(
(faceId) => !supportedFaceIds.has(faceId)
);
if (extraFaceKeys.length > 0) {
throw new Error(
@@ -2733,11 +2747,15 @@ function readWorldTimePhaseProfile(
};
return {
background: readWorldBackgroundSettings(value.background, `${label}.background`, {
allowMissing: true,
allowShader: false,
defaultValue: fallbackBackground
}),
background: readWorldBackgroundSettings(
value.background,
`${label}.background`,
{
allowMissing: true,
allowShader: false,
defaultValue: fallbackBackground
}
),
skyTopColorHex,
skyBottomColorHex,
ambientColorHex: expectHexColor(
@@ -3870,27 +3888,29 @@ function readProjectScheduler(
routineValue.target,
`${label}.routines.${routineId}.target`
);
const effects =
Array.isArray(routineValue.effects)
? routineValue.effects.map((effectValue, effectIndex) =>
readControlEffect(
effectValue,
`${label}.routines.${routineId}.effects.${effectIndex}`
)
const effects = Array.isArray(routineValue.effects)
? routineValue.effects.map((effectValue, effectIndex) =>
readControlEffect(
effectValue,
`${label}.routines.${routineId}.effects.${effectIndex}`
)
: routineValue.effect === undefined
? undefined
: [
readControlEffect(
routineValue.effect,
`${label}.routines.${routineId}.effect`
)
];
)
: routineValue.effect === undefined
? undefined
: [
readControlEffect(
routineValue.effect,
`${label}.routines.${routineId}.effect`
)
];
return [
routineId,
createProjectScheduleRoutine({
id: expectString(routineValue.id, `${label}.routines.${routineId}.id`),
id: expectString(
routineValue.id,
`${label}.routines.${routineId}.id`
),
title: expectString(
routineValue.title,
`${label}.routines.${routineId}.title`
@@ -4018,7 +4038,10 @@ function readProjectDialogueLibrary(
}
dialogues[dialogueKey] = createProjectDialogue({
id: expectString(dialogueValue.id, `${label}.dialogues.${dialogueKey}.id`),
id: expectString(
dialogueValue.id,
`${label}.dialogues.${dialogueKey}.id`
),
title: expectString(
dialogueValue.title,
`${label}.dialogues.${dialogueKey}.title`
@@ -4049,7 +4072,10 @@ function readProjectDialogueLibrary(
};
}
function readProjectSequenceEffect(value: unknown, label: string): SequenceClip {
function readProjectSequenceEffect(
value: unknown,
label: string
): SequenceClip {
if (!isRecord(value)) {
throw new Error(`${label} must be an object.`);
}
@@ -4070,7 +4096,9 @@ function readProjectSequenceEffect(value: unknown, label: string): SequenceClip
};
case "makeNpcTalk":
if (stepClass !== "impulse") {
throw new Error(`${label}.makeNpcTalk effects must use the impulse class.`);
throw new Error(
`${label}.makeNpcTalk effects must use the impulse class.`
);
}
return {
@@ -4084,7 +4112,9 @@ function readProjectSequenceEffect(value: unknown, label: string): SequenceClip
};
case "teleportPlayer":
if (stepClass !== "impulse") {
throw new Error(`${label}.teleportPlayer effects must use the impulse class.`);
throw new Error(
`${label}.teleportPlayer effects must use the impulse class.`
);
}
return {
@@ -4119,7 +4149,9 @@ function readProjectSequenceEffect(value: unknown, label: string): SequenceClip
const isLegacyToggle = value.type === "toggleVisibility";
if (stepClass !== "impulse") {
throw new Error(`${label}.${String(value.type)} effects must use the impulse class.`);
throw new Error(
`${label}.${String(value.type)} effects must use the impulse class.`
);
}
if (isLegacyToggle) {
@@ -4135,8 +4167,7 @@ function readProjectSequenceEffect(value: unknown, label: string): SequenceClip
kind: "brush",
brushId: expectString(value.targetBrushId, `${label}.targetBrushId`)
},
mode:
visible === undefined ? "toggle" : visible ? "show" : "hide"
mode: visible === undefined ? "toggle" : visible ? "show" : "hide"
};
}
@@ -4144,7 +4175,10 @@ function readProjectSequenceEffect(value: unknown, label: string): SequenceClip
throw new Error(`${label}.target must be an object.`);
}
const targetKind = expectString(value.target.kind, `${label}.target.kind`);
const targetKind = expectString(
value.target.kind,
`${label}.target.kind`
);
if (targetKind !== "brush" && targetKind !== "modelInstance") {
throw new Error(`${label}.target.kind must be brush or modelInstance.`);
@@ -4224,7 +4258,10 @@ function readProjectSequenceLibrary(
}
sequences[sequenceKey] = createProjectSequence({
id: expectString(sequenceValue.id, `${label}.sequences.${sequenceKey}.id`),
id: expectString(
sequenceValue.id,
`${label}.sequences.${sequenceKey}.id`
),
title: expectString(
sequenceValue.title,
`${label}.sequences.${sequenceKey}.title`
@@ -5095,8 +5132,10 @@ export function migrateSceneDocument(source: unknown): SceneDocument {
source.version !== PROJECT_SEQUENCE_CLIPS_SCENE_DOCUMENT_VERSION &&
source.version !== PROJECT_SEQUENCE_TIMING_SCENE_DOCUMENT_VERSION &&
source.version !== PROJECT_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION &&
source.version !== PROJECT_SEQUENCE_UNIFIED_VISIBILITY_SCENE_DOCUMENT_VERSION &&
source.version !== SCENE_TRANSITION_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION &&
source.version !==
PROJECT_SEQUENCE_UNIFIED_VISIBILITY_SCENE_DOCUMENT_VERSION &&
source.version !==
SCENE_TRANSITION_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION &&
source.version !== AUTHORED_TERRAIN_FOUNDATION_SCENE_DOCUMENT_VERSION &&
source.version !== AUTHORED_TERRAIN_PAINT_SCENE_DOCUMENT_VERSION &&
source.version !== AUTHORED_TERRAIN_COLLISION_SCENE_DOCUMENT_VERSION &&
@@ -5115,9 +5154,13 @@ export function migrateSceneDocument(source: unknown): SceneDocument {
source.version < STARTER_PBR_MATERIAL_LIBRARY_SCENE_DOCUMENT_VERSION
});
const assets = readAssets(source.assets);
const legacyDialogues = readProjectDialogueLibrary(source.dialogues, "dialogues", {
allowMissing: true
});
const legacyDialogues = readProjectDialogueLibrary(
source.dialogues,
"dialogues",
{
allowMissing: true
}
);
const migratedDocument: SceneDocument = {
version: SCENE_DOCUMENT_VERSION,
@@ -5130,7 +5173,8 @@ export function migrateSceneDocument(source: unknown): SceneDocument {
source.version < PROJECT_SCHEDULER_FOUNDATION_SCENE_DOCUMENT_VERSION
}),
sequences: readProjectSequenceLibrary(source.sequences, "sequences", {
allowMissing: source.version < PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION
allowMissing:
source.version < PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION
}),
world: readWorldSettings(source.world, {
legacyProjectTimeValue:
@@ -5248,9 +5292,13 @@ export function migrateProjectDocument(source: unknown): ProjectDocument {
source.version < SCENE_EDITOR_PREFERENCES_SCENE_DOCUMENT_VERSION;
const allowMissingTimeSettings =
source.version < PROJECT_TIME_SYSTEM_SCENE_DOCUMENT_VERSION;
const legacyDialogues = readProjectDialogueLibrary(source.dialogues, "dialogues", {
allowMissing: true
});
const legacyDialogues = readProjectDialogueLibrary(
source.dialogues,
"dialogues",
{
allowMissing: true
}
);
for (const [sceneKey, sceneValue] of Object.entries(source.scenes)) {
scenes[sceneKey] = readProjectScene(

View File

@@ -56,7 +56,10 @@ import {
normalizeTorusMajorSegmentCount,
normalizeTorusTubeSegmentCount
} from "./brushes";
import { getBrushFaceIds, getBrushVertexIds } from "../geometry/whitebox-topology";
import {
getBrushFaceIds,
getBrushVertexIds
} from "../geometry/whitebox-topology";
import {
createSceneDocumentFromProject,
type ProjectDocument,
@@ -3315,7 +3318,12 @@ function validateNpcEntity(
for (const [dialogueIndex, dialogue] of entity.dialogues.entries()) {
const dialoguePath = `${path}.dialogues.${dialogueIndex}`;
registerAuthoredId(dialogue.id, dialoguePath, seenNpcDialogueIds, diagnostics);
registerAuthoredId(
dialogue.id,
dialoguePath,
seenNpcDialogueIds,
diagnostics
);
validateProjectDialogue(
dialogue,
dialoguePath,
@@ -3326,7 +3334,9 @@ function validateNpcEntity(
if (
entity.defaultDialogueId !== null &&
!entity.dialogues.some((dialogue) => dialogue.id === entity.defaultDialogueId)
!entity.dialogues.some(
(dialogue) => dialogue.id === entity.defaultDialogueId
)
) {
diagnostics.push(
createDiagnostic(
@@ -3479,7 +3489,10 @@ function validateSoundEmitterControlTarget(
return;
}
if (targetEntity.kind !== target.entityKind || targetEntity.kind !== "soundEmitter") {
if (
targetEntity.kind !== target.entityKind ||
targetEntity.kind !== "soundEmitter"
) {
diagnostics.push(
createDiagnostic(
"error",
@@ -4014,7 +4027,8 @@ function validateInteractionLink(
break;
}
case "runSequence": {
const sequence = document.sequences.sequences[link.action.sequenceId] ?? null;
const sequence =
document.sequences.sequences[link.action.sequenceId] ?? null;
if (sequence === null) {
diagnostics.push(
@@ -4214,7 +4228,10 @@ function validateProjectScheduler(
);
}
if (!isFiniteNumber(routine.priority) || !Number.isInteger(routine.priority)) {
if (
!isFiniteNumber(routine.priority) ||
!Number.isInteger(routine.priority)
) {
diagnostics.push(
createDiagnostic(
"error",
@@ -4229,7 +4246,11 @@ function validateProjectScheduler(
routine.sequenceId === null
? []
: getProjectSequenceImpulseSteps(
sequences.sequences[routine.sequenceId] ?? { id: "", title: "", effects: [] }
sequences.sequences[routine.sequenceId] ?? {
id: "",
title: "",
effects: []
}
);
if (resolvedEffects.length === 0) {
@@ -4392,7 +4413,9 @@ function recordProjectSchedulerSceneTargets(
context.actorIds.add(entity.actorId);
const usages = context.actorUsagesById.get(entity.actorId) ?? [];
const targetAsset =
entity.modelAssetId === null ? undefined : scene.assets[entity.modelAssetId];
entity.modelAssetId === null
? undefined
: scene.assets[entity.modelAssetId];
usages.push({
sceneId,
entityId: entity.id,
@@ -4408,7 +4431,10 @@ function recordProjectSchedulerSceneTargets(
}
if (entity.kind === "soundEmitter") {
context.soundEmitterHasAudioById.set(entity.id, entity.audioAssetId !== null);
context.soundEmitterHasAudioById.set(
entity.id,
entity.audioAssetId !== null
);
}
}
@@ -4442,13 +4468,17 @@ function createProjectSchedulerValidationContextFromProjectDocument(
const context = createEmptyProjectSchedulerValidationContext();
for (const scene of Object.values(document.scenes)) {
recordProjectSchedulerSceneTargets(context, {
brushes: scene.brushes,
entities: scene.entities,
modelInstances: scene.modelInstances,
assets: document.assets,
paths: scene.paths
}, scene.id);
recordProjectSchedulerSceneTargets(
context,
{
brushes: scene.brushes,
entities: scene.entities,
modelInstances: scene.modelInstances,
assets: document.assets,
paths: scene.paths
},
scene.id
);
}
return context;
@@ -4490,7 +4520,10 @@ function validateProjectSchedulerSingleActorUsage(
): ProjectSchedulerActorValidationUsage | null {
validateProjectSchedulerActorTarget(target, path, context, diagnostics);
const usages = getProjectSchedulerActorValidationUsages(context, target.actorId);
const usages = getProjectSchedulerActorValidationUsages(
context,
target.actorId
);
if (usages.length === 1) {
return usages[0] ?? null;
@@ -4616,7 +4649,8 @@ function validateProjectSchedulerModelTarget(
context: ProjectSchedulerValidationContext,
diagnostics: SceneDiagnostic[]
) {
const modelInstanceCount = context.modelInstanceCounts.get(target.modelInstanceId) ?? 0;
const modelInstanceCount =
context.modelInstanceCounts.get(target.modelInstanceId) ?? 0;
if (modelInstanceCount === 0) {
diagnostics.push(
@@ -4675,7 +4709,12 @@ function validateProjectSchedulerControlTarget(
validateProjectSchedulerEntityTarget(target, path, context, diagnostics);
return;
case "interaction":
validateProjectSchedulerInteractionTarget(target, path, context, diagnostics);
validateProjectSchedulerInteractionTarget(
target,
path,
context,
diagnostics
);
return;
case "scene":
validateProjectSchedulerSceneTarget(target, path, diagnostics);
@@ -4901,7 +4940,8 @@ function validateProjectSchedulerEffect(
}
const animationNames =
context.modelAnimationNamesById.get(effect.target.modelInstanceId) ?? [];
context.modelAnimationNamesById.get(effect.target.modelInstanceId) ??
[];
if (
animationNames.length > 0 &&
@@ -5046,7 +5086,11 @@ function validateProjectSchedulerEffect(
}
return;
case "setAmbientLightIntensity":
validateProjectSchedulerSceneTarget(effect.target, `${path}.target`, diagnostics);
validateProjectSchedulerSceneTarget(
effect.target,
`${path}.target`,
diagnostics
);
if (!isNonNegativeFiniteNumber(effect.intensity)) {
diagnostics.push(
createDiagnostic(
@@ -5059,7 +5103,11 @@ function validateProjectSchedulerEffect(
}
return;
case "setAmbientLightColor":
validateProjectSchedulerSceneTarget(effect.target, `${path}.target`, diagnostics);
validateProjectSchedulerSceneTarget(
effect.target,
`${path}.target`,
diagnostics
);
if (!isHexColorString(effect.colorHex)) {
diagnostics.push(
createDiagnostic(
@@ -5072,7 +5120,11 @@ function validateProjectSchedulerEffect(
}
return;
case "setSunLightIntensity":
validateProjectSchedulerSceneTarget(effect.target, `${path}.target`, diagnostics);
validateProjectSchedulerSceneTarget(
effect.target,
`${path}.target`,
diagnostics
);
if (!isNonNegativeFiniteNumber(effect.intensity)) {
diagnostics.push(
createDiagnostic(
@@ -5085,7 +5137,11 @@ function validateProjectSchedulerEffect(
}
return;
case "setSunLightColor":
validateProjectSchedulerSceneTarget(effect.target, `${path}.target`, diagnostics);
validateProjectSchedulerSceneTarget(
effect.target,
`${path}.target`,
diagnostics
);
if (!isHexColorString(effect.colorHex)) {
diagnostics.push(
createDiagnostic(
@@ -5185,7 +5241,6 @@ function validateProjectDialogue(
)
);
}
}
}
@@ -5270,7 +5325,9 @@ function validateProjectSequence(
if (
effect.dialogueId !== null &&
!targetNpc.dialogues.some((dialogue) => dialogue.id === effect.dialogueId)
!targetNpc.dialogues.some(
(dialogue) => dialogue.id === effect.dialogueId
)
) {
diagnostics.push(
createDiagnostic(
@@ -5343,7 +5400,10 @@ function validateProjectSequence(
)
);
}
} else if ((context.modelInstanceCounts.get(effect.target.modelInstanceId) ?? 0) === 0) {
} else if (
(context.modelInstanceCounts.get(effect.target.modelInstanceId) ??
0) === 0
) {
diagnostics.push(
createDiagnostic(
"error",
@@ -5403,7 +5463,9 @@ function validateProjectResources(
validateProjectAsset(asset, path, diagnostics);
}
for (const [sequenceKey, sequence] of Object.entries(document.sequences.sequences)) {
for (const [sequenceKey, sequence] of Object.entries(
document.sequences.sequences
)) {
const path = `sequences.sequences.${sequenceKey}`;
if (sequence.id !== sequenceKey) {
@@ -5673,7 +5735,9 @@ export function validateSceneDocument(
validateProjectAsset(asset, path, diagnostics);
}
for (const [sequenceKey, sequence] of Object.entries(document.sequences.sequences)) {
for (const [sequenceKey, sequence] of Object.entries(
document.sequences.sequences
)) {
const path = `sequences.sequences.${sequenceKey}`;
if (sequence.id !== sequenceKey) {
@@ -6367,7 +6431,6 @@ export function validateProjectDocument(
path: prefixDiagnosticPath(`${scenePath}.`, diagnostic.path)
});
}
}
return {

View File

@@ -43,8 +43,7 @@ export const NPC_DIALOGUE_LINE_SPEAKER_REMOVED_SCENE_DOCUMENT_VERSION =
export const NPC_ONLY_DIALOGUES_SCENE_DOCUMENT_VERSION = 62 as const;
export const WHITEBOX_EXPANDED_PRIMITIVES_SCENE_DOCUMENT_VERSION = 61 as const;
export const WHITEBOX_PRIMITIVES_SCENE_DOCUMENT_VERSION = 60 as const;
export const STARTER_PBR_MATERIAL_LIBRARY_SCENE_DOCUMENT_VERSION =
59 as const;
export const STARTER_PBR_MATERIAL_LIBRARY_SCENE_DOCUMENT_VERSION = 59 as const;
export const SCENE_TRANSITION_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION =
58 as const;
export const PROJECT_SEQUENCE_UNIFIED_VISIBILITY_SCENE_DOCUMENT_VERSION =
@@ -56,7 +55,8 @@ export const PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION = 53 as const;
export const PLAYER_START_PAUSE_BINDINGS_SCENE_DOCUMENT_VERSION = 52 as const;
export const NPC_DIALOGUE_REFERENCE_SCENE_DOCUMENT_VERSION = 51 as const;
export const PROJECT_DIALOGUE_LIBRARY_SCENE_DOCUMENT_VERSION = 50 as const;
export const SCHEDULER_ACTOR_ROUTINE_EFFECTS_SCENE_DOCUMENT_VERSION = 49 as const;
export const SCHEDULER_ACTOR_ROUTINE_EFFECTS_SCENE_DOCUMENT_VERSION =
49 as const;
export const SCHEDULER_CONTROL_EFFECTS_SCENE_DOCUMENT_VERSION = 48 as const;
export const EXPANDED_CONTROL_SURFACE_SCENE_DOCUMENT_VERSION = 47 as const;
export const PROJECT_SCHEDULER_FOUNDATION_SCENE_DOCUMENT_VERSION = 46 as const;

View File

@@ -8,19 +8,40 @@ export type WorldBackgroundMode =
| "image"
| "shader";
export type WorldTimePhase = "dawn" | "dusk" | "night";
export type WorldShaderSkyPresetId = (typeof WORLD_SHADER_SKY_PRESET_IDS)[number];
export type WorldShaderSkyPresetId =
(typeof WORLD_SHADER_SKY_PRESET_IDS)[number];
export const ADVANCED_RENDERING_SHADOW_MAP_SIZES = [512, 1024, 2048, 4096] as const;
export const ADVANCED_RENDERING_SHADOW_TYPES = ["basic", "pcf", "pcfSoft"] as const;
export const ADVANCED_RENDERING_TONE_MAPPING_MODES = ["none", "linear", "reinhard", "cineon", "acesFilmic"] as const;
export const ADVANCED_RENDERING_SHADOW_MAP_SIZES = [
512, 1024, 2048, 4096
] as const;
export const ADVANCED_RENDERING_SHADOW_TYPES = [
"basic",
"pcf",
"pcfSoft"
] as const;
export const ADVANCED_RENDERING_TONE_MAPPING_MODES = [
"none",
"linear",
"reinhard",
"cineon",
"acesFilmic"
] as const;
export const BOX_VOLUME_RENDER_PATHS = ["performance", "quality"] as const;
export const ADVANCED_RENDERING_WATER_REFLECTION_MODES = ["none", "world", "all"] as const;
export const ADVANCED_RENDERING_WATER_REFLECTION_MODES = [
"none",
"world",
"all"
] as const;
export type AdvancedRenderingShadowMapSize = (typeof ADVANCED_RENDERING_SHADOW_MAP_SIZES)[number];
export type AdvancedRenderingShadowType = (typeof ADVANCED_RENDERING_SHADOW_TYPES)[number];
export type AdvancedRenderingToneMappingMode = (typeof ADVANCED_RENDERING_TONE_MAPPING_MODES)[number];
export type AdvancedRenderingShadowMapSize =
(typeof ADVANCED_RENDERING_SHADOW_MAP_SIZES)[number];
export type AdvancedRenderingShadowType =
(typeof ADVANCED_RENDERING_SHADOW_TYPES)[number];
export type AdvancedRenderingToneMappingMode =
(typeof ADVANCED_RENDERING_TONE_MAPPING_MODES)[number];
export type BoxVolumeRenderPath = (typeof BOX_VOLUME_RENDER_PATHS)[number];
export type AdvancedRenderingWaterReflectionMode = (typeof ADVANCED_RENDERING_WATER_REFLECTION_MODES)[number];
export type AdvancedRenderingWaterReflectionMode =
(typeof ADVANCED_RENDERING_WATER_REFLECTION_MODES)[number];
export interface WorldSolidBackgroundSettings {
mode: "solid";
@@ -186,7 +207,8 @@ const DEFAULT_GRADIENT_BOTTOM_COLOR = "#141a22";
export const DEFAULT_NIGHT_IMAGE_ENVIRONMENT_INTENSITY = 0.35 as const;
export const DEFAULT_TIME_PHASE_IMAGE_ENVIRONMENT_INTENSITY = 0.5 as const;
const DEFAULT_ADVANCED_RENDERING_SHADOW_MAP_SIZE: AdvancedRenderingShadowMapSize = 2048;
const DEFAULT_ADVANCED_RENDERING_SHADOW_TYPE: AdvancedRenderingShadowType = "pcfSoft";
const DEFAULT_ADVANCED_RENDERING_SHADOW_TYPE: AdvancedRenderingShadowType =
"pcfSoft";
const DEFAULT_ADVANCED_RENDERING_SHADOW_BIAS = -0.0005;
const DEFAULT_ADVANCED_RENDERING_AMBIENT_OCCLUSION_INTENSITY = 1;
const DEFAULT_ADVANCED_RENDERING_AMBIENT_OCCLUSION_RADIUS = 0.5;
@@ -194,7 +216,8 @@ const DEFAULT_ADVANCED_RENDERING_AMBIENT_OCCLUSION_SAMPLES = 8;
const DEFAULT_ADVANCED_RENDERING_BLOOM_INTENSITY = 0.75;
const DEFAULT_ADVANCED_RENDERING_BLOOM_THRESHOLD = 0.85;
const DEFAULT_ADVANCED_RENDERING_BLOOM_RADIUS = 0.35;
const DEFAULT_ADVANCED_RENDERING_TONE_MAPPING_MODE: AdvancedRenderingToneMappingMode = "acesFilmic";
const DEFAULT_ADVANCED_RENDERING_TONE_MAPPING_MODE: AdvancedRenderingToneMappingMode =
"acesFilmic";
const DEFAULT_ADVANCED_RENDERING_TONE_MAPPING_EXPOSURE = 1;
const DEFAULT_ADVANCED_RENDERING_DEPTH_OF_FIELD_FOCUS_DISTANCE = 10;
const DEFAULT_ADVANCED_RENDERING_DEPTH_OF_FIELD_FOCAL_LENGTH = 0.03;
@@ -202,7 +225,8 @@ const DEFAULT_ADVANCED_RENDERING_DEPTH_OF_FIELD_BOKEH_SCALE = 1.5;
const DEFAULT_ADVANCED_RENDERING_WHITEBOX_BEVEL_EDGE_WIDTH = 0.14;
const DEFAULT_ADVANCED_RENDERING_WHITEBOX_BEVEL_NORMAL_STRENGTH = 0.75;
const DEFAULT_BOX_VOLUME_RENDER_PATH: BoxVolumeRenderPath = "performance";
const DEFAULT_ADVANCED_RENDERING_WATER_REFLECTION_MODE: AdvancedRenderingWaterReflectionMode = "none";
const DEFAULT_ADVANCED_RENDERING_WATER_REFLECTION_MODE: AdvancedRenderingWaterReflectionMode =
"none";
const DEFAULT_SHADER_SKY_DAY_TOP_COLOR = "#5f8fd3";
const DEFAULT_SHADER_SKY_DAY_BOTTOM_COLOR = "#d8eeff";
const DEFAULT_SHADER_SKY_SUN_DISC_SIZE_DEGREES = 2.6;
@@ -247,24 +271,42 @@ function resolveShaderSkyDayGradient(
};
}
export function isAdvancedRenderingShadowMapSize(value: unknown): value is AdvancedRenderingShadowMapSize {
return ADVANCED_RENDERING_SHADOW_MAP_SIZES.includes(value as AdvancedRenderingShadowMapSize);
export function isAdvancedRenderingShadowMapSize(
value: unknown
): value is AdvancedRenderingShadowMapSize {
return ADVANCED_RENDERING_SHADOW_MAP_SIZES.includes(
value as AdvancedRenderingShadowMapSize
);
}
export function isAdvancedRenderingShadowType(value: unknown): value is AdvancedRenderingShadowType {
return ADVANCED_RENDERING_SHADOW_TYPES.includes(value as AdvancedRenderingShadowType);
export function isAdvancedRenderingShadowType(
value: unknown
): value is AdvancedRenderingShadowType {
return ADVANCED_RENDERING_SHADOW_TYPES.includes(
value as AdvancedRenderingShadowType
);
}
export function isAdvancedRenderingToneMappingMode(value: unknown): value is AdvancedRenderingToneMappingMode {
return ADVANCED_RENDERING_TONE_MAPPING_MODES.includes(value as AdvancedRenderingToneMappingMode);
export function isAdvancedRenderingToneMappingMode(
value: unknown
): value is AdvancedRenderingToneMappingMode {
return ADVANCED_RENDERING_TONE_MAPPING_MODES.includes(
value as AdvancedRenderingToneMappingMode
);
}
export function isBoxVolumeRenderPath(value: unknown): value is BoxVolumeRenderPath {
export function isBoxVolumeRenderPath(
value: unknown
): value is BoxVolumeRenderPath {
return BOX_VOLUME_RENDER_PATHS.includes(value as BoxVolumeRenderPath);
}
export function isAdvancedRenderingWaterReflectionMode(value: unknown): value is AdvancedRenderingWaterReflectionMode {
return ADVANCED_RENDERING_WATER_REFLECTION_MODES.includes(value as AdvancedRenderingWaterReflectionMode);
export function isAdvancedRenderingWaterReflectionMode(
value: unknown
): value is AdvancedRenderingWaterReflectionMode {
return ADVANCED_RENDERING_WATER_REFLECTION_MODES.includes(
value as AdvancedRenderingWaterReflectionMode
);
}
export function createDefaultAdvancedRenderingSettings(): AdvancedRenderingSettings {
@@ -478,7 +520,9 @@ export function isHexColorString(value: string): boolean {
return /^#[0-9a-f]{6}$/i.test(value);
}
export function isWorldBackgroundMode(value: unknown): value is WorldBackgroundMode {
export function isWorldBackgroundMode(
value: unknown
): value is WorldBackgroundMode {
return (
value === "solid" ||
value === "verticalGradient" ||
@@ -487,7 +531,9 @@ export function isWorldBackgroundMode(value: unknown): value is WorldBackgroundM
);
}
export function cloneWorldBackgroundSettings(background: WorldBackgroundSettings): WorldBackgroundSettings {
export function cloneWorldBackgroundSettings(
background: WorldBackgroundSettings
): WorldBackgroundSettings {
if (background.mode === "solid") {
return {
mode: "solid",
@@ -572,7 +618,9 @@ export function cloneWorldSettings(world: WorldSettings): WorldSettings {
};
}
export function cloneAdvancedRenderingSettings(settings: AdvancedRenderingSettings): AdvancedRenderingSettings {
export function cloneAdvancedRenderingSettings(
settings: AdvancedRenderingSettings
): AdvancedRenderingSettings {
return {
enabled: settings.enabled,
shadows: {
@@ -599,7 +647,10 @@ export function cloneAdvancedRenderingSettings(settings: AdvancedRenderingSettin
};
}
export function areWorldBackgroundSettingsEqual(left: WorldBackgroundSettings, right: WorldBackgroundSettings): boolean {
export function areWorldBackgroundSettingsEqual(
left: WorldBackgroundSettings,
right: WorldBackgroundSettings
): boolean {
if (left.mode !== right.mode) {
return false;
}
@@ -609,14 +660,22 @@ export function areWorldBackgroundSettingsEqual(left: WorldBackgroundSettings, r
}
if (left.mode === "verticalGradient" && right.mode === "verticalGradient") {
return left.topColorHex === right.topColorHex && left.bottomColorHex === right.bottomColorHex;
return (
left.topColorHex === right.topColorHex &&
left.bottomColorHex === right.bottomColorHex
);
}
if (left.mode === "shader" && right.mode === "shader") {
return true;
}
return left.mode === "image" && right.mode === "image" && left.assetId === right.assetId && left.environmentIntensity === right.environmentIntensity;
return (
left.mode === "image" &&
right.mode === "image" &&
left.assetId === right.assetId &&
left.environmentIntensity === right.environmentIntensity
);
}
export function areWorldShaderSkySettingsEqual(
@@ -628,7 +687,8 @@ export function areWorldShaderSkySettingsEqual(
left.dayTopColorHex === right.dayTopColorHex &&
left.dayBottomColorHex === right.dayBottomColorHex &&
left.celestial.sunDiscSizeDegrees === right.celestial.sunDiscSizeDegrees &&
left.celestial.moonDiscSizeDegrees === right.celestial.moonDiscSizeDegrees &&
left.celestial.moonDiscSizeDegrees ===
right.celestial.moonDiscSizeDegrees &&
left.stars.density === right.stars.density &&
left.stars.brightness === right.stars.brightness &&
left.clouds.coverage === right.clouds.coverage &&
@@ -684,7 +744,10 @@ export function areWorldTimeOfDaySettingsEqual(
);
}
export function areWorldSettingsEqual(left: WorldSettings, right: WorldSettings): boolean {
export function areWorldSettingsEqual(
left: WorldSettings,
right: WorldSettings
): boolean {
return (
left.projectTimeLightingEnabled === right.projectTimeLightingEnabled &&
left.showCelestialBodies === right.showCelestialBodies &&
@@ -698,11 +761,17 @@ export function areWorldSettingsEqual(left: WorldSettings, right: WorldSettings)
left.sunLight.direction.y === right.sunLight.direction.y &&
left.sunLight.direction.z === right.sunLight.direction.z &&
areWorldTimeOfDaySettingsEqual(left.timeOfDay, right.timeOfDay) &&
areAdvancedRenderingSettingsEqual(left.advancedRendering, right.advancedRendering)
areAdvancedRenderingSettingsEqual(
left.advancedRendering,
right.advancedRendering
)
);
}
export function areAdvancedRenderingSettingsEqual(left: AdvancedRenderingSettings, right: AdvancedRenderingSettings): boolean {
export function areAdvancedRenderingSettingsEqual(
left: AdvancedRenderingSettings,
right: AdvancedRenderingSettings
): boolean {
return (
left.enabled === right.enabled &&
left.shadows.enabled === right.shadows.enabled &&
@@ -736,8 +805,7 @@ export function changeWorldBackgroundMode(
background: WorldBackgroundSettings,
mode: WorldBackgroundMode,
imageAssetId?: string,
imageEnvironmentIntensity: number =
DEFAULT_TIME_PHASE_IMAGE_ENVIRONMENT_INTENSITY,
imageEnvironmentIntensity: number = DEFAULT_TIME_PHASE_IMAGE_ENVIRONMENT_INTENSITY,
shaderSkySettings?: WorldShaderSkySettings
): WorldBackgroundSettings {
if (mode === "image") {
@@ -746,7 +814,9 @@ export function changeWorldBackgroundMode(
return cloneWorldBackgroundSettings(background);
}
throw new Error("An image asset must be selected to use an image background.");
throw new Error(
"An image asset must be selected to use an image background."
);
}
return {
@@ -779,7 +849,7 @@ export function changeWorldBackgroundMode(
? background.topColorHex
: background.mode === "shader" && shaderSkySettings !== undefined
? shaderSkySettings.dayTopColorHex
: DEFAULT_SOLID_BACKGROUND_COLOR
: DEFAULT_SOLID_BACKGROUND_COLOR
};
}