diff --git a/src/document/migrate-scene-document.js b/src/document/migrate-scene-document.js index a7ac0a7c..8fb40fa9 100644 --- a/src/document/migrate-scene-document.js +++ b/src/document/migrate-scene-document.js @@ -3,9 +3,9 @@ import { createModelInstanceCollisionSettings, createModelInstance, isModelInsta import { isProjectAssetKind } from "../assets/project-assets"; import { createPlayerStartColliderSettings, createInteractableEntity, normalizeEntityName, createPointLightEntity, createPlayerStartEntity, createSoundEmitterEntity, createSpotLightEntity, createTeleportTargetEntity, createTriggerVolumeEntity, isPlayerStartColliderMode } from "../entities/entity-instances"; import { createPlayAnimationInteractionLink, createPlaySoundInteractionLink, createStopAnimationInteractionLink, createStopSoundInteractionLink, createTeleportPlayerInteractionLink, createToggleVisibilityInteractionLink, isInteractionTriggerKind } from "../interactions/interaction-links"; -import { BOX_VERTEX_IDS, createBoxBrush, createDefaultBoxBrushGeometry, createDefaultBoxBrushFogSettings, createDefaultBoxBrushWaterSettings, createDefaultFaceUvState, DEFAULT_BOX_BRUSH_ROTATION_DEGREES, isBoxBrushVolumeMode, isBoxFaceId, isFaceUvRotationQuarterTurns, normalizeBrushName } from "./brushes"; +import { BOX_VERTEX_IDS, MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT, createBoxBrush, createDefaultBoxBrushGeometry, createDefaultBoxBrushFogSettings, createDefaultBoxBrushWaterSettings, createDefaultFaceUvState, DEFAULT_BOX_BRUSH_ROTATION_DEGREES, isBoxBrushVolumeMode, isBoxFaceId, isFaceUvRotationQuarterTurns, normalizeBrushName } from "./brushes"; import { BOX_BRUSH_SCENE_DOCUMENT_VERSION, ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION, ENTITY_NAMES_SCENE_DOCUMENT_VERSION, ENTITY_SYSTEM_FOUNDATION_SCENE_DOCUMENT_VERSION, FACE_MATERIALS_SCENE_DOCUMENT_VERSION, FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION, FOUNDATION_SCENE_DOCUMENT_VERSION, IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION, LOCAL_LIGHTS_AND_SKYBOX_SCENE_DOCUMENT_VERSION, MODEL_ASSET_PIPELINE_SCENE_DOCUMENT_VERSION, PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION, RUNNER_V1_SCENE_DOCUMENT_VERSION, SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION, SCENE_DOCUMENT_VERSION, TRIGGER_ACTION_TARGET_FOUNDATION_SCENE_DOCUMENT_VERSION, WHITEBOX_BOX_VOLUME_SCENE_DOCUMENT_VERSION, WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION, WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION, WORLD_ENVIRONMENT_SCENE_DOCUMENT_VERSION } from "./scene-document"; -import { createDefaultAdvancedRenderingSettings, isBoxVolumeRenderPath, isAdvancedRenderingShadowMapSize, isAdvancedRenderingShadowType, isAdvancedRenderingToneMappingMode, isWorldBackgroundMode } from "./world-settings"; +import { createDefaultAdvancedRenderingSettings, isAdvancedRenderingWaterReflectionMode, isBoxVolumeRenderPath, isAdvancedRenderingShadowMapSize, isAdvancedRenderingShadowType, isAdvancedRenderingToneMappingMode, isWorldBackgroundMode } from "./world-settings"; function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } @@ -88,6 +88,9 @@ function readOptionalPositiveInteger(value, label, fallback) { } return integerValue; } +function readOptionalPositiveIntegerWithMax(value, label, fallback, max) { + return Math.min(readOptionalPositiveInteger(value, label, fallback), max); +} function readOptionalAllowedValue(value, label, fallback, guard) { if (value === undefined) { return fallback; @@ -130,6 +133,7 @@ function readAdvancedRenderingSettings(value) { const toneMappingMode = readOptionalAllowedValue(toneMapping?.mode, "world.advancedRendering.toneMapping.mode", defaults.toneMapping.mode, isAdvancedRenderingToneMappingMode); const fogPath = readOptionalAllowedValue(value.fogPath, "world.advancedRendering.fogPath", defaults.fogPath, isBoxVolumeRenderPath); const waterPath = readOptionalAllowedValue(value.waterPath, "world.advancedRendering.waterPath", defaults.waterPath, isBoxVolumeRenderPath); + const waterReflectionMode = readOptionalAllowedValue(value.waterReflectionMode, "world.advancedRendering.waterReflectionMode", defaults.waterReflectionMode, isAdvancedRenderingWaterReflectionMode); return { enabled: readOptionalBoolean(value.enabled, "world.advancedRendering.enabled", defaults.enabled), shadows: { @@ -161,7 +165,8 @@ function readAdvancedRenderingSettings(value) { bokehScale: readOptionalNonNegativeFiniteNumber(depthOfField?.bokehScale, "world.advancedRendering.depthOfField.bokehScale", defaults.depthOfField.bokehScale) }, fogPath, - waterPath + waterPath, + waterReflectionMode }; } function readBoxBrushVolumeSettings(value, label) { @@ -190,7 +195,8 @@ function readBoxBrushVolumeSettings(value, label) { water: { colorHex: water.colorHex === undefined ? defaults.colorHex : expectHexColor(water.colorHex, `${label}.water.colorHex`), surfaceOpacity: readOptionalNonNegativeFiniteNumber(water.surfaceOpacity, `${label}.water.surfaceOpacity`, defaults.surfaceOpacity), - waveStrength: readOptionalNonNegativeFiniteNumber(water.waveStrength, `${label}.water.waveStrength`, defaults.waveStrength) + waveStrength: readOptionalNonNegativeFiniteNumber(water.waveStrength, `${label}.water.waveStrength`, defaults.waveStrength), + foamContactLimit: readOptionalPositiveIntegerWithMax(water.foamContactLimit, `${label}.water.foamContactLimit`, defaults.foamContactLimit, MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT) } }; } diff --git a/src/document/scene-document-validation.js b/src/document/scene-document-validation.js index cb011841..be38a1db 100644 --- a/src/document/scene-document-validation.js +++ b/src/document/scene-document-validation.js @@ -2,7 +2,7 @@ import {} from "../assets/project-assets"; import { isModelInstanceCollisionMode } from "../assets/model-instances"; import { isPlayerStartColliderMode, getPlayerStartColliderHeight } from "../entities/entity-instances"; import {} from "../interactions/interaction-links"; -import { BOX_FACE_IDS, BOX_VERTEX_IDS, hasPositiveBoxSize, isBoxBrushVolumeMode } from "./brushes"; +import { BOX_FACE_IDS, BOX_VERTEX_IDS, MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT, hasPositiveBoxSize, isBoxBrushVolumeMode } from "./brushes"; import { isAdvancedRenderingShadowMapSize, isAdvancedRenderingShadowType, isBoxVolumeRenderPath, isAdvancedRenderingToneMappingMode, isHexColorString } from "./world-settings"; export function createDiagnostic(severity, code, message, path, scope = "document") { return { @@ -31,6 +31,9 @@ function isPositiveFiniteNumber(value) { function isPositiveInteger(value) { return isFiniteNumber(value) && Number.isInteger(value) && value > 0; } +function isPositiveIntegerInRange(value, max) { + return isPositiveInteger(value) && value <= max; +} function isBoolean(value) { return typeof value === "boolean"; } @@ -147,6 +150,9 @@ function validateWorldSettings(world, document, diagnostics) { if (!isBoxVolumeRenderPath(advancedRendering.waterPath)) { diagnostics.push(createDiagnostic("error", "invalid-advanced-rendering-water-path", "Advanced rendering water path must be performance or quality.", "world.advancedRendering.waterPath")); } + if (!["none", "world", "all"].includes(advancedRendering.waterReflectionMode)) { + diagnostics.push(createDiagnostic("error", "invalid-advanced-rendering-water-reflection-mode", "Advanced rendering water reflection mode must be none, world, or all.", "world.advancedRendering.waterReflectionMode")); + } } function validatePointLightEntity(entity, path, diagnostics) { if (!isFiniteVec3(entity.position)) { @@ -594,6 +600,9 @@ export function validateSceneDocument(document) { if (!isNonNegativeFiniteNumber(water.waveStrength)) { diagnostics.push(createDiagnostic("error", "invalid-box-water-wave-strength", "Water wave strength must be a non-negative finite number.", `${path}.volume.water.waveStrength`)); } + if (!isPositiveIntegerInRange(water.foamContactLimit, MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT)) { + diagnostics.push(createDiagnostic("error", "invalid-box-water-foam-contact-limit", `Water foam contact limit must be a positive integer between 1 and ${MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT}.`, `${path}.volume.water.foamContactLimit`)); + } } } if (volume.mode === "fog") {