Add visibility and enabled properties to model instances, brushes, and entities

This commit is contained in:
2026-04-12 03:32:23 +02:00
parent 0f4ccab2d6
commit 9300e62e7d
3 changed files with 112 additions and 22 deletions

View File

@@ -16,6 +16,8 @@ export interface ModelInstance {
kind: "modelInstance"; kind: "modelInstance";
assetId: string; assetId: string;
name?: string; name?: string;
visible: boolean;
enabled: boolean;
position: Vec3; position: Vec3;
rotationDegrees: Vec3; rotationDegrees: Vec3;
scale: Vec3; scale: Vec3;
@@ -42,6 +44,9 @@ export const DEFAULT_MODEL_INSTANCE_SCALE: Vec3 = {
z: 1 z: 1
}; };
export const DEFAULT_MODEL_INSTANCE_VISIBLE = true;
export const DEFAULT_MODEL_INSTANCE_ENABLED = true;
export const DEFAULT_MODEL_INSTANCE_COLLISION_SETTINGS: ModelInstanceCollisionSettings = { export const DEFAULT_MODEL_INSTANCE_COLLISION_SETTINGS: ModelInstanceCollisionSettings = {
mode: "none", mode: "none",
visible: false visible: false
@@ -117,7 +122,7 @@ function assertPositiveFiniteVec3(vector: Vec3, label: string) {
export function createModelInstance( export function createModelInstance(
overrides: Partial< overrides: Partial<
Pick<ModelInstance, "id" | "name" | "position" | "rotationDegrees" | "scale" | "collision" | "animationClipName" | "animationAutoplay"> Pick<ModelInstance, "id" | "name" | "visible" | "enabled" | "position" | "rotationDegrees" | "scale" | "collision" | "animationClipName" | "animationAutoplay">
> & > &
Pick<ModelInstance, "assetId"> Pick<ModelInstance, "assetId">
): ModelInstance { ): ModelInstance {
@@ -125,6 +130,8 @@ export function createModelInstance(
const rotationDegrees = cloneVec3(overrides.rotationDegrees ?? DEFAULT_MODEL_INSTANCE_ROTATION_DEGREES); const rotationDegrees = cloneVec3(overrides.rotationDegrees ?? DEFAULT_MODEL_INSTANCE_ROTATION_DEGREES);
const scale = cloneVec3(overrides.scale ?? DEFAULT_MODEL_INSTANCE_SCALE); const scale = cloneVec3(overrides.scale ?? DEFAULT_MODEL_INSTANCE_SCALE);
const collision = cloneModelInstanceCollisionSettings(overrides.collision ?? DEFAULT_MODEL_INSTANCE_COLLISION_SETTINGS); const collision = cloneModelInstanceCollisionSettings(overrides.collision ?? DEFAULT_MODEL_INSTANCE_COLLISION_SETTINGS);
const visible = overrides.visible ?? DEFAULT_MODEL_INSTANCE_VISIBLE;
const enabled = overrides.enabled ?? DEFAULT_MODEL_INSTANCE_ENABLED;
if (overrides.assetId.trim().length === 0) { if (overrides.assetId.trim().length === 0) {
throw new Error("Model instance assetId must be a non-empty string."); throw new Error("Model instance assetId must be a non-empty string.");
@@ -134,11 +141,21 @@ export function createModelInstance(
assertFiniteVec3(rotationDegrees, "Model instance rotation"); assertFiniteVec3(rotationDegrees, "Model instance rotation");
assertPositiveFiniteVec3(scale, "Model instance scale"); assertPositiveFiniteVec3(scale, "Model instance scale");
if (typeof visible !== "boolean") {
throw new Error("Model instance visible must be a boolean.");
}
if (typeof enabled !== "boolean") {
throw new Error("Model instance enabled must be a boolean.");
}
return { return {
id: overrides.id ?? createOpaqueId("model-instance"), id: overrides.id ?? createOpaqueId("model-instance"),
kind: "modelInstance", kind: "modelInstance",
assetId: overrides.assetId, assetId: overrides.assetId,
name: normalizeModelInstanceName(overrides.name), name: normalizeModelInstanceName(overrides.name),
visible,
enabled,
position, position,
rotationDegrees, rotationDegrees,
scale, scale,
@@ -178,6 +195,8 @@ export function areModelInstancesEqual(left: ModelInstance, right: ModelInstance
left.kind === right.kind && left.kind === right.kind &&
left.assetId === right.assetId && left.assetId === right.assetId &&
left.name === right.name && left.name === right.name &&
left.visible === right.visible &&
left.enabled === right.enabled &&
areVec3Equal(left.position, right.position) && areVec3Equal(left.position, right.position) &&
areVec3Equal(left.rotationDegrees, right.rotationDegrees) && areVec3Equal(left.rotationDegrees, right.rotationDegrees) &&
areVec3Equal(left.scale, right.scale) && areVec3Equal(left.scale, right.scale) &&

View File

@@ -122,6 +122,8 @@ export interface BoxBrush {
id: string; id: string;
kind: "box"; kind: "box";
name?: string; name?: string;
visible: boolean;
enabled: boolean;
center: Vec3; center: Vec3;
rotationDegrees: Vec3; rotationDegrees: Vec3;
size: Vec3; size: Vec3;
@@ -152,6 +154,9 @@ export const DEFAULT_BOX_BRUSH_ROTATION_DEGREES: Vec3 = {
z: 0 z: 0
}; };
export const DEFAULT_BOX_BRUSH_VISIBLE = true;
export const DEFAULT_BOX_BRUSH_ENABLED = true;
export const DEFAULT_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT = 6; export const DEFAULT_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT = 6;
export const MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT = 24; export const MAX_BOX_BRUSH_WATER_FOAM_CONTACT_LIMIT = 24;
@@ -482,7 +487,7 @@ export function cloneBoxBrushVolumeSettings(volume: BoxBrushVolumeSettings): Box
export function createBoxBrush( export function createBoxBrush(
overrides: Partial< overrides: Partial<
Pick<BoxBrush, "id" | "name" | "center" | "rotationDegrees" | "size" | "geometry" | "faces" | "volume" | "layerId" | "groupId"> Pick<BoxBrush, "id" | "name" | "visible" | "enabled" | "center" | "rotationDegrees" | "size" | "geometry" | "faces" | "volume" | "layerId" | "groupId">
> = {} > = {}
): BoxBrush { ): BoxBrush {
const center = cloneVec3(overrides.center ?? DEFAULT_BOX_BRUSH_CENTER); const center = cloneVec3(overrides.center ?? DEFAULT_BOX_BRUSH_CENTER);
@@ -490,15 +495,27 @@ export function createBoxBrush(
const fallbackSize = cloneVec3(overrides.size ?? DEFAULT_BOX_BRUSH_SIZE); const fallbackSize = cloneVec3(overrides.size ?? DEFAULT_BOX_BRUSH_SIZE);
const geometry = overrides.geometry === undefined ? createDefaultBoxBrushGeometry(fallbackSize) : cloneBoxBrushGeometry(overrides.geometry); const geometry = overrides.geometry === undefined ? createDefaultBoxBrushGeometry(fallbackSize) : cloneBoxBrushGeometry(overrides.geometry);
const size = deriveBoxBrushSizeFromGeometry(geometry); const size = deriveBoxBrushSizeFromGeometry(geometry);
const visible = overrides.visible ?? DEFAULT_BOX_BRUSH_VISIBLE;
const enabled = overrides.enabled ?? DEFAULT_BOX_BRUSH_ENABLED;
if (!hasPositiveBoxSize(size)) { if (!hasPositiveBoxSize(size)) {
throw new Error("Box brush size must remain positive on every axis."); throw new Error("Box brush size must remain positive on every axis.");
} }
if (typeof visible !== "boolean") {
throw new Error("Box brush visible must be a boolean.");
}
if (typeof enabled !== "boolean") {
throw new Error("Box brush enabled must be a boolean.");
}
return { return {
id: overrides.id ?? createOpaqueId("brush"), id: overrides.id ?? createOpaqueId("brush"),
kind: "box", kind: "box",
name: normalizeBrushName(overrides.name), name: normalizeBrushName(overrides.name),
visible,
enabled,
center, center,
rotationDegrees, rotationDegrees,
size, size,

View File

@@ -5,6 +5,8 @@ import { isHexColorString } from "../document/world-settings";
interface PositionedEntity { interface PositionedEntity {
id: string; id: string;
name?: string; name?: string;
visible: boolean;
enabled: boolean;
position: Vec3; position: Vec3;
} }
@@ -218,14 +220,14 @@ export interface InteractableEntity extends PositionedEntity {
kind: "interactable"; kind: "interactable";
radius: number; radius: number;
prompt: string; prompt: string;
enabled: boolean; interactionEnabled: boolean;
} }
export interface SceneExitEntity extends PositionedEntity { export interface SceneExitEntity extends PositionedEntity {
kind: "sceneExit"; kind: "sceneExit";
radius: number; radius: number;
prompt: string; prompt: string;
enabled: boolean; interactionEnabled: boolean;
targetSceneId: string; targetSceneId: string;
targetEntryEntityId: string; targetEntryEntityId: string;
} }
@@ -290,6 +292,9 @@ export const DEFAULT_ENTITY_POSITION: Vec3 = {
z: 0 z: 0
}; };
export const DEFAULT_ENTITY_VISIBLE = true;
export const DEFAULT_ENTITY_ENABLED = true;
export const DEFAULT_PLAYER_START_POSITION = DEFAULT_ENTITY_POSITION; export const DEFAULT_PLAYER_START_POSITION = DEFAULT_ENTITY_POSITION;
export const DEFAULT_PLAYER_START_YAW_DEGREES = 0; export const DEFAULT_PLAYER_START_YAW_DEGREES = 0;
export const DEFAULT_PLAYER_START_NAVIGATION_MODE: PlayerStartNavigationMode = export const DEFAULT_PLAYER_START_NAVIGATION_MODE: PlayerStartNavigationMode =
@@ -1062,6 +1067,20 @@ export function normalizeEntityName(name: string | null | undefined): string | u
return trimmedName.length === 0 ? undefined : trimmedName; return trimmedName.length === 0 ? undefined : trimmedName;
} }
function resolveAuthoredEntityVisibility(visible: boolean | undefined): boolean {
const resolvedVisible = visible ?? DEFAULT_ENTITY_VISIBLE;
assertBoolean(resolvedVisible, "Entity visible");
return resolvedVisible;
}
function resolveAuthoredEntityEnabled(enabled: boolean | undefined): boolean {
const resolvedEnabled = enabled ?? DEFAULT_ENTITY_ENABLED;
assertBoolean(resolvedEnabled, "Entity enabled");
return resolvedEnabled;
}
export function normalizeYawDegrees(yawDegrees: number): number { export function normalizeYawDegrees(yawDegrees: number): number {
const normalizedYaw = yawDegrees % 360; const normalizedYaw = yawDegrees % 360;
return normalizedYaw < 0 ? normalizedYaw + 360 : normalizedYaw; return normalizedYaw < 0 ? normalizedYaw + 360 : normalizedYaw;
@@ -1078,7 +1097,7 @@ export function normalizeInteractablePrompt(prompt: string): string {
} }
export function createPointLightEntity( export function createPointLightEntity(
overrides: Partial<Pick<PointLightEntity, "id" | "name" | "position" | "colorHex" | "intensity" | "distance">> = {} overrides: Partial<Pick<PointLightEntity, "id" | "name" | "visible" | "enabled" | "position" | "colorHex" | "intensity" | "distance">> = {}
): PointLightEntity { ): PointLightEntity {
const position = cloneVec3(overrides.position ?? DEFAULT_POINT_LIGHT_POSITION); const position = cloneVec3(overrides.position ?? DEFAULT_POINT_LIGHT_POSITION);
const colorHex = overrides.colorHex ?? DEFAULT_POINT_LIGHT_COLOR_HEX; const colorHex = overrides.colorHex ?? DEFAULT_POINT_LIGHT_COLOR_HEX;
@@ -1094,6 +1113,8 @@ export function createPointLightEntity(
id: overrides.id ?? createOpaqueId("entity-point-light"), id: overrides.id ?? createOpaqueId("entity-point-light"),
kind: "pointLight", kind: "pointLight",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
colorHex, colorHex,
intensity, intensity,
@@ -1102,7 +1123,7 @@ export function createPointLightEntity(
} }
export function createSpotLightEntity( export function createSpotLightEntity(
overrides: Partial<Pick<SpotLightEntity, "id" | "name" | "position" | "direction" | "colorHex" | "intensity" | "distance" | "angleDegrees">> = {} overrides: Partial<Pick<SpotLightEntity, "id" | "name" | "visible" | "enabled" | "position" | "direction" | "colorHex" | "intensity" | "distance" | "angleDegrees">> = {}
): SpotLightEntity { ): SpotLightEntity {
const position = cloneVec3(overrides.position ?? DEFAULT_SPOT_LIGHT_POSITION); const position = cloneVec3(overrides.position ?? DEFAULT_SPOT_LIGHT_POSITION);
const direction = cloneVec3(overrides.direction ?? DEFAULT_SPOT_LIGHT_DIRECTION); const direction = cloneVec3(overrides.direction ?? DEFAULT_SPOT_LIGHT_DIRECTION);
@@ -1126,6 +1147,8 @@ export function createSpotLightEntity(
id: overrides.id ?? createOpaqueId("entity-spot-light"), id: overrides.id ?? createOpaqueId("entity-spot-light"),
kind: "spotLight", kind: "spotLight",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
direction, direction,
colorHex, colorHex,
@@ -1139,7 +1162,7 @@ export function createPlayerStartEntity(
overrides: Partial< overrides: Partial<
Pick< Pick<
PlayerStartEntity, PlayerStartEntity,
"id" | "name" | "position" | "yawDegrees" | "navigationMode" "id" | "name" | "visible" | "enabled" | "position" | "yawDegrees" | "navigationMode"
> >
> & { > & {
movementTemplate?: PlayerStartMovementTemplateOverrides; movementTemplate?: PlayerStartMovementTemplateOverrides;
@@ -1173,6 +1196,8 @@ export function createPlayerStartEntity(
id: overrides.id ?? createOpaqueId("entity-player-start"), id: overrides.id ?? createOpaqueId("entity-player-start"),
kind: "playerStart", kind: "playerStart",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
yawDegrees: normalizeYawDegrees(yawDegrees), yawDegrees: normalizeYawDegrees(yawDegrees),
navigationMode, navigationMode,
@@ -1183,7 +1208,7 @@ export function createPlayerStartEntity(
} }
export function createSceneEntryEntity( export function createSceneEntryEntity(
overrides: Partial<Pick<SceneEntryEntity, "id" | "name" | "position" | "yawDegrees">> = {} overrides: Partial<Pick<SceneEntryEntity, "id" | "name" | "visible" | "enabled" | "position" | "yawDegrees">> = {}
): SceneEntryEntity { ): SceneEntryEntity {
const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION); const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION);
const yawDegrees = overrides.yawDegrees ?? DEFAULT_SCENE_ENTRY_YAW_DEGREES; const yawDegrees = overrides.yawDegrees ?? DEFAULT_SCENE_ENTRY_YAW_DEGREES;
@@ -1198,6 +1223,8 @@ export function createSceneEntryEntity(
id: overrides.id ?? createOpaqueId("entity-scene-entry"), id: overrides.id ?? createOpaqueId("entity-scene-entry"),
kind: "sceneEntry", kind: "sceneEntry",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
yawDegrees: normalizeYawDegrees(yawDegrees) yawDegrees: normalizeYawDegrees(yawDegrees)
}; };
@@ -1207,7 +1234,7 @@ export function createSoundEmitterEntity(
overrides: Partial< overrides: Partial<
Pick< Pick<
SoundEmitterEntity, SoundEmitterEntity,
"id" | "name" | "position" | "audioAssetId" | "volume" | "refDistance" | "maxDistance" | "autoplay" | "loop" "id" | "name" | "visible" | "enabled" | "position" | "audioAssetId" | "volume" | "refDistance" | "maxDistance" | "autoplay" | "loop"
> >
> = {} > = {}
): SoundEmitterEntity { ): SoundEmitterEntity {
@@ -1235,6 +1262,8 @@ export function createSoundEmitterEntity(
id: overrides.id ?? createOpaqueId("entity-sound-emitter"), id: overrides.id ?? createOpaqueId("entity-sound-emitter"),
kind: "soundEmitter", kind: "soundEmitter",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
audioAssetId, audioAssetId,
volume, volume,
@@ -1246,7 +1275,7 @@ export function createSoundEmitterEntity(
} }
export function createTriggerVolumeEntity( export function createTriggerVolumeEntity(
overrides: Partial<Pick<TriggerVolumeEntity, "id" | "name" | "position" | "size" | "triggerOnEnter" | "triggerOnExit">> = {} overrides: Partial<Pick<TriggerVolumeEntity, "id" | "name" | "visible" | "enabled" | "position" | "size" | "triggerOnEnter" | "triggerOnExit">> = {}
): TriggerVolumeEntity { ): TriggerVolumeEntity {
const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION); const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION);
const size = cloneVec3(overrides.size ?? DEFAULT_TRIGGER_VOLUME_SIZE); const size = cloneVec3(overrides.size ?? DEFAULT_TRIGGER_VOLUME_SIZE);
@@ -1262,6 +1291,8 @@ export function createTriggerVolumeEntity(
id: overrides.id ?? createOpaqueId("entity-trigger-volume"), id: overrides.id ?? createOpaqueId("entity-trigger-volume"),
kind: "triggerVolume", kind: "triggerVolume",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
size, size,
triggerOnEnter, triggerOnEnter,
@@ -1270,7 +1301,7 @@ export function createTriggerVolumeEntity(
} }
export function createTeleportTargetEntity( export function createTeleportTargetEntity(
overrides: Partial<Pick<TeleportTargetEntity, "id" | "name" | "position" | "yawDegrees">> = {} overrides: Partial<Pick<TeleportTargetEntity, "id" | "name" | "visible" | "enabled" | "position" | "yawDegrees">> = {}
): TeleportTargetEntity { ): TeleportTargetEntity {
const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION); const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION);
const yawDegrees = overrides.yawDegrees ?? DEFAULT_TELEPORT_TARGET_YAW_DEGREES; const yawDegrees = overrides.yawDegrees ?? DEFAULT_TELEPORT_TARGET_YAW_DEGREES;
@@ -1285,31 +1316,35 @@ export function createTeleportTargetEntity(
id: overrides.id ?? createOpaqueId("entity-teleport-target"), id: overrides.id ?? createOpaqueId("entity-teleport-target"),
kind: "teleportTarget", kind: "teleportTarget",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
yawDegrees: normalizeYawDegrees(yawDegrees) yawDegrees: normalizeYawDegrees(yawDegrees)
}; };
} }
export function createInteractableEntity( export function createInteractableEntity(
overrides: Partial<Pick<InteractableEntity, "id" | "name" | "position" | "radius" | "prompt" | "enabled">> = {} overrides: Partial<Pick<InteractableEntity, "id" | "name" | "visible" | "enabled" | "position" | "radius" | "prompt" | "interactionEnabled">> = {}
): InteractableEntity { ): InteractableEntity {
const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION); const position = cloneVec3(overrides.position ?? DEFAULT_ENTITY_POSITION);
const radius = overrides.radius ?? DEFAULT_INTERACTABLE_RADIUS; const radius = overrides.radius ?? DEFAULT_INTERACTABLE_RADIUS;
const prompt = normalizeInteractablePrompt(overrides.prompt ?? DEFAULT_INTERACTABLE_PROMPT); const prompt = normalizeInteractablePrompt(overrides.prompt ?? DEFAULT_INTERACTABLE_PROMPT);
const enabled = overrides.enabled ?? true; const interactionEnabled = overrides.interactionEnabled ?? true;
assertFiniteVec3(position, "Interactable position"); assertFiniteVec3(position, "Interactable position");
assertPositiveFiniteNumber(radius, "Interactable radius"); assertPositiveFiniteNumber(radius, "Interactable radius");
assertBoolean(enabled, "Interactable enabled"); assertBoolean(interactionEnabled, "Interactable interactionEnabled");
return { return {
id: overrides.id ?? createOpaqueId("entity-interactable"), id: overrides.id ?? createOpaqueId("entity-interactable"),
kind: "interactable", kind: "interactable",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
radius, radius,
prompt, prompt,
enabled interactionEnabled
}; };
} }
@@ -1319,10 +1354,12 @@ export function createSceneExitEntity(
SceneExitEntity, SceneExitEntity,
| "id" | "id"
| "name" | "name"
| "visible"
| "enabled"
| "position" | "position"
| "radius" | "radius"
| "prompt" | "prompt"
| "enabled" | "interactionEnabled"
| "targetSceneId" | "targetSceneId"
| "targetEntryEntityId" | "targetEntryEntityId"
> >
@@ -1333,7 +1370,7 @@ export function createSceneExitEntity(
const prompt = normalizeInteractablePrompt( const prompt = normalizeInteractablePrompt(
overrides.prompt ?? DEFAULT_SCENE_EXIT_PROMPT overrides.prompt ?? DEFAULT_SCENE_EXIT_PROMPT
); );
const enabled = overrides.enabled ?? true; const interactionEnabled = overrides.interactionEnabled ?? true;
const targetSceneId = normalizeSceneReferenceId( const targetSceneId = normalizeSceneReferenceId(
overrides.targetSceneId, overrides.targetSceneId,
"Scene Exit target scene id" "Scene Exit target scene id"
@@ -1345,16 +1382,18 @@ export function createSceneExitEntity(
assertFiniteVec3(position, "Scene Exit position"); assertFiniteVec3(position, "Scene Exit position");
assertPositiveFiniteNumber(radius, "Scene Exit radius"); assertPositiveFiniteNumber(radius, "Scene Exit radius");
assertBoolean(enabled, "Scene Exit enabled"); assertBoolean(interactionEnabled, "Scene Exit interactionEnabled");
return { return {
id: overrides.id ?? createOpaqueId("entity-scene-exit"), id: overrides.id ?? createOpaqueId("entity-scene-exit"),
kind: "sceneExit", kind: "sceneExit",
name: normalizeEntityName(overrides.name), name: normalizeEntityName(overrides.name),
visible: resolveAuthoredEntityVisibility(overrides.visible),
enabled: resolveAuthoredEntityEnabled(overrides.enabled),
position, position,
radius, radius,
prompt, prompt,
enabled, interactionEnabled,
targetSceneId, targetSceneId,
targetEntryEntityId targetEntryEntityId
}; };
@@ -1488,7 +1527,14 @@ export function cloneEntityRegistry(entities: Record<string, EntityInstance>): R
} }
export function areEntityInstancesEqual(left: EntityInstance, right: EntityInstance): boolean { export function areEntityInstancesEqual(left: EntityInstance, right: EntityInstance): boolean {
if (left.kind !== right.kind || left.id !== right.id || left.name !== right.name || !areVec3Equal(left.position, right.position)) { if (
left.kind !== right.kind ||
left.id !== right.id ||
left.name !== right.name ||
left.visible !== right.visible ||
left.enabled !== right.enabled ||
!areVec3Equal(left.position, right.position)
) {
return false; return false;
} }
@@ -1560,14 +1606,18 @@ export function areEntityInstancesEqual(left: EntityInstance, right: EntityInsta
} }
case "interactable": { case "interactable": {
const typedRight = right as InteractableEntity; const typedRight = right as InteractableEntity;
return left.radius === typedRight.radius && left.prompt === typedRight.prompt && left.enabled === typedRight.enabled; return (
left.radius === typedRight.radius &&
left.prompt === typedRight.prompt &&
left.interactionEnabled === typedRight.interactionEnabled
);
} }
case "sceneExit": { case "sceneExit": {
const typedRight = right as SceneExitEntity; const typedRight = right as SceneExitEntity;
return ( return (
left.radius === typedRight.radius && left.radius === typedRight.radius &&
left.prompt === typedRight.prompt && left.prompt === typedRight.prompt &&
left.enabled === typedRight.enabled && left.interactionEnabled === typedRight.interactionEnabled &&
left.targetSceneId === typedRight.targetSceneId && left.targetSceneId === typedRight.targetSceneId &&
left.targetEntryEntityId === typedRight.targetEntryEntityId left.targetEntryEntityId === typedRight.targetEntryEntityId
); );
@@ -1605,6 +1655,10 @@ export function getPrimaryPlayerStartEntity(entities: Record<string, EntityInsta
return getPlayerStartEntities(entities)[0] ?? null; return getPlayerStartEntities(entities)[0] ?? null;
} }
export function getPrimaryEnabledPlayerStartEntity(entities: Record<string, EntityInstance>): PlayerStartEntity | null {
return getPlayerStartEntities(entities).find((entity) => entity.enabled) ?? null;
}
export function getEntityKindLabel(kind: EntityKind): string { export function getEntityKindLabel(kind: EntityKind): string {
return getEntityRegistryEntry(kind).label; return getEntityRegistryEntry(kind).label;
} }