diff --git a/.eslintrc.js b/.eslintrc.js index b8cb6049..df0fd342 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -99,7 +99,7 @@ module.exports = { "@typescript-eslint/no-magic-numbers": [ "error", { - "ignore": [-1, 0, 1, 2], + "ignore": [-1, 0, 1, 2, "0n", "1n"], "ignoreEnums": true, "ignoreNumericLiteralTypes": true, "ignoreReadonlyClassProperties": true diff --git a/example/interface.css b/example/interface.css index 8fdb386d..6af53a74 100644 --- a/example/interface.css +++ b/example/interface.css @@ -62,6 +62,10 @@ input { border: dotted 1px #d0d0d0; } + input:disabled { + color: #808080; + } + button { width: 88px; } diff --git a/example/interface.html b/example/interface.html index 7a75c5e8..a02c6b40 100644 --- a/example/interface.html +++ b/example/interface.html @@ -184,6 +184,40 @@

Entity List

+ + +

Entity Editing

+ +
+ Add: + + + + +
+ +
+ Clone added: + + + + +
+ +
+ Can rez: + +
+ +
+ Can rez temp: + +
+ +
+ Can use private: + +

Message Mixer

diff --git a/example/interface.js b/example/interface.js index e36f0c3b..8fa855b7 100644 --- a/example/interface.js +++ b/example/interface.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer, MessageMixer, Vec3, Uuid } +import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer, MessageMixer, EntityType, Uuid, Vec3 } from "../dist/Vircadia.js"; (function () { @@ -639,6 +639,12 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer, const entityListBody = document.querySelector("#entityList > tbody"); let entityIDsList = []; + const addEntityButton = document.getElementById("addEntityButton"); + const addedEntityID = document.getElementById("addedEntityID"); + const canRezStatus = document.getElementById("canRezStatus"); + const canRezTempStatus = document.getElementById("canRezTempStatus"); + const canUsePrivateStatus = document.getElementById("canUsePrivateStatus"); + // Status @@ -646,16 +652,21 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer, function onStateChanged(state) { statusText.value = EntityServer.stateToString(state); - if (state === EntityServer.UNAVAILABLE || state === EntityServer.DISCONNECTED) { + const isConnected = state === EntityServer.CONNECTED; + if (!isConnected) { while (entityListBody.hasChildNodes()) { entityListBody.removeChild(entityListBody.firstChild); } entityIDsList = []; entitiesCount.value = 0; } + + canRezStatus.disabled = !isConnected; + canRezTempStatus.disabled = !isConnected; + canUsePrivateStatus.disabled = !isConnected; } - onStateChanged(entityServer.state); entityServer.onStateChanged = onStateChanged; + onStateChanged(entityServer.state); // Entity List @@ -715,6 +726,35 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer, entityServer.entityData.connect(onEntityData); + // Entity Editing + + addEntityButton.addEventListener("click", () => { + const entityID = entityServer.addEntity({ + entityType: EntityType.Shape + }); + addedEntityID.value = entityID.stringify(); + }); + + const onCanRezChanged = (canRez) => { + canRezStatus.value = canRez; + addEntityButton.disabled = !entityServer.canRez; + }; + entityServer.canRezChanged.connect(onCanRezChanged); + onCanRezChanged(entityServer.canRez); + + const onCanRezTempChanged = (canRezTemp) => { + canRezTempStatus.value = canRezTemp; + }; + entityServer.canRezTempChanged.connect(onCanRezTempChanged); + onCanRezTempChanged(entityServer.canRezTemp); + + const onCanGetAndSetPrivateUserDataChanged = (canGetAndSetPrivateUserData) => { + canUsePrivateStatus.value = canGetAndSetPrivateUserData; + }; + entityServer.canGetAndSetPrivateUserDataChanged.connect(onCanGetAndSetPrivateUserDataChanged); + onCanGetAndSetPrivateUserDataChanged(entityServer.canGetAndSetPrivateUserData); + + // Game Loop entityServerGameLoop = () => { diff --git a/package.json b/package.json index 79fcaaaa..d77833bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vircadia/web-sdk", - "version": "2023.1.2", + "version": "2023.2.0", "productName": "Vircadia Web SDK", "description": "Vircadia Web SDK for virtual worlds.", "author": "DigiSomni LLC | Vircadia Contributors", diff --git a/src/EntityServer.ts b/src/EntityServer.ts index 5850d4ea..483fcb38 100644 --- a/src/EntityServer.ts +++ b/src/EntityServer.ts @@ -9,17 +9,24 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import { HostType } from "./domain/entities/EntityItem"; +import { EntityType } from "./domain/entities/EntityTypes"; +import { EntityProperties } from "./domain/networking/packets/EntityData"; import PacketScribe from "./domain/networking/packets/PacketScribe"; +import PacketType, { PacketTypeValue } from "./domain/networking/udt/PacketHeaders"; import Node from "./domain/networking/Node"; import NodeList from "./domain/networking/NodeList"; import NodeType, { NodeTypeValue } from "./domain/networking/NodeType"; +import EntityEditPacketSender from "./domain/entities/EntityEditPacketSender"; import OctreeConstants from "./domain/octree/OctreeConstants"; import OctreePacketProcessor from "./domain/octree/OctreePacketProcessor"; import OctreeQuery from "./domain/octree/OctreeQuery"; import Camera from "./domain/shared/Camera"; import ContextManager from "./domain/shared/ContextManager"; import SignalEmitter, { Signal } from "./domain/shared/SignalEmitter"; +import Uuid from "./domain/shared/Uuid"; import AssignmentClient from "./domain/AssignmentClient"; +import { bigintReplacer, bigintReviver } from "./domain/shared/JSONExtensions"; /*@sdkdoc @@ -53,6 +60,17 @@ import AssignmentClient from "./domain/AssignmentClient"; * willing to handle. * @property {Signal} entityData - Triggered when new or changed entity data is received from the * entity server. + * @property {boolean} canRez - Whether the user has permissions to rez (create) persistent entities in the domain. + * @property {Signal} canRezChanged - Triggered when whether the user's permissions to rez (create) + * persistent entities changes in the domain. + * @property {boolean} canRezTemp - Whether the user has permissions to rez (create) temporary entities in the domain. + * @property {Signal} canRezTempChanged - Triggered when whether the user's permissions to rez + * (create) temporary entities changes in the domain. Temporary entities are entities with a finite lifetime + * property value set. + * @property {boolean} canGetAndSetPrivateUserData - Whether the user has permissions to get and set entities' + * privateUserData properties in the domain. + * @property {Signal} canGetAndSetPrivateUserDataChanged - Triggered when the + * user's permissions to get and set entities' privateUserData properties changes in the domain. */ class EntityServer extends AssignmentClient { @@ -99,9 +117,13 @@ class EntityServer extends AssignmentClient { #_octreeQuery = new OctreeQuery(true); #_octreeProcessor; #_maxOctreePPS = OctreeConstants.DEFAULT_MAX_OCTREE_PPS; + #_entityEditPacketSender; #_queryExpiry = 0; #_physicsEnabled = true; #_entityData = new SignalEmitter(); + #_canRezChanged = new SignalEmitter(); + #_canRezTempChanged = new SignalEmitter(); + #_canGetAndSetPrivateUserDataChanged = new SignalEmitter(); constructor(contextID: number) { @@ -117,6 +139,23 @@ class EntityServer extends AssignmentClient { this.#_entityData.emit(data); }); + ContextManager.set(contextID, EntityEditPacketSender, contextID); + this.#_entityEditPacketSender = ContextManager.get(contextID, EntityEditPacketSender) as EntityEditPacketSender; + + // C++ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) + this.#_nodeList.canRezChanged.connect((canRez: boolean) => { + this.#_canRezChanged.emit(canRez); + }); + this.#_nodeList.canRezTmpChanged.connect((canRezTmp: boolean) => { + this.#_canRezTempChanged.emit(canRezTmp); + }); + this.#_nodeList.canGetAndSetPrivateUserDataChanged.connect((canGetAndSetPrivateUserData: boolean) => { + this.#_canGetAndSetPrivateUserDataChanged.emit(canGetAndSetPrivateUserData); + }); + + // C++ OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packetSender = nullptr) + // Nothing to do here. + // C++ Application::Application() this.#_nodeList.nodeActivated.connect(this.#nodeActivated); this.#_nodeList.nodeKilled.connect(this.#nodeKilled); @@ -129,6 +168,22 @@ class EntityServer extends AssignmentClient { return this.#_maxOctreePPS; } + get canRez(): boolean { + // C++ bool EntityScriptingInterface::canRez() + return this.#_nodeList.getThisNodeCanRez(); + } + + get canRezTemp(): boolean { // Intentionally renamed from canRezTmp() in order to be more user-friendly. + // C++ bool EntityScriptingInterface::canRezTmp() + return this.#_nodeList.getThisNodeCanRezTmp(); + } + + get canGetAndSetPrivateUserData(): boolean { + // C++ bool EntityScriptingInterface::canGetAndSetPrivateUserData() + return this.#_nodeList.getThisNodeCanGetAndSetPrivateUserData(); + } + + /*@sdkdoc * Triggered when new or changed entity data is received from the entity server. * @callback EntityServer~entityData @@ -136,9 +191,45 @@ class EntityServer extends AssignmentClient { * properties are provided for both new and changed entities. */ get entityData(): Signal { + // C++ N/A return this.#_entityData.signal(); } + /*@sdkdoc + * Triggered when the user's permissions to rez (create) persistent entities changes in the domain. + * @callback EntityServer~canRezChanged + * @param {boolean} canRez - true if the user has permissions to rez persistent entities in the domain, + * false if the user doesn't. + */ + get canRezChanged(): Signal { + // C++ void EntityScriptingInterface::canRezChanged(bool canRez) + return this.#_canRezChanged.signal(); + } + + /*@sdkdoc + * Triggered when the user's permissions to rez (create) temporary entities changes in the domain. Temporary + * entities are entities with a finite lifetime property value set. + * @callback EntityServer~canRezTempChanged + * @param {boolean} canRezTemp - true if the user has permissions to rez temporary entities in the domain, + * false if the user doesn't. + */ + get canRezTempChanged(): Signal { + // C++ void EntityScriptingInterface::canRezTmpChanged(bool canRez) + return this.#_canRezTempChanged.signal(); + } + + /*@sdkdoc + * Triggered when the user's permissions to get and set entities' privateUserData properties changes in the + * domain. + * @callback EntityServer~canGetAndSetPrivateUserDataChanged + * @param {boolean} canGetAndSetPrivateUserdata - true if the user has permissions to get and set entities' + * privateUserData properties in the domain, false if the user doesn't. + */ + get canGetAndSetPrivateUserDataChanged(): Signal { + // C++ void EntityScriptingInterface::canGetAndSetPrivateUserDataChanged(bool canGetAndSetPrivateUserData) + return this.#_canGetAndSetPrivateUserDataChanged.signal(); + } + /*@sdkdoc * Game loop update method that should be called multiple times per second to keep the entity server up to date with user @@ -156,6 +247,96 @@ class EntityServer extends AssignmentClient { } } + /*@sdkdoc + * Adds a new entity to the entity server. + * @param {EntityProperties} properties - The properties of the entity to add. + * @param {HostType} [hostType=HostType.DOMAIN] - Where to host the entity. + *

Note: Currently only HostType.DOMAIN is supported.

+ * @returns {Uuid} The ID of the new entity if an add request was successfully sent to the server, or + * {@link Uuid(1)|Uuid.NULL} if no entity add request was made (invalid data or not connected). + */ + addEntity(properties: EntityProperties, hostType?: HostType): Uuid { + // C++ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, + // const QString& entityHostTypeString) + + if (typeof properties !== "object") { + console.error("[EntityServer] addEntity() called with invalid entity properties!"); + return new Uuid(); + } + + if (typeof properties.entityType !== "number" || properties.entityType <= EntityType.Unknown + || properties.entityType >= EntityType.NUM_TYPES) { + console.error("[EntityServer] addEntity() called with invalid entity type!"); + return new Uuid(); + } + + if (typeof hostType !== "undefined") { + if (typeof hostType !== "number" || hostType < HostType.DOMAIN || hostType > HostType.LOCAL) { + console.error("[EntityServer] addEntity() called with invalid entity hostType!"); + return new Uuid(); + } + if (hostType === HostType.AVATAR) { + console.error("[EntityServer] addEntity() for avatar entities not implemented!"); + return new Uuid(); + } + if (hostType === HostType.LOCAL) { + console.error("[EntityServer] addEntity() for local entities not implemented!"); + return new Uuid(); + } + } + + if ([EntityType.Line, EntityType.PolyLine, EntityType.PolyVox, EntityType.Grid, EntityType.Gizmo] + .includes(properties.entityType)) { + console.error("[EntityServer] addEntity() called with unsupported entity type!", properties.entityType); + return new Uuid(); + } + + return this.#addEntityInternal(properties, hostType ?? HostType.DOMAIN); + } + + /*@sdkdoc + * Edits an entity, changing one or more of its property values. + * @param {Uuid} entityID - The ID of the entity to edit. + * @param {Entities.EntityProperties} properties - The new property values. + * @returns {Uuid} The ID of the entity if an edit request was successfully sent to the server, or + * {@link Uuid(1)|Uuid.NULL} if no entity edit request was sent (invalid data or not connected). + */ + editEntity(entityID: Uuid, properties: EntityProperties): Uuid { + // C++ QUuid EntityScriptingInterface::editEntity(const QUuid& entityID, const EntityItemProperties& properties) + + if (!(entityID instanceof Uuid)) { + console.error("[EntityServer] editEntity() called with invalid entity ID!"); + return new Uuid(); + } + + if (typeof properties !== "object") { + console.error("[EntityServer] editEntity() called with invalid entity properties!"); + return new Uuid(); + } + + // Required properties. + // Required by EntityItemProperties.encodeEntityEditPacket() ... + if (typeof properties.entityType !== "number" || properties.entityType <= EntityType.Unknown + || properties.entityType >= EntityType.NUM_TYPES) { + console.error("[EntityServer] editEntity() called with invalid entity type value!"); + return new Uuid(); + } + if (typeof properties.lastEdited !== "bigint") { + console.error("[EntityServer] editEntity() called with invalid lastEdited value!"); + return new Uuid(); + } + + // Invalid properties are checked by when they are written to the packet. + + // WEBRTC TODO: Queue edit requests if not connected. + if (this.state !== EntityServer.CONNECTED) { + console.warn("[EntityServer] Could not send edit message because not connected."); + return new Uuid(); + } + + return this.#editEntityInternal(entityID, properties); + } + // Sends an EntityQuery packet to the entity server. #queryOctree(serverType: NodeTypeValue): void { @@ -186,6 +367,74 @@ class EntityServer extends AssignmentClient { } } + #addEntityInternal(properties: EntityProperties, entityHostType: HostType): Uuid { + // C++ QUuid EntityScriptingInterface::addEntityInternal(const EntityItemProperties& properties, + // entity::HostType entityHostType) + + // WEBRTC TODO: Address further C++ code - activity tracking. + + // WEBRTC TODO: Address further C++ code - avatar entities and local entities. + if (entityHostType === HostType.AVATAR) { + console.error("[EntityServer] addEntity() for avatar entities not implemented!"); + return new Uuid(Uuid.NULL); + } + if (entityHostType === HostType.LOCAL) { + console.error("[EntityServer] addEntity() for local entities not implemented!"); + return new Uuid(Uuid.NULL); + } + + // WEBRTC TODO: Address further C++ code - avatar entities. + + const propertiesWithSimID = JSON.parse(JSON.stringify(properties, bigintReplacer), bigintReviver) as EntityProperties; + propertiesWithSimID.entityHostType = entityHostType; + + // WEBRTC TODO: Address further C++ code - avatar entities and local entities. + + propertiesWithSimID.created = BigInt(Date.now()); + + const sessionID = this.#_nodeList.getSessionUUID(); + propertiesWithSimID.lastEditedBy = sessionID; + + // Don't use native client's "script semantics" for properties, use the server's. + + // Don't track whether the dimensions have been initialized, this is the client's responsibility. + + // WEBRTC TODO: Address synchronizing grab properties? + + // The SDK doesn't maintain a local entity tree so just create an entity ID. + const id = Uuid.createUuid(); + + this.#queueEntityMessage(PacketType.EntityAdd, id, propertiesWithSimID); + return id; + } + + #editEntityInternal(entityID: Uuid, properties: EntityProperties): Uuid { + // C++ QUuid EntityScriptingInterface::editEntity(const QUuid& entityID, const EntityItemProperties& properties) + + // WEBRTC TODO: Address further C++ code - activity tracking. + + const sessionID = this.#_nodeList.getSessionUUID(); + const propertiesWithSessionID + = JSON.parse(JSON.stringify(properties, bigintReplacer), bigintReviver) as EntityProperties; + properties.lastEditedBy = sessionID; + + // The SDK doesn't maintain a local entity tree so skip entity tree-related code. + + // The SDK doesn't support local positions and such script-side semantics. + + // WEBRTC TODO: Address synchronizing grab properties? + + this.#queueEntityMessage(PacketType.EntityEdit, entityID, propertiesWithSessionID); + return entityID; + } + + #queueEntityMessage(packetType: PacketTypeValue, entityID: Uuid, properties: EntityProperties): void { + // C++ void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, + // const EntityItemProperties& properties) + + this.#_entityEditPacketSender.queueEditEntityMessage(packetType, entityID, properties); + } + // Slot. #nodeActivated = (node: Node): void => { diff --git a/src/MessageMixer.ts b/src/MessageMixer.ts index a5234233..e1715faa 100644 --- a/src/MessageMixer.ts +++ b/src/MessageMixer.ts @@ -138,7 +138,6 @@ class MessageMixer extends AssignmentClient { return; } - this.#_messagesClient.sendMessage(channel, message, localOnly); } diff --git a/src/Vircadia.ts b/src/Vircadia.ts index 9043a620..510978d4 100644 --- a/src/Vircadia.ts +++ b/src/Vircadia.ts @@ -52,9 +52,15 @@ export { default as EntityServer } from "./EntityServer"; export { default as MessageMixer } from "./MessageMixer"; export type { AssignmentClientState } from "./domain/AssignmentClient"; +export { HostType } from "./domain/entities/EntityItem"; +export { EntityType } from "./domain/entities/EntityTypes"; + export { default as ModerationFlags } from "./domain/shared/ModerationFlags"; export type { BanFlagsValue } from "./domain/shared/ModerationFlags"; +export { default as Quat } from "./domain/shared/Quat"; +export type { quat } from "./domain/shared/Quat"; + export { default as SignalEmitter } from "./domain/shared/SignalEmitter"; export type { Signal, Slot } from "./domain/shared/SignalEmitter"; @@ -62,6 +68,3 @@ export { default as Uuid } from "./domain/shared/Uuid"; export { default as Vec3 } from "./domain/shared/Vec3"; export type { vec3 } from "./domain/shared/Vec3"; - -export { default as Quat } from "./domain/shared/Quat"; -export type { quat } from "./domain/shared/Quat"; diff --git a/src/domain/entities/AmbientLightPropertyGroup.ts b/src/domain/entities/AmbientLightPropertyGroup.ts index 76c1930d..6793dc2b 100644 --- a/src/domain/entities/AmbientLightPropertyGroup.ts +++ b/src/domain/entities/AmbientLightPropertyGroup.ts @@ -10,8 +10,9 @@ // import UDT from "../networking/udt/UDT"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; +import { ZoneEntityProperties } from "./ZoneEntityItem"; type AmbientLightProperties = { @@ -34,6 +35,12 @@ type AmbientLightPropertyGroupSubclassData = { class AmbientLightPropertyGroup { // C++ class AmbientLightPropertyGroup : public PropertyGroup + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ EntityPropertyFlags AmbientLightPropertyGroup::getChangedProperties() const + ["intensity", EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY], + ["url", EntityPropertyList.PROP_AMBIENT_LIGHT_URL] + ]); + /*@sdkdoc * Defines the ambient light in a zone. * @typedef {object} AmbientLightProperties @@ -61,7 +68,7 @@ class AmbientLightPropertyGroup { * read. */ static readEntitySubclassDataFromBuffer(data: DataView, position: number, - propertyFlags: PropertyFlags): AmbientLightPropertyGroupSubclassData { + propertyFlags: EntityPropertyFlags): AmbientLightPropertyGroupSubclassData { // C++ int AmbientLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -73,13 +80,13 @@ class AmbientLightPropertyGroup { const textDecoder = new TextDecoder(); let intensity: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_AMBIENT_LIGHT_INTENSITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY)) { intensity = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let url: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_AMBIENT_LIGHT_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -101,6 +108,65 @@ class AmbientLightPropertyGroup { /* eslint-enable @typescript-eslint/no-magic-numbers */ } + /*@devdoc + * Gets property flags for Zone ambientLight properties set in the entity properties object passed in, + * assuming that there may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity properties and values. + * @returns {EntityPropertyFlags} Flags for all the Zone ambientLight properties included in the entity + * properties object. + */ + static getChangedProperties(properties: ZoneEntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags getChangedProperties() const + const changedProperties = new EntityPropertyFlags(); + if (properties.ambientLight) { + const propertyNames = Object.keys(properties.ambientLight); + for (const propertyName of propertyNames) { + const propertyValue = AmbientLightPropertyGroup.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + } + return changedProperties; + } + + /*@devdoc + * Writes AnbientLightPropertyGroup properties to a buffer as are able to fit. + * @param {DataView} data - The buffer to write to. + * @param {number} dataPosition - The position to start writing at. + * @param {EntityProperties} entityProperties - A set of entity properties and values. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendToEditPacket(data: DataView, dataPosition: number, entityProperties: ZoneEntityProperties, + packetContext: OctreePacketContext): number { + // C++ bool appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& requestedProperties, + // EntityPropertyFlags & propertyFlags, EntityPropertyFlags& propertiesDidntFit, int& propertyCount, + // OctreeElement:: AppendState & appendState) const + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + let bytesWritten = 0; + const requestedProperties = packetContext.propertiesToWrite; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY, + entityProperties.ambientLight!.intensity!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL)) { + bytesWritten += OctreePacketData.appendStringValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_AMBIENT_LIGHT_URL, + entityProperties.ambientLight!.url!, packetContext); + } + + return bytesWritten; + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } + } export default AmbientLightPropertyGroup; diff --git a/src/domain/entities/AnimationPropertyGroup.ts b/src/domain/entities/AnimationPropertyGroup.ts new file mode 100644 index 00000000..7de2d515 --- /dev/null +++ b/src/domain/entities/AnimationPropertyGroup.ts @@ -0,0 +1,136 @@ +// +// AnimationPropertyGroup.ts +// +// Created by David Rowe on 27 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import UDT from "../networking/udt/UDT"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; +import { ModelEntityProperties } from "./ModelEntityItem"; + + +/*@devdoc + * The AnimationPropertyGroup class provides facilities for handling grab properties of an entity. + *

C++: class AnimationPropertyGroup : public PropertyGroup

+ * @class AnimationPropertyGroup + * @property Map PROPERTY_MAP - Maps grab property group names to {@link EntityPropertyList} values. + */ +class AnimationPropertyGroup { + // C++ class AnimationPropertyGroup : public PropertyGroup + + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const + ["url", EntityPropertyList.PROP_ANIMATION_URL], + ["allowTranslation", EntityPropertyList.PROP_ANIMATION_ALLOW_TRANSLATION], + ["fps", EntityPropertyList.PROP_ANIMATION_FPS], + ["currentFrame", EntityPropertyList.PROP_ANIMATION_FRAME_INDEX], + ["running", EntityPropertyList.PROP_ANIMATION_PLAYING], + ["loop", EntityPropertyList.PROP_ANIMATION_LOOP], + ["firstFrame", EntityPropertyList.PROP_ANIMATION_FIRST_FRAME], + ["lastFrame", EntityPropertyList.PROP_ANIMATION_LAST_FRAME], + ["hold", EntityPropertyList.PROP_ANIMATION_HOLD] + ]); + + /*@devdoc + * Gets property flags for Model animation properties set in the entity properties object passed in, assuming + * that there may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity properties and values. + * @returns {EntityPropertyFlags} Flags for all the Model animation properties included in the entity + * properties object. + */ + static getChangedProperties(properties: ModelEntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags getChangedProperties() const + const changedProperties = new EntityPropertyFlags(); + if (properties.animation) { + const propertyNames = Object.keys(properties.animation); + for (const propertyName of propertyNames) { + const propertyValue = AnimationPropertyGroup.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + } + return changedProperties; + } + + /*@devdoc + * Writes AnimationPropertyGroup properties to a buffer as are able to fit. + * @param {DataView} data - The buffer to write to. + * @param {number} dataPosition - The position to start writing at. + * @param {EntityProperties} entityProperties - A set of entity properties and values. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendToEditPacket(data: DataView, dataPosition: number, entityProperties: ModelEntityProperties, + packetContext: OctreePacketContext): number { + // C++ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData, + // EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, + // EntityPropertyFlags& propertiesDidntFit, int& propertyCount, OctreeElement::AppendState& appendState) const + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + let bytesWritten = 0; + const requestedProperties = packetContext.propertiesToWrite; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)) { + bytesWritten += OctreePacketData.appendStringValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_URL, + entityProperties.animation!.url!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_ALLOW_TRANSLATION)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_ALLOW_TRANSLATION, + entityProperties.animation!.allowTranslation!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_FPS)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_FPS, + entityProperties.animation!.fps!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_FRAME_INDEX)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_FRAME_INDEX, + entityProperties.animation!.currentFrame!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_PLAYING)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_PLAYING, + entityProperties.animation!.running!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_LOOP, + entityProperties.animation!.loop!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_FIRST_FRAME)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_FIRST_FRAME, + entityProperties.animation!.firstFrame!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_LAST_FRAME)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_LAST_FRAME, + entityProperties.animation!.lastFrame!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_ANIMATION_HOLD, + entityProperties.animation!.hold!, packetContext); + } + + return bytesWritten; + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } + +} + +export default AnimationPropertyGroup; diff --git a/src/domain/entities/BloomPropertyGroup.ts b/src/domain/entities/BloomPropertyGroup.ts index cd7f88d6..28c517ef 100644 --- a/src/domain/entities/BloomPropertyGroup.ts +++ b/src/domain/entities/BloomPropertyGroup.ts @@ -10,8 +10,9 @@ // import UDT from "../networking/udt/UDT"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; +import { ZoneEntityProperties } from "./ZoneEntityItem"; type BloomProperties = { @@ -34,6 +35,14 @@ type BloomPropertyGroupSubclassData = { class BloomPropertyGroup { // C++ class BloomPropertyGroup : public PropertyGroup + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ EntityPropertyFlags BloomPropertyGroup::getChangedProperties() const + ["intensity", EntityPropertyList.PROP_BLOOM_INTENSITY], + ["threshold", EntityPropertyList.PROP_BLOOM_THRESHOLD], + ["size", EntityPropertyList.PROP_BLOOM_SIZE] + ]); + + /*@sdkdoc * Defines the bloom in a zone. * @typedef {object} BloomProperties @@ -59,7 +68,7 @@ class BloomPropertyGroup { * @returns {BloomPropertyGroupSubclassData} The Zone entity's bloom properties and the number of bytes read. */ static readEntitySubclassDataFromBuffer(data: DataView, position: number, - propertyFlags: PropertyFlags): BloomPropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len + propertyFlags: EntityPropertyFlags): BloomPropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int BloomPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -69,19 +78,19 @@ class BloomPropertyGroup { let dataPosition = position; let intensity: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BLOOM_INTENSITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY)) { intensity = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let threshold: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BLOOM_THRESHOLD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BLOOM_THRESHOLD)) { threshold = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let size: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BLOOM_SIZE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BLOOM_SIZE)) { size = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -98,6 +107,70 @@ class BloomPropertyGroup { /* eslint-enable @typescript-eslint/no-magic-numbers */ } + /*@devdoc + * Gets property flags for Zone bloom properties set in the entity properties object passed in, + * assuming that there may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity properties and values. + * @returns {EntityPropertyFlags} Flags for all the Zone bloom properties included in the entity + * properties object. + */ + static getChangedProperties(properties: ZoneEntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags getChangedProperties() const + const changedProperties = new EntityPropertyFlags(); + if (properties.bloom) { + const propertyNames = Object.keys(properties.bloom); + for (const propertyName of propertyNames) { + const propertyValue = BloomPropertyGroup.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + } + return changedProperties; + } + + /*@devdoc + * Writes BloomPropertyGroup properties to a buffer as are able to fit. + * @param {DataView} data - The buffer to write to. + * @param {number} dataPosition - The position to start writing at. + * @param {EntityProperties} entityProperties - A set of entity properties and values. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendToEditPacket(data: DataView, dataPosition: number, entityProperties: ZoneEntityProperties, + packetContext: OctreePacketContext): number { + // C++ bool appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& requestedProperties, + // EntityPropertyFlags & propertyFlags, EntityPropertyFlags& propertiesDidntFit, int& propertyCount, + // OctreeElement:: AppendState & appendState) const + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + let bytesWritten = 0; + const requestedProperties = packetContext.propertiesToWrite; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_BLOOM_INTENSITY, + entityProperties.bloom!.intensity!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BLOOM_THRESHOLD)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_BLOOM_THRESHOLD, + entityProperties.bloom!.threshold!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BLOOM_SIZE)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_BLOOM_SIZE, + entityProperties.bloom!.size!, UDT.LITTLE_ENDIAN, packetContext); + } + + return bytesWritten; + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } + } export default BloomPropertyGroup; diff --git a/src/domain/entities/EntityEditPacketSender.ts b/src/domain/entities/EntityEditPacketSender.ts new file mode 100644 index 00000000..aa5d5208 --- /dev/null +++ b/src/domain/entities/EntityEditPacketSender.ts @@ -0,0 +1,139 @@ +// +// EntityEditPacketSender.js +// +// Created by David Rowe on 19 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { HostType } from "../entities/EntityItem"; +import EntityPropertyFlags, { EntityPropertyList } from "../entities/EntityPropertyFlags"; +import { EntityProperties } from "../networking/packets/EntityData"; +import PacketScribe from "../networking/packets/PacketScribe"; +import { PacketTypeValue } from "../networking/udt/PacketHeaders"; +import NLPacket from "../networking/NLPacket"; +import NodeList from "../networking/NodeList"; +import OctreeEditPacketSender from "../octree/OctreeEditPacketSender"; +import ContextManager from "../shared/ContextManager"; +import { bigintReplacer, bigintReviver } from "../shared/JSONExtensions"; +import Uuid from "../shared/Uuid"; +import EntityItemProperties from "./EntityItemProperties"; + + +/*@devdoc + * The EntityEditPacketSender class handles preparing and sending entity edit packets to the entity server. + *

C++: class EntityEditPacketSender : public OctreeEditPacketSender

+ * + * @class EntityEditPacketSender + * @param {number} contextID - The {@link ContextManager} context ID. + */ +class EntityEditPacketSender extends OctreeEditPacketSender { + // C++ class EntityEditPacketSender : public OctreeEditPacketSender + + static override readonly contextItemType = "EntityEditPacketSender"; + + + // Context + #_nodeList; + + + constructor(contextID: number) { + super(contextID); + + // Context + this.#_nodeList = ContextManager.get(contextID, NodeList) as NodeList; + } + + + /*@devdoc + * Sends an entity edit to the entity server as an {@link NLPacket} or {@link NLPacketList}. + *

Note: The edit is sent reliably whereas the C++ sends it unreliably. The C++ can send it unreliably because it + * maintains its own copy of the entity tree

+ * @param {PacketTypeValue} type - The entity edit packet type. + * @param {Uuid} entityItemID - The entity ID. + * @param {EntityProperties} properties - The properties to include in the entity edit. + */ + queueEditEntityMessage(type: PacketTypeValue, entityItemID: Uuid, properties: EntityProperties): void { + // C++ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, + // EntityItemID entityItemID, const EntityItemProperties& properties) + + if (properties.entityHostType === HostType.AVATAR) { + + // WEBRTC TODO: Address further C++ code - avatar entities. + + console.error("[EntityEditPacketSender] queueEditEntityMessage() for avatar entities not implemented!"); + return; + } + if (properties.entityHostType === HostType.LOCAL) { + // Don't send edits for local entities + return; + } + + // WEBRTC TODO: Address further C++ code - serverless domains. + + let didntFitProperties = new EntityPropertyFlags(); + const propertiesCopy = JSON.parse(JSON.stringify(properties, bigintReplacer), bigintReviver) as EntityProperties; + + if (properties.parentID && properties.parentID.value() === Uuid.AVATAR_SELF_ID) { + const myNodeID = this.#_nodeList.getSessionUUID(); + propertiesCopy.parentID = myNodeID; + } + + let requestedProperties = EntityItemProperties.getChangedProperties(propertiesCopy); + + if (!this.#_nodeList.getThisNodeCanGetAndSetPrivateUserData() + && requestedProperties.getHasProperty(EntityPropertyList.PROP_PRIVATE_USER_DATA)) { + requestedProperties.setHasProperty(EntityPropertyList.PROP_PRIVATE_USER_DATA, false); + } + + let packetType = type; + /* + let nlPacketList: NLPacketList | null = null; + */ + let nlPacket: NLPacket | null = null; + + let packetOverflow = false; + while (!requestedProperties.isEmpty() && !packetOverflow) { + // Create NLPacketList / NLPacket here rather than in queueOctreeEditMessage(). + const entityOperationDetails = { + entityID: entityItemID, + properties: propertiesCopy, + requestedProperties, + didntFitProperties + }; + if (packetType === PacketTypeValue.EntityAdd) { + // WEBRTC TODO: Implement EntityAdd. + /* + nlPacketList = PacketScribe.EntityAdd.write(entityOperationDetails); // Creates a reliable packet. + this.queueOctreeEditMessage(type, nlPacketList); + */ + } else { + switch (packetType) { + case PacketTypeValue.EntityEdit: + nlPacket = PacketScribe.EntityEdit.write(entityOperationDetails); // Creates a reliable packet. + break; + default: + // WEBRTC TODO: Address further packet types. + console.error("[EntityEditPacketSender] Not implemented for packet type!", packetType); + return; + } + if (nlPacket) { + this.queueOctreeEditMessage(/* type, */ nlPacket); + } else { + console.warn("[networking] Some properties didn't fit writing an EntityEdit packet - packet not sent.", + entityOperationDetails.entityID.stringify()); + packetOverflow = true; + } + } + + packetType = PacketTypeValue.EntityEdit; + requestedProperties = didntFitProperties; + didntFitProperties = new EntityPropertyFlags(); + } + } +} + +export default EntityEditPacketSender; diff --git a/src/domain/entities/EntityItem.ts b/src/domain/entities/EntityItem.ts new file mode 100644 index 00000000..d26e3394 --- /dev/null +++ b/src/domain/entities/EntityItem.ts @@ -0,0 +1,33 @@ +// +// EntityItem.ts +// +// Created by David Rowe on 19 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/*@sdkdoc + * The HostType namespace enumerates how an entity is hosted and sent to others for display. + *

C++: HostType

+ * @namespace HostType + * @property {number} DOMAIN - 0 - Domain entities are stored on the domain, are visible to everyone, and are sent + * to everyone by the entity server. + * @property {number} AVATAR - 1 - Local entities are ephemeral — they aren't stored anywhere — and are visible + * only to the client. They follow the client to each domain visited, displaying at the same domain coordinates unless + * parented to the client's avatar. Additionally, local entities are always collisionless. + * @property {number} LOCAL - 2 - Avatar entities are stored on an Interface client, are visible to everyone, and + * are sent to everyone by the avatar mixer. They follow the client to each domain visited, displaying at the same domain + * coordinates unless parented to the client's avatar. + */ +enum HostType { + // C++ enum class HostType + + DOMAIN = 0, + AVATAR, + LOCAL +} + +export { HostType }; diff --git a/src/domain/entities/EntityItemProperties.ts b/src/domain/entities/EntityItemProperties.ts new file mode 100644 index 00000000..d8673ffa --- /dev/null +++ b/src/domain/entities/EntityItemProperties.ts @@ -0,0 +1,1249 @@ +// +// EntityItemProperties.ts +// +// Created by David Rowe on 26 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { EntityProperties } from "../networking/packets/EntityData"; +import UDT from "../networking/udt/UDT"; +import MessageData from "../networking/MessageData"; +import { AppendState } from "../octree/OctreeElement"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; +import assert from "../shared/assert"; +import ByteCountCoded from "../shared/ByteCountCoded"; +import Uuid from "../shared/Uuid"; +import AmbientLightPropertyGroup from "./AmbientLightPropertyGroup"; +import AnimationPropertyGroup from "./AnimationPropertyGroup"; +import BloomPropertyGroup from "./BloomPropertyGroup"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; +import { EntityType } from "./EntityTypes"; +import GrabPropertyGroup from "./GrabPropertyGroup"; +import HazePropertyGroup from "./HazePropertyGroup"; +import { ImageEntityProperties } from "./ImageEntityItem"; +import KeyLightPropertyGroup from "./KeyLightPropertyGroup"; +import { LightEntityProperties } from "./LightEntityItem"; +import { MaterialEntityProperties } from "./MaterialEntityItem"; +import { ModelEntityProperties } from "./ModelEntityItem"; +import { ParticleEffectEntityProperties } from "./ParticleEffectEntityItem"; +import { ShapeEntityProperties } from "./ShapeEntityItem"; +import SkyboxPropertyGroup from "./SkyboxPropertyGroup"; +import { TextEntityProperties, TextAlignment, TextEffect } from "./TextEntityItem"; +import { WebEntityProperties } from "./WebEntityItem"; +import { ZoneEntityProperties } from "./ZoneEntityItem"; + + +/*@devdoc + * The EntityItemProperties class provides methods for working with entity item properties. + *

C++: EntityItemProperties

+ * @class EntityItemProperties + */ +class EntityItemProperties { + // C++: class EntityItemProperties + + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ EntityPropertyFlags EntityItemProperties::getChangedProperties() const + + // Core + ["simulationOwner", EntityPropertyList.PROP_SIMULATION_OWNER], + ["parentID", EntityPropertyList.PROP_PARENT_ID], + ["parentJointIndex", EntityPropertyList.PROP_PARENT_JOINT_INDEX], + ["visible", EntityPropertyList.PROP_VISIBLE], + ["name", EntityPropertyList.PROP_NAME], + ["locked", EntityPropertyList.PROP_LOCKED], + ["userData", EntityPropertyList.PROP_USER_DATA], + ["privateUserData", EntityPropertyList.PROP_PRIVATE_USER_DATA], + ["href", EntityPropertyList.PROP_HREF], + ["description", EntityPropertyList.PROP_DESCRIPTION], + ["position", EntityPropertyList.PROP_POSITION], + ["dimensions", EntityPropertyList.PROP_DIMENSIONS], + ["rotation", EntityPropertyList.PROP_ROTATION], + ["registrationPoint", EntityPropertyList.PROP_REGISTRATION_POINT], + ["created", EntityPropertyList.PROP_CREATED], + ["lastEditedBy", EntityPropertyList.PROP_LAST_EDITED_BY], + ["entityHostType", EntityPropertyList.PROP_ENTITY_HOST_TYPE], + ["owningAvatarID", EntityPropertyList.PROP_OWNING_AVATAR_ID], + ["queryAACube", EntityPropertyList.PROP_QUERY_AA_CUBE], + ["canCastShadow", EntityPropertyList.PROP_CAN_CAST_SHADOW], + ["isVisibleInSecondaryCamera", EntityPropertyList.PROP_VISIBLE_IN_SECONDARY_CAMERA], + ["renderLayer", EntityPropertyList.PROP_RENDER_LAYER], + ["primitiveMode", EntityPropertyList.PROP_PRIMITIVE_MODE], + ["ignorePickIntersection", EntityPropertyList.PROP_IGNORE_PICK_INTERSECTION], + ["renderWithZones", EntityPropertyList.PROP_RENDER_WITH_ZONES], + ["billboardMode", EntityPropertyList.PROP_BILLBOARD_MODE], + // GrabPropertyGroup.PROPERTY_MAP, // These are handled in GrabPropertyGroup. + + // Physics + ["density", EntityPropertyList.PROP_DENSITY], + ["velocity", EntityPropertyList.PROP_VELOCITY], + ["angularVelocity", EntityPropertyList.PROP_ANGULAR_VELOCITY], + ["gravity", EntityPropertyList.PROP_GRAVITY], + ["acceleration", EntityPropertyList.PROP_ACCELERATION], + ["damping", EntityPropertyList.PROP_DAMPING], + ["angularDamping", EntityPropertyList.PROP_ANGULAR_DAMPING], + ["restitution", EntityPropertyList.PROP_RESTITUTION], + ["friction", EntityPropertyList.PROP_FRICTION], + ["lifetime", EntityPropertyList.PROP_LIFETIME], + ["collisionless", EntityPropertyList.PROP_COLLISIONLESS], + ["collisionMask", EntityPropertyList.PROP_COLLISION_MASK], + ["dynamic", EntityPropertyList.PROP_DYNAMIC], + ["collisionSoundURL", EntityPropertyList.PROP_COLLISION_SOUND_URL], + ["actionData", EntityPropertyList.PROP_ACTION_DATA], + + // Cloning + ["cloneable", EntityPropertyList.PROP_CLONEABLE], + ["cloneLifetime", EntityPropertyList.PROP_CLONE_LIFETIME], + ["cloneLimit", EntityPropertyList.PROP_CLONE_LIMIT], + ["cloneDynamic", EntityPropertyList.PROP_CLONE_DYNAMIC], + ["cloneAvatarEntity", EntityPropertyList.PROP_CLONE_AVATAR_ENTITY], + ["cloneOriginID", EntityPropertyList.PROP_CLONE_ORIGIN_ID], + + // Scripts + ["script", EntityPropertyList.PROP_SCRIPT], + ["scriptTimestamp", EntityPropertyList.PROP_SCRIPT_TIMESTAMP], + ["serverScripts", EntityPropertyList.PROP_SERVER_SCRIPTS], + + // Certifiable Properties + ["itemName", EntityPropertyList.PROP_ITEM_NAME], + ["itemDescription", EntityPropertyList.PROP_ITEM_DESCRIPTION], + ["itemCategories", EntityPropertyList.PROP_ITEM_CATEGORIES], + ["itemArtist", EntityPropertyList.PROP_ITEM_ARTIST], + ["itemLicense", EntityPropertyList.PROP_ITEM_LICENSE], + ["limitedRun", EntityPropertyList.PROP_LIMITED_RUN], + ["marketplaceID", EntityPropertyList.PROP_MARKETPLACE_ID], + ["editionNumber", EntityPropertyList.PROP_EDITION_NUMBER], + ["entityInstanceNumber", EntityPropertyList.PROP_ENTITY_INSTANCE_NUMBER], + ["certificateID", EntityPropertyList.PROP_CERTIFICATE_ID], + ["certificateType", EntityPropertyList.PROP_CERTIFICATE_TYPE], + ["staticCertificateVersion", EntityPropertyList.PROP_STATIC_CERTIFICATE_VERSION], + + // Location data for scripts + ["localPosition", EntityPropertyList.PROP_LOCAL_POSITION], + ["localRotation", EntityPropertyList.PROP_LOCAL_ROTATION], + ["localVelocity", EntityPropertyList.PROP_LOCAL_VELOCITY], + ["localAngularVelocity", EntityPropertyList.PROP_LOCAL_ANGULAR_VELOCITY], + ["localDimensions", EntityPropertyList.PROP_LOCAL_DIMENSIONS], + + // Common + ["shapeType", EntityPropertyList.PROP_SHAPE_TYPE], + ["compoundShapeURL", EntityPropertyList.PROP_COMPOUND_SHAPE_URL], + ["color", EntityPropertyList.PROP_COLOR], + ["alpha", EntityPropertyList.PROP_ALPHA], + // PulsePropertyGroup.PROPERTY_MAP, // Pulse properties are deprecated and aren't implemented in the Web SDK. + ["textures", EntityPropertyList.PROP_TEXTURES], + + // Particles + ["maxParticles", EntityPropertyList.PROP_MAX_PARTICLES], + ["lifespan", EntityPropertyList.PROP_LIFESPAN], + ["isEmitting", EntityPropertyList.PROP_EMITTING_PARTICLES], + ["emitRate", EntityPropertyList.PROP_EMIT_RATE], + ["emitSpeed", EntityPropertyList.PROP_EMIT_SPEED], + ["speedSpread", EntityPropertyList.PROP_SPEED_SPREAD], + ["emitOrientation", EntityPropertyList.PROP_EMIT_ORIENTATION], + ["emitDimensions", EntityPropertyList.PROP_EMIT_DIMENSIONS], + ["emitRadiusStart", EntityPropertyList.PROP_EMIT_RADIUS_START], + ["polarStart", EntityPropertyList.PROP_POLAR_START], + ["polarFinish", EntityPropertyList.PROP_POLAR_FINISH], + ["azimuthStart", EntityPropertyList.PROP_AZIMUTH_START], + ["azimuthFinish", EntityPropertyList.PROP_AZIMUTH_FINISH], + ["emitAcceleration", EntityPropertyList.PROP_EMIT_ACCELERATION], + ["accelerationSpread", EntityPropertyList.PROP_ACCELERATION_SPREAD], + ["particleRadius", EntityPropertyList.PROP_PARTICLE_RADIUS], + ["radiusSpread", EntityPropertyList.PROP_RADIUS_SPREAD], + ["radiusStart", EntityPropertyList.PROP_RADIUS_START], + ["radiusFinish", EntityPropertyList.PROP_RADIUS_FINISH], + ["colorSpread", EntityPropertyList.PROP_COLOR_SPREAD], + ["colorStart", EntityPropertyList.PROP_COLOR_START], + ["colorFinish", EntityPropertyList.PROP_COLOR_FINISH], + ["alphaSpread", EntityPropertyList.PROP_ALPHA_SPREAD], + ["alphaStart", EntityPropertyList.PROP_ALPHA_START], + ["alphaFinish", EntityPropertyList.PROP_ALPHA_FINISH], + ["emitterShouldTrail", EntityPropertyList.PROP_EMITTER_SHOULD_TRAIL], + ["particleSpin", EntityPropertyList.PROP_PARTICLE_SPIN], + ["spinSpread", EntityPropertyList.PROP_SPIN_SPREAD], + ["spinStart", EntityPropertyList.PROP_SPIN_START], + ["spinFinish", EntityPropertyList.PROP_SPIN_FINISH], + ["rotateWithEntity", EntityPropertyList.PROP_PARTICLE_ROTATE_WITH_ENTITY], + + // Model + ["modelURL", EntityPropertyList.PROP_MODEL_URL], + ["modelScale", EntityPropertyList.PROP_MODEL_SCALE], + ["jointRotationsSet", EntityPropertyList.PROP_JOINT_ROTATIONS_SET], + ["jointRotations", EntityPropertyList.PROP_JOINT_ROTATIONS], + ["jointTranslationsSet", EntityPropertyList.PROP_JOINT_TRANSLATIONS_SET], + ["jointTranslations", EntityPropertyList.PROP_JOINT_TRANSLATIONS], + ["relayParentJoints", EntityPropertyList.PROP_RELAY_PARENT_JOINTS], + ["groupCulled", EntityPropertyList.PROP_GROUP_CULLED], + ["blendshapeCoefficiengts", EntityPropertyList.PROP_BLENDSHAPE_COEFFICIENTS], + ["useOriginalPivot", EntityPropertyList.PROP_USE_ORIGINAL_PIVOT], + // AnimationPropertyGroup.PROPERTY_MAP, // These are handled in AnimationPropertyGroup. + + // Light + ["isSpotlight", EntityPropertyList.PROP_IS_SPOTLIGHT], + ["intensity", EntityPropertyList.PROP_INTENSITY], + ["exponent", EntityPropertyList.PROP_EXPONENT], + ["cutoff", EntityPropertyList.PROP_CUTOFF], + ["falloffRadius", EntityPropertyList.PROP_FALLOFF_RADIUS], + + // Text + ["text", EntityPropertyList.PROP_TEXT], + ["lineHeight", EntityPropertyList.PROP_LINE_HEIGHT], + ["textColor", EntityPropertyList.PROP_TEXT_COLOR], + ["textAlpha", EntityPropertyList.PROP_TEXT_ALPHA], + ["backgroundColor", EntityPropertyList.PROP_BACKGROUND_COLOR], + ["backgroundAlpha", EntityPropertyList.PROP_BACKGROUND_ALPHA], + ["leftMargin", EntityPropertyList.PROP_LEFT_MARGIN], + ["rightMargin", EntityPropertyList.PROP_RIGHT_MARGIN], + ["topMargin", EntityPropertyList.PROP_TOP_MARGIN], + ["bottomMargin", EntityPropertyList.PROP_BOTTOM_MARGIN], + ["unlit", EntityPropertyList.PROP_UNLIT], + ["font", EntityPropertyList.PROP_FONT], + ["textEffect", EntityPropertyList.PROP_TEXT_EFFECT], + ["textEffectColor", EntityPropertyList.PROP_TEXT_EFFECT_COLOR], + ["textEffectThickness", EntityPropertyList.PROP_TEXT_EFFECT_THICKNESS], + ["alignment", EntityPropertyList.PROP_TEXT_ALIGNMENT], + + // Zone + // KeyLightPropertyGroup.PROPERTY_MAP, // These are handled in KeyLightPropertyGroup. + // AmbientLightPropertyGroup.PROPERTY_MAP, // These are handled in AmbientLightPropertyGroup. + // SkyboxPropertyGroup.PROPERTY_MAP, // These are handled in SkyboxPropertyGroup. + // HazePropertyGroup.PROPERTY_MAP, // These are handled in HazePropertyGroup. + // BloomPropertyGroup.PROPERTY_MAP, // These are handled in BloomPropertyGroup. + ["flyingAllowed", EntityPropertyList.PROP_FLYING_ALLOWED], + ["ghostingAllowed", EntityPropertyList.PROP_GHOSTING_ALLOWED], + ["filterURL", EntityPropertyList.PROP_FILTER_URL], + ["keyLightMode", EntityPropertyList.PROP_KEY_LIGHT_MODE], + ["ambientLightMode", EntityPropertyList.PROP_AMBIENT_LIGHT_MODE], + ["skyboxMode", EntityPropertyList.PROP_SKYBOX_MODE], + ["hazeMode", EntityPropertyList.PROP_HAZE_MODE], + ["bloomMode", EntityPropertyList.PROP_BLOOM_MODE], + ["avatarPriority", EntityPropertyList.PROP_AVATAR_PRIORITY], + ["screenshare", EntityPropertyList.PROP_SCREENSHARE], + + // Polyvox + ["voxelVolumeSize", EntityPropertyList.PROP_VOXEL_VOLUME_SIZE], + ["voxelData", EntityPropertyList.PROP_VOXEL_DATA], + ["voxelSurfaceStyle", EntityPropertyList.PROP_VOXEL_SURFACE_STYLE], + ["xTextureURL", EntityPropertyList.PROP_X_TEXTURE_URL], + ["yTextureURL", EntityPropertyList.PROP_Y_TEXTURE_URL], + ["zTextureURL", EntityPropertyList.PROP_Z_TEXTURE_URL], + ["xNNeighborID", EntityPropertyList.PROP_X_N_NEIGHBOR_ID], + ["yNNeighborID", EntityPropertyList.PROP_Y_N_NEIGHBOR_ID], + ["zNNeighborID", EntityPropertyList.PROP_Z_N_NEIGHBOR_ID], + ["xPNeighborID", EntityPropertyList.PROP_X_P_NEIGHBOR_ID], + ["yPNeighborID", EntityPropertyList.PROP_Y_P_NEIGHBOR_ID], + ["zPNeighborID", EntityPropertyList.PROP_Z_P_NEIGHBOR_ID], + + // Web + ["sourceUrl", EntityPropertyList.PROP_SOURCE_URL], + ["dpi", EntityPropertyList.PROP_DPI], + ["scriptURL", EntityPropertyList.PROP_SCRIPT_URL], + ["maxFPS", EntityPropertyList.PROP_MAX_FPS], + ["inputMode", EntityPropertyList.PROP_INPUT_MODE], + ["showKeyboardFocusHighlight", EntityPropertyList.PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT], + ["useBackground", EntityPropertyList.PROP_WEB_USE_BACKGROUND], + ["userAgent", EntityPropertyList.PROP_USER_AGENT], + + // Polyline + ["linePoints", EntityPropertyList.PROP_LINE_POINTS], + ["strokeWidths", EntityPropertyList.PROP_STROKE_WIDTHS], + ["normals", EntityPropertyList.PROP_STROKE_NORMALS], + ["strokeColors", EntityPropertyList.PROP_STROKE_COLORS], + ["isUVModeStretch", EntityPropertyList.PROP_IS_UV_MODE_STRETCH], + ["glow", EntityPropertyList.PROP_LINE_GLOW], + ["faceCamera", EntityPropertyList.PROP_LINE_FACE_CAMERA], + + // Shape + ["shape", EntityPropertyList.PROP_SHAPE], + + // Material + ["materialURL", EntityPropertyList.PROP_MATERIAL_URL], + ["materialMappingMode", EntityPropertyList.PROP_MATERIAL_MAPPING_MODE], + ["priority", EntityPropertyList.PROP_MATERIAL_PRIORITY], + ["parentMaterialName", EntityPropertyList.PROP_PARENT_MATERIAL_NAME], + ["materialMappingPos", EntityPropertyList.PROP_MATERIAL_MAPPING_POS], + ["materialMappingScale", EntityPropertyList.PROP_MATERIAL_MAPPING_SCALE], + ["materialMappingRot", EntityPropertyList.PROP_MATERIAL_MAPPING_ROT], + ["materialData", EntityPropertyList.PROP_MATERIAL_DATA], + ["materialRepeat", EntityPropertyList.PROP_MATERIAL_REPEAT], + + // Image + ["imageURL", EntityPropertyList.PROP_IMAGE_URL], + ["emissive", EntityPropertyList.PROP_EMISSIVE], + ["keepAspectRatio", EntityPropertyList.PROP_KEEP_ASPECT_RATIO], + ["subImage", EntityPropertyList.PROP_SUB_IMAGE], + + // Grid + ["followCamera", EntityPropertyList.PROP_GRID_FOLLOW_CAMERA], + ["majorGridEvery", EntityPropertyList.PROP_MAJOR_GRID_EVERY], + ["minorGridEvery", EntityPropertyList.PROP_MINOR_GRID_EVERY], + + // Gizmo + ["gizmoType", EntityPropertyList.PROP_GIZMO_TYPE] + // changedProperties += _ring.getChangedProperties(); + ]); + + /*@devdoc + * Gets property flags for all properties set in the entity properties object passed in, assuming that they may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity properties and values. + * @returns {EntityPropertyFlags} Flags for all the properties included in the entity properties object. + */ + static getChangedProperties(properties: EntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags EntityItemProperties::getChangedProperties() const + + const changedProperties = new EntityPropertyFlags(); + const propertyNames = Object.keys(properties); + for (const propertyName of propertyNames) { + const propertyValue = EntityItemProperties.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + + changedProperties.or(GrabPropertyGroup.getChangedProperties(properties)); + + if (properties.entityType === EntityType.Model) { + changedProperties.or(AnimationPropertyGroup.getChangedProperties(properties as ModelEntityProperties)); + } + if (properties.entityType === EntityType.Zone) { + changedProperties.or(KeyLightPropertyGroup.getChangedProperties(properties as ZoneEntityProperties)); + changedProperties.or(AmbientLightPropertyGroup.getChangedProperties(properties as ZoneEntityProperties)); + changedProperties.or(SkyboxPropertyGroup.getChangedProperties(properties as ZoneEntityProperties)); + changedProperties.or(HazePropertyGroup.getChangedProperties(properties as ZoneEntityProperties)); + changedProperties.or(BloomPropertyGroup.getChangedProperties(properties as ZoneEntityProperties)); + } + // WEBRTC TODO: Handle other entity types. + + return changedProperties; + } + + /*@devdoc + * Writes as many entity properties as can fit in the buffer. + * @param {Uuid} id - The entity ID. + * @param {EntityProperties} properties - A set of entity properties and values. Must include the entity type. + * @param {MessageData} buffer - The buffer to write the entity properties to. + * @param {EntityPropertyFlags} requestedProperties - The properties that are requested to be written. + * @param {EntityPropertyFlags} didntFitProperties - The properties that couldn't be written to the buffer this call. + * @returns {AppendState} Whether all, some, or none of the requested properties were written. Any properties that weren't + * written are returned in the didntFitProperties parameter. + */ + static encodeEntityEditPacket(/* command: PacketTypeValue, */ id: Uuid, properties: EntityProperties, + buffer: MessageData, requestedProperties: EntityPropertyFlags, didntFitProperties: EntityPropertyFlags): AppendState { + // C++ OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, + // const EntityItemProperties& properties, QByteArray& buffer, EntityPropertyFlags requestedProperties, + // EntityPropertyFlags & didntFitProperties) + // The command parameter isn't used in the C++, either. + + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + assert(didntFitProperties.isEmpty()); + + const data = buffer.data; + let dataPosition = buffer.dataPosition; + + const codec = new ByteCountCoded(); + + let appendState = AppendState.COMPLETED; + + // Simplification: The server doesn't actually use octcode data (see EntityItemProperties::decodeEntityEditPacket()) and + // we're not compressing data so we can write an empty octcode value. + // WEBRTC TODO: Support compressing data? + data.setUint8(dataPosition, 0); + dataPosition += 1; + + const propertyFlags = new EntityPropertyFlags(); + propertyFlags.setHasProperty(EntityPropertyList.PROP_LAST_ITEM, true); + didntFitProperties.copy(requestedProperties); + + data.setBigUint64(dataPosition, properties.lastEdited, UDT.LITTLE_ENDIAN); + dataPosition += 8; + + data.setBigUint128(dataPosition, id.value(), UDT.BIG_ENDIAN); + dataPosition += 16; + + codec.data = BigInt(properties.entityType); + const bytesWritten = codec.encode(new DataView(data.buffer, data.byteOffset + dataPosition)); + dataPosition += bytesWritten; + + dataPosition += 1; // 0x00 for endcodedUpdateDelta. + + const propertyFlagsOffset = dataPosition; + const oldPropertyFlagsLength + = propertyFlags.encode(new DataView(data.buffer, data.byteOffset + propertyFlagsOffset)); + dataPosition += oldPropertyFlagsLength; + let propertyCount = 0; + + + // Simplification: The header values will always fit because this method is always called for a new packet. + + propertyFlags.setHasProperty(EntityPropertyList.PROP_LAST_ITEM, false); + + const entityType = properties.entityType; + + const packetContext: OctreePacketContext = { + propertiesToWrite: didntFitProperties, + propertiesWritten: propertyFlags, + propertyCount, + appendState + }; + + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SIMULATION_OWNER)) { + dataPosition += OctreePacketData.appendArrayBufferValue(data, dataPosition, + EntityPropertyList.PROP_SIMULATION_OWNER, + properties.simOwnerData!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PARENT_ID)) { + dataPosition += OctreePacketData.appendUuidValue(data, dataPosition, EntityPropertyList.PROP_PARENT_ID, + properties.parentID ?? new Uuid(), packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)) { + dataPosition += OctreePacketData.appendUint16Value(data, dataPosition, EntityPropertyList.PROP_PARENT_JOINT_INDEX, + properties.parentJointIndex!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_VISIBLE)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_VISIBLE, + properties.visible!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_NAME)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_NAME, + properties.name!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_LOCKED)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_LOCKED, + properties.locked!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_USER_DATA)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_USER_DATA, + properties.userData!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PRIVATE_USER_DATA)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_PRIVATE_USER_DATA, + properties.privateUserData!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HREF)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_HREF, + properties.href!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_DESCRIPTION)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_DESCRIPTION, + properties.description!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_POSITION, + properties.position!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_DIMENSIONS)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_DIMENSIONS, + properties.dimensions!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ROTATION)) { + dataPosition += OctreePacketData.appendQuatValue(data, dataPosition, EntityPropertyList.PROP_ROTATION, + properties.rotation!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_REGISTRATION_POINT)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_REGISTRATION_POINT, + properties.registrationPoint!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CREATED)) { + dataPosition += OctreePacketData.appendUint64Value(data, dataPosition, EntityPropertyList.PROP_CREATED, + properties.created!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)) { + dataPosition += OctreePacketData.appendUuidValue(data, dataPosition, EntityPropertyList.PROP_LAST_EDITED_BY, + properties.lastEditedBy!, packetContext); + } + // PROP_ENTITY_HOST_TYPE - not sent over the wire. + // PROP_OWNING_AVATAR_ID - not sent over the wire. + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)) { + dataPosition += OctreePacketData.appendAACubeValue(data, dataPosition, EntityPropertyList.PROP_QUERY_AA_CUBE, + properties.queryAACube!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CAN_CAST_SHADOW)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_CAN_CAST_SHADOW, + properties.canCastShadow!, packetContext); + + } + // PROP_VISIBLE_IN_SECONDARY_CAMERA - not sent over the wire. + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_RENDER_LAYER, + properties.renderLayer!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PRIMITIVE_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_PRIMITIVE_MODE, + properties.primitiveMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_IGNORE_PICK_INTERSECTION)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_IGNORE_PICK_INTERSECTION, properties.ignorePickIntersection!, packetContext); + + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)) { + dataPosition += OctreePacketData.appendUuidArray(data, dataPosition, EntityPropertyList.PROP_RENDER_WITH_ZONES, + properties.renderWithZones!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BILLBOARD_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_BILLBOARD_MODE, + properties.billboardMode!, UDT.LITTLE_ENDIAN, packetContext); + } + + dataPosition += GrabPropertyGroup.appendToEditPacket(data, dataPosition, properties, packetContext); + + // Physics + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_DENSITY)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_DENSITY, + properties.density!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_VELOCITY)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_VELOCITY, + properties.velocity!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANGULAR_VELOCITY)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_ANGULAR_VELOCITY, + properties.angularVelocity!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAVITY)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_GRAVITY, + properties.gravity!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ACCELERATION)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_ACCELERATION, + properties.acceleration!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_DAMPING)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_DAMPING, + properties.damping!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ANGULAR_DAMPING)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_ANGULAR_DAMPING, + properties.angularDamping!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RESTITUTION)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_RESTITUTION, + properties.restitution!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_FRICTION)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_FRICTION, + properties.friction!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_LIFETIME)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_LIFETIME, + properties.lifetime!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLLISIONLESS)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_COLLISIONLESS, + properties.collisionless!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLLISION_MASK)) { + dataPosition += OctreePacketData.appendUint16Value(data, dataPosition, EntityPropertyList.PROP_COLLISION_MASK, + properties.collisionMask!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_DYNAMIC)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_DYNAMIC, + properties.dynamic!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLLISION_SOUND_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_COLLISION_SOUND_URL, + properties.collisionSoundURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)) { + dataPosition += OctreePacketData.appendArrayBufferValue(data, dataPosition, EntityPropertyList.PROP_ACTION_DATA, + properties.actionData!, packetContext); + } + + // Cloning + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CLONEABLE)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_CLONEABLE, + properties.cloneable!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CLONE_LIFETIME)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_CLONE_LIFETIME, + properties.cloneLifetime!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CLONE_LIMIT)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_CLONE_LIMIT, + properties.cloneLimit!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CLONE_DYNAMIC)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_CLONE_DYNAMIC, + properties.cloneDynamic!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CLONE_AVATAR_ENTITY)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_CLONE_AVATAR_ENTITY, + properties.cloneAvatarEntity!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CLONE_ORIGIN_ID)) { + dataPosition += OctreePacketData.appendUuidValue(data, dataPosition, EntityPropertyList.PROP_CLONE_ORIGIN_ID, + properties.cloneOriginID!, packetContext); + } + + // Scripts + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SCRIPT)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_SCRIPT, + properties.script!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SCRIPT_TIMESTAMP)) { + dataPosition += OctreePacketData.appendUint64Value(data, dataPosition, EntityPropertyList.PROP_SCRIPT_TIMESTAMP, + properties.scriptTimestamp!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SERVER_SCRIPTS)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_SERVER_SCRIPTS, + properties.serverScripts!, packetContext); + } + + // Certifiable Properties + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ITEM_NAME)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_ITEM_NAME, + properties.itemName!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ITEM_DESCRIPTION)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_ITEM_DESCRIPTION, + properties.itemDescription!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ITEM_CATEGORIES)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_ITEM_CATEGORIES, + properties.itemCategories!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ITEM_ARTIST)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_ITEM_ARTIST, + properties.itemArtist!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ITEM_LICENSE)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_ITEM_LICENSE, + properties.itemLicense!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_LIMITED_RUN)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_LIMITED_RUN, + properties.limitedRun!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MARKETPLACE_ID)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_MARKETPLACE_ID, + properties.marketplaceID!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EDITION_NUMBER)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_EDITION_NUMBER, + properties.editionNumber!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ENTITY_INSTANCE_NUMBER)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, + EntityPropertyList.PROP_ENTITY_INSTANCE_NUMBER, + properties.entityInstanceNumber!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CERTIFICATE_ID)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_CERTIFICATE_ID, + properties.certificateID!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CERTIFICATE_TYPE)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_CERTIFICATE_TYPE, + properties.certificateType!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_STATIC_CERTIFICATE_VERSION)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, + EntityPropertyList.PROP_STATIC_CERTIFICATE_VERSION, + properties.staticCertificateVersion!, UDT.LITTLE_ENDIAN, packetContext); + } + + + if (entityType === EntityType.ParticleEffect) { + const entityProperties = properties as ParticleEffectEntityProperties; + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SHAPE_TYPE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_SHAPE_TYPE, + entityProperties.shapeType!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COMPOUND_SHAPE_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, + EntityPropertyList.PROP_COMPOUND_SHAPE_URL, + entityProperties.compoundShapeURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_COLOR, + entityProperties.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_ALPHA, + entityProperties.alpha!, UDT.LITTLE_ENDIAN, packetContext); + } + // ... Ignore deprecated pulse properties. + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXTURES)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_TEXTURES, + entityProperties.textures!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MAX_PARTICLES)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_MAX_PARTICLES, + entityProperties.maxParticles!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_LIFESPAN)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_LIFESPAN, + entityProperties.lifespan!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMITTING_PARTICLES)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_EMITTING_PARTICLES, + entityProperties.isEmitting!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMIT_RATE)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_EMIT_RATE, + entityProperties.emitRate!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMIT_SPEED)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_EMIT_SPEED, + entityProperties.emitSpeed!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SPEED_SPREAD)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_SPEED_SPREAD, + entityProperties.speedSpread!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMIT_ORIENTATION)) { + dataPosition += OctreePacketData.appendQuatValue(data, dataPosition, + EntityPropertyList.PROP_EMIT_ORIENTATION, + entityProperties.emitOrientation!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMIT_DIMENSIONS)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, + EntityPropertyList.PROP_EMIT_DIMENSIONS, + entityProperties.emitDimensions!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMIT_RADIUS_START)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_EMIT_RADIUS_START, + entityProperties.emitRadiusStart!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_POLAR_START)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_POLAR_START, + entityProperties.polarStart!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_POLAR_FINISH)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_POLAR_FINISH, + entityProperties.polarFinish!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_AZIMUTH_START)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_AZIMUTH_START, + entityProperties.azimuthStart!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_AZIMUTH_FINISH)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_AZIMUTH_FINISH, + entityProperties.azimuthFinish!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMIT_ACCELERATION)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, + EntityPropertyList.PROP_EMIT_ACCELERATION, + entityProperties.emitAcceleration!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ACCELERATION_SPREAD)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, + EntityPropertyList.PROP_ACCELERATION_SPREAD, + entityProperties.accelerationSpread!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PARTICLE_RADIUS)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_PARTICLE_RADIUS, + entityProperties.particleRadius!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RADIUS_SPREAD)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_RADIUS_SPREAD, + entityProperties.radiusSpread!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RADIUS_START)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_RADIUS_START, + entityProperties.radiusStart!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RADIUS_FINISH)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_RADIUS_FINISH, + entityProperties.radiusFinish!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR_SPREAD)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_COLOR_SPREAD, + entityProperties.colorSpread!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR_START)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_COLOR_START, + { + x: entityProperties.colorStart!.red, + y: entityProperties.colorStart!.green, + z: entityProperties.colorStart!.blue + }, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR_FINISH)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_COLOR_FINISH, + { + x: entityProperties.colorFinish!.red, + y: entityProperties.colorFinish!.green, + z: entityProperties.colorFinish!.blue + }, packetContext); + } + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA_SPREAD)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_ALPHA_SPREAD, + entityProperties.alphaSpread!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA_START)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_ALPHA_START, + entityProperties.alphaStart!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA_FINISH)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_ALPHA_FINISH, + entityProperties.alphaFinish!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMITTER_SHOULD_TRAIL)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_EMITTER_SHOULD_TRAIL, + entityProperties.emitterShouldTrail!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PARTICLE_SPIN)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_PARTICLE_SPIN, + entityProperties.particleSpin!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SPIN_SPREAD)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_SPIN_SPREAD, + entityProperties.spinSpread!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SPIN_START)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_SPIN_START, + entityProperties.spinStart!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SPIN_FINISH)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_SPIN_FINISH, + entityProperties.spinFinish!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PARTICLE_ROTATE_WITH_ENTITY)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_PARTICLE_ROTATE_WITH_ENTITY, + entityProperties.rotateWithEntity!, packetContext); + } + } + + if (entityType === EntityType.Model) { + const entityProperties = properties as ModelEntityProperties; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SHAPE_TYPE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_SHAPE_TYPE, + entityProperties.shapeType!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COMPOUND_SHAPE_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, + EntityPropertyList.PROP_COMPOUND_SHAPE_URL, + entityProperties.compoundShapeURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_COLOR, + entityProperties.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXTURES)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, + EntityPropertyList.PROP_TEXTURES, + entityProperties.textures!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MODEL_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, + EntityPropertyList.PROP_MODEL_URL, + entityProperties.modelURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MODEL_SCALE)) { + dataPosition += OctreePacketData.appendVec3Value(data, dataPosition, EntityPropertyList.PROP_MODEL_SCALE, + entityProperties.modelScale!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)) { + dataPosition += OctreePacketData.appendBooleanArray(data, dataPosition, + EntityPropertyList.PROP_JOINT_ROTATIONS_SET, + entityProperties.jointRotationsSet!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)) { + dataPosition += OctreePacketData.appendQuatArray(data, dataPosition, EntityPropertyList.PROP_JOINT_ROTATIONS, + entityProperties.jointRotations!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS_SET)) { + dataPosition += OctreePacketData.appendBooleanArray(data, dataPosition, + EntityPropertyList.PROP_JOINT_TRANSLATIONS_SET, + entityProperties.jointTranslationsSet!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)) { + dataPosition += OctreePacketData.appendVec3Array(data, dataPosition, EntityPropertyList.PROP_JOINT_TRANSLATIONS, + entityProperties.jointTranslations!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RELAY_PARENT_JOINTS)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_RELAY_PARENT_JOINTS, + entityProperties.relayParentJoints!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GROUP_CULLED)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_GROUP_CULLED, + entityProperties.groupCulled!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BLENDSHAPE_COEFFICIENTS)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, + EntityPropertyList.PROP_BLENDSHAPE_COEFFICIENTS, + entityProperties.blendShapeCoefficients!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_USE_ORIGINAL_PIVOT)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_USE_ORIGINAL_PIVOT, + entityProperties.useOriginalPivot!, packetContext); + } + dataPosition += AnimationPropertyGroup.appendToEditPacket(data, dataPosition, entityProperties, packetContext); + } + + if (entityType === EntityType.Light) { + const entityProperties = properties as LightEntityProperties; + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_COLOR, + entityProperties.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_IS_SPOTLIGHT)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_IS_SPOTLIGHT, + entityProperties.isSpotlight!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_INTENSITY)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_INTENSITY, + entityProperties.intensity!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EXPONENT)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_EXPONENT, + entityProperties.exponent!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_CUTOFF)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_CUTOFF, + entityProperties.cutoff!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_FALLOFF_RADIUS)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_FALLOFF_RADIUS, + entityProperties.falloffRadius!, UDT.LITTLE_ENDIAN, packetContext); + } + } + + if (entityType === EntityType.Text) { + const entityProperties = properties as TextEntityProperties; + // ... Ignore deprecated pulse properties. + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXT)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_TEXT, + entityProperties.text!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_LINE_HEIGHT)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_LINE_HEIGHT, + entityProperties.lineHeight!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXT_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_TEXT_COLOR, + entityProperties.textColor!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXT_ALPHA)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_TEXT_ALPHA, + entityProperties.textAlpha!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BACKGROUND_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_BACKGROUND_COLOR, + entityProperties.backgroundColor!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BACKGROUND_ALPHA)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_BACKGROUND_ALPHA, + entityProperties.backgroundAlpha!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_LEFT_MARGIN)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_LEFT_MARGIN, + entityProperties.leftMargin!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_RIGHT_MARGIN)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_RIGHT_MARGIN, + entityProperties.rightMargin!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TOP_MARGIN)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_TOP_MARGIN, + entityProperties.topMargin!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BOTTOM_MARGIN)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_BOTTOM_MARGIN, + entityProperties.bottomMargin!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_UNLIT)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_UNLIT, + entityProperties.unlit!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_FONT)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_FONT, + entityProperties.font!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXT_EFFECT)) { + const textEffectValue = Object.values(TextEffect).indexOf(entityProperties.textEffect!); + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_TEXT_EFFECT, + textEffectValue, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXT_EFFECT_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_TEXT_EFFECT_COLOR, + entityProperties.textEffectColor!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXT_EFFECT_THICKNESS)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_TEXT_EFFECT_THICKNESS, + entityProperties.textEffectThickness!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_TEXT_ALIGNMENT)) { + const textAlignmentValue = Object.values(TextAlignment).indexOf(entityProperties.textAlignment!); + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_TEXT_ALIGNMENT, + textAlignmentValue, UDT.LITTLE_ENDIAN, packetContext); + } + } + + if (entityType === EntityType.Zone) { + const entityProperties = properties as ZoneEntityProperties; + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SHAPE_TYPE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_SHAPE_TYPE, + entityProperties.shapeType!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COMPOUND_SHAPE_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, + EntityPropertyList.PROP_COMPOUND_SHAPE_URL, + entityProperties.compoundShapeURL!, packetContext); + } + + dataPosition += KeyLightPropertyGroup.appendToEditPacket(data, dataPosition, entityProperties, packetContext); + dataPosition += AmbientLightPropertyGroup.appendToEditPacket(data, dataPosition, entityProperties, packetContext); + dataPosition += SkyboxPropertyGroup.appendToEditPacket(data, dataPosition, entityProperties, packetContext); + dataPosition += HazePropertyGroup.appendToEditPacket(data, dataPosition, entityProperties, packetContext); + dataPosition += BloomPropertyGroup.appendToEditPacket(data, dataPosition, entityProperties, packetContext); + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_FLYING_ALLOWED)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_FLYING_ALLOWED, + entityProperties.flyingAllowed!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GHOSTING_ALLOWED)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_GHOSTING_ALLOWED, + entityProperties.ghostingAllowed!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_FILTER_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_FILTER_URL, + entityProperties.filterURL!, packetContext); + } + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEY_LIGHT_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_KEY_LIGHT_MODE, + entityProperties.keyLightMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, + EntityPropertyList.PROP_AMBIENT_LIGHT_MODE, + entityProperties.ambientLightMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SKYBOX_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_SKYBOX_MODE, + entityProperties.skyboxMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_HAZE_MODE, + entityProperties.hazeMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_BLOOM_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_BLOOM_MODE, + entityProperties.bloomMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_AVATAR_PRIORITY)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_AVATAR_PRIORITY, + entityProperties.avatarPriority!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SCREENSHARE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_SCREENSHARE, + entityProperties.screenshare!, UDT.LITTLE_ENDIAN, packetContext); + } + } + + // WEBRTC TODO: Address further C++ code - other entity properties. + + if (entityType === EntityType.Web) { + const entityProperties = properties as WebEntityProperties; + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_COLOR, + entityProperties.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_ALPHA, + entityProperties.alpha!, UDT.LITTLE_ENDIAN, packetContext); + } + // ... Ignore deprecated pulse properties. + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SOURCE_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_SOURCE_URL, + entityProperties.sourceURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_DPI)) { + dataPosition += OctreePacketData.appendUint16Value(data, dataPosition, EntityPropertyList.PROP_DPI, + entityProperties.dpi!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SCRIPT_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_SCRIPT_URL, + entityProperties.scriptURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MAX_FPS)) { + dataPosition += OctreePacketData.appendUint8Value(data, dataPosition, EntityPropertyList.PROP_MAX_FPS, + entityProperties.maxFPS!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_INPUT_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, EntityPropertyList.PROP_INPUT_MODE, + entityProperties.inputMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, + entityProperties.showKeyboardFocusHighlight!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_WEB_USE_BACKGROUND)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_WEB_USE_BACKGROUND, + entityProperties.useBackground!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_USER_AGENT)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_USER_AGENT, + entityProperties.userAgent!, packetContext); + } + } + + // WEBRTC TODO: Address further C++ code - other entity properties. + + if (entityType === EntityType.Box || entityType === EntityType.Sphere || entityType === EntityType.Shape) { + const entityProperties = properties as ShapeEntityProperties; + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_COLOR, + entityProperties.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_ALPHA, + entityProperties.alpha!, UDT.LITTLE_ENDIAN, packetContext); + } + // ... Ignore deprecated pulse properties. + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SHAPE)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_SHAPE, + entityProperties.shape!, packetContext); + } + } + + // WEBRTC TODO: Address further C++ code - other entity properties. + + if (entityType === EntityType.Material) { + const entityProperties = properties as MaterialEntityProperties; + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_MATERIAL_URL, + entityProperties.materialURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_MODE)) { + dataPosition += OctreePacketData.appendUint32Value(data, dataPosition, + EntityPropertyList.PROP_MATERIAL_MAPPING_MODE, + entityProperties.materialMappingMode!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_PRIORITY)) { + dataPosition += OctreePacketData.appendUint16Value(data, dataPosition, + EntityPropertyList.PROP_MATERIAL_PRIORITY, + entityProperties.priority!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_PARENT_MATERIAL_NAME)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, + EntityPropertyList.PROP_PARENT_MATERIAL_NAME, + entityProperties.parentMaterialName!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)) { + dataPosition += OctreePacketData.appendVec2Value(data, dataPosition, + EntityPropertyList.PROP_MATERIAL_MAPPING_POS, + entityProperties.materialMappingPos!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_SCALE)) { + dataPosition += OctreePacketData.appendVec2Value(data, dataPosition, + EntityPropertyList.PROP_MATERIAL_MAPPING_SCALE, + entityProperties.materialMappingScale!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_ROT)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, + EntityPropertyList.PROP_MATERIAL_MAPPING_ROT, + entityProperties.materialMappingRot!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_DATA)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_MATERIAL_DATA, + entityProperties.materialData!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_MATERIAL_REPEAT)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_MATERIAL_REPEAT, + entityProperties.materialRepeat!, packetContext); + } + } + + if (entityType === EntityType.Image) { + const entityProperties = properties as ImageEntityProperties; + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_COLOR)) { + dataPosition += OctreePacketData.appendColorValue(data, dataPosition, EntityPropertyList.PROP_COLOR, + entityProperties.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA)) { + dataPosition += OctreePacketData.appendFloat32Value(data, dataPosition, EntityPropertyList.PROP_ALPHA, + entityProperties.alpha!, UDT.LITTLE_ENDIAN, packetContext); + } + // ... Ignore deprecated pulse properties. + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_IMAGE_URL)) { + dataPosition += OctreePacketData.appendStringValue(data, dataPosition, EntityPropertyList.PROP_IMAGE_URL, + entityProperties.imageURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_EMISSIVE)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, EntityPropertyList.PROP_EMISSIVE, + entityProperties.emissive!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEEP_ASPECT_RATIO)) { + dataPosition += OctreePacketData.appendBooleanValue(data, dataPosition, + EntityPropertyList.PROP_KEEP_ASPECT_RATIO, + entityProperties.keepAspectRatio!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SUB_IMAGE)) { + dataPosition += OctreePacketData.appendRectValue(data, dataPosition, EntityPropertyList.PROP_SUB_IMAGE, + entityProperties.subImage!, packetContext); + } + } + + // WEBRTC TODO: Address further C++ code - other entity properties. + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + + + propertyCount = packetContext.propertyCount; + appendState = packetContext.appendState; + + if (propertyCount > 0) { + const newPropertyFlagsLength + = propertyFlags.encode(new DataView(data.buffer, data.byteOffset + propertyFlagsOffset)); + + // If the size of the PropertyFlags has shrunk then move property data. + if (newPropertyFlagsLength < oldPropertyFlagsLength) { + const numPropertyBytes = dataPosition - (propertyFlagsOffset + oldPropertyFlagsLength); + for (let i = 0; i < numPropertyBytes; i++) { + data.setUint8(propertyFlagsOffset + newPropertyFlagsLength + i, + data.getUint8(propertyFlagsOffset + oldPropertyFlagsLength + i)); + } + dataPosition = propertyFlagsOffset + newPropertyFlagsLength + numPropertyBytes; + } else { + assert(newPropertyFlagsLength === oldPropertyFlagsLength); // Should not have grown. + } + } else { + dataPosition = buffer.dataPosition; + appendState = AppendState.NONE; + } + + buffer.dataPosition = dataPosition; + + return appendState; + } + +} + +export default EntityItemProperties; diff --git a/src/domain/entities/EntityPropertyFlags.ts b/src/domain/entities/EntityPropertyFlags.ts index 143ebe10..3ce61e16 100644 --- a/src/domain/entities/EntityPropertyFlags.ts +++ b/src/domain/entities/EntityPropertyFlags.ts @@ -9,229 +9,215 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import PropertyFlags from "../shared/PropertyFlags"; + /*@devdoc - * The EntityPropertyFlags namespace provides the positions of the flags in {@link PropertyFlags}. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
NameValueDescription
PROP_PAGED_PROPERTY0Paged property flag.
PROP_CUSTOM_PROPERTIES_INCLUDED1Custom properties included flag.
PROP_SIMULATION_OWNER2Simulation owner flag.
PROP_PARENT_ID3Parent id flag.
PROP_PARENT_JOINT_INDEX4Parent joint index flag.
PROP_VISIBLE5Visible flag.
PROP_NAME6Name flag.
PROP_LOCKED7Locked flag.
PROP_USER_DATA8User data flag.
PROP_PRIVATE_USER_DATA9Private user data flag.
PROP_HREF10Href flag.
PROP_DESCRIPTION11Description flag.
PROP_POSITION12Position flag.
PROP_DIMENSIONS13Dimensions flag.
PROP_ROTATION14Rotation flag.
PROP_REGISTRATION_POINT15Registration point flag.
PROP_CREATED16Created flag.
PROP_LAST_EDITED_BY17Last edited by flag.
PROP_ENTITY_HOST_TYPE18Entity host type flag.
PROP_OWNING_AVATAR_ID19Owning avatar id flag.
PROP_QUERY_AA_CUBE20Query aa cube flag.
PROP_CAN_CAST_SHADOW21Can cast shadow flag.
PROP_VISIBLE_IN_SECONDARY_CAMERA22Visible in secondary camera flag.
PROP_RENDER_LAYER23Render layer flag.
PROP_PRIMITIVE_MODE24Primitive mode flag.
PROP_IGNORE_PICK_INTERSECTION25Ignore pick intersection flag.
PROP_RENDER_WITH_ZONES26Render with zones flag.
PROP_BILLBOARD_MODE27Billboard mode flag.
PROP_GRAB_GRABBABLE28Grab grabbable flag.
PROP_GRAB_KINEMATIC29Grab kinematic flag.
PROP_GRAB_FOLLOWS_CONTROLLER30Grab follows controller flag.
PROP_GRAB_TRIGGERABLE31Grab triggerable flag.
PROP_GRAB_EQUIPPABLE32Grab equippable flag.
PROP_GRAB_DELEGATE_TO_PARENT33Grab delegate to parent flag.
PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET34Grab left equippable position offset flag.
PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET35Grab left equippable rotation offset flag.
PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET36Grab right equippable position offset flag. - *
PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET37Grab right equippable rotation offset flag. - *
PROP_GRAB_EQUIPPABLE_INDICATOR_URL38Grab equippable indicator url flag.
PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE39Grab equippable indicator scale flag.
PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET40Grab equippable indicator offset flag.
PROP_DENSITY41Density flag.
PROP_VELOCITY42Velocity flag.
PROP_ANGULAR_VELOCITY43Angular velocity flag.
PROP_GRAVITY44Gravity flag.
PROP_ACCELERATION45Acceleration flag.
PROP_DAMPING46Damping flag.
PROP_ANGULAR_DAMPING47Angular damping flag.
PROP_RESTITUTION48Restitution flag.
PROP_FRICTION49Friction flag.
PROP_LIFETIME50Lifetime flag.
PROP_COLLISIONLESS51Collisionless flag.
PROP_COLLISION_MASK52Collision mask flag.
PROP_DYNAMIC53Dynamic flag.
PROP_COLLISION_SOUND_URL54Collision sound url flag.
PROP_ACTION_DATA55Action data flag.
PROP_CLONEABLE56Cloneable flag.
PROP_CLONE_LIFETIME57Clone lifetime flag.
PROP_CLONE_LIMIT58Clone limit flag.
PROP_CLONE_DYNAMIC59Clone dynamic flag.
PROP_CLONE_AVATAR_ENTITY60Clone avatar entity flag.
PROP_CLONE_ORIGIN_ID61Clone origin id flag.
PROP_SCRIPT62Script flag.
PROP_SCRIPT_TIMESTAMP63Script timestamp flag.
PROP_SERVER_SCRIPTS64Server scripts flag.
PROP_ITEM_NAME65Item name flag.
PROP_ITEM_DESCRIPTION66Item description flag.
PROP_ITEM_CATEGORIES67Item categories flag.
PROP_ITEM_ARTIST68Item artist flag.
PROP_ITEM_LICENSE69Item license flag.
PROP_LIMITED_RUN70Limited run flag.
PROP_MARKETPLACE_ID71Marketplace id flag.
PROP_EDITION_NUMBER72Edition number flag.
PROP_ENTITY_INSTANCE_NUMBER73Entity instance number flag.
PROP_CERTIFICATE_ID74Certificate id flag.
PROP_CERTIFICATE_TYPE75Certificate type flag.
PROP_STATIC_CERTIFICATE_VERSION76Static certificate version flag.
PROP_LOCAL_POSITION77Local position flag.
PROP_LOCAL_ROTATION78Local rotation flag.
PROP_LOCAL_VELOCITY79Local velocity flag.
PROP_LOCAL_ANGULAR_VELOCITY80Local angular velocity flag.
PROP_LOCAL_DIMENSIONS81Local dimensions flag.
PROP_SHAPE_TYPE82Shape type flag.
PROP_COMPOUND_SHAPE_URL83Compound shape url flag.
PROP_COLOR84Color flag.
PROP_ALPHA85Alpha flag.
PROP_PULSE_MIN86Pulse min flag.
PROP_PULSE_MAX87Pulse max flag.
PROP_PULSE_PERIOD88Pulse period flag.
PROP_PULSE_COLOR_MODE89Pulse color mode flag.
PROP_PULSE_ALPHA_MODE90Pulse alpha mode flag.
PROP_TEXTURES91Textures flag.
PROP_DERIVED_092Derived 0 flag.
PROP_DERIVED_193Derived 1 flag.
PROP_DERIVED_294Derived 2 flag.
PROP_DERIVED_395Derived 3 flag.
PROP_DERIVED_496Derived 4 flag.
PROP_DERIVED_597Derived 5 flag.
PROP_DERIVED_698Derived 6 flag.
PROP_DERIVED_799Derived 7 flag.
PROP_DERIVED_8100Derived 8 flag.
PROP_DERIVED_9101Derived 9 flag.
PROP_DERIVED_10102Derived 10 flag.
PROP_DERIVED_11103Derived 11 flag.
PROP_DERIVED_12104Derived 12 flag.
PROP_DERIVED_13105Derived 13 flag.
PROP_DERIVED_14106Derived 14 flag.
PROP_DERIVED_15107Derived 15 flag.
PROP_DERIVED_16108Derived 16 flag.
PROP_DERIVED_17109Derived 17 flag.
PROP_DERIVED_18110Derived 18 flag.
PROP_DERIVED_19111Derived 19 flag.
PROP_DERIVED_20112Derived 20 flag.
PROP_DERIVED_21113Derived 21 flag.
PROP_DERIVED_22114Derived 22 flag.
PROP_DERIVED_23115Derived 23 flag.
PROP_DERIVED_24116Derived 24 flag.
PROP_DERIVED_25117Derived 25 flag.
PROP_DERIVED_26118Derived 26 flag.
PROP_DERIVED_27119Derived 27 flag.
PROP_DERIVED_28120Derived 28 flag.
PROP_DERIVED_29121Derived 29 flag.
PROP_DERIVED_30122Derived 30 flag.
PROP_DERIVED_31123Derived 31 flag.
PROP_DERIVED_32124Derived 32 flag.
PROP_DERIVED_33125Derived 33 flag.
PROP_DERIVED_34126Derived 34 flag.
PROP_AFTER_LAST_ITEM127After last item flag.
PROP_MAX_PARTICLES{@link EntityPropertyFlags|PROP_DERIVED_0}Max particles flag. First - * ParticleEffect entity-specific property.
PROP_LIFESPAN{@link EntityPropertyFlags|PROP_DERIVED_1}Lifespan flag.
PROP_EMITTING_PARTICLES{@link EntityPropertyFlags|PROP_DERIVED_2}Emitting_particles flag. - *
PROP_EMIT_RATE{@link EntityPropertyFlags|PROP_DERIVED_3}Emit rate flag.
PROP_EMIT_SPEED{@link EntityPropertyFlags|PROP_DERIVED_4}Emit speed flag.
PROP_SPEED_SPREAD{@link EntityPropertyFlags|PROP_DERIVED_5}Speed spread flag.
PROP_EMIT_ORIENTATION{@link EntityPropertyFlags|PROP_DERIVED_6}Emit orientation flag. - *
PROP_EMIT_DIMENSIONS{@link EntityPropertyFlags|PROP_DERIVED_7}Emit dimensions flag.
PROP_ACCELERATION_SPREAD{@link EntityPropertyFlags|PROP_DERIVED_8}Acceleration spread - * flag.
PROP_POLAR_START{@link EntityPropertyFlags|PROP_DERIVED_9}Polar start flag.
PROP_POLAR_FINISH{@link EntityPropertyFlags|PROP_DERIVED_10}Polar finish flag.
PROP_AZIMUTH_START{@link EntityPropertyFlags|PROP_DERIVED_11}Azimuth start flag.
PROP_AZIMUTH_FINISH{@link EntityPropertyFlags|PROP_DERIVED_12}Azimuth finish flag.
PROP_EMIT_RADIUS_START{@link EntityPropertyFlags|PROP_DERIVED_13}Emit radius start flag. - *
PROP_EMIT_ACCELERATION{@link EntityPropertyFlags|PROP_DERIVED_14}Emit acceleration flag. - *
PROP_PARTICLE_RADIUS{@link EntityPropertyFlags|PROP_DERIVED_15}Particle radius flag.
PROP_RADIUS_SPREAD{@link EntityPropertyFlags|PROP_DERIVED_16}Radius spread flag.
PROP_RADIUS_START{@link EntityPropertyFlags|PROP_DERIVED_17}Radius start flag.
PROP_RADIUS_FINISH{@link EntityPropertyFlags|PROP_DERIVED_18}Radius finish flag.
PROP_COLOR_SPREAD{@link EntityPropertyFlags|PROP_DERIVED_19}Color spread flag.
PROP_COLOR_START{@link EntityPropertyFlags|PROP_DERIVED_20}Color start flag.
PROP_COLOR_FINISH{@link EntityPropertyFlags|PROP_DERIVED_21}Color finish flag.
PROP_ALPHA_SPREAD{@link EntityPropertyFlags|PROP_DERIVED_22}Alpha spread flag.
PROP_ALPHA_START{@link EntityPropertyFlags|PROP_DERIVED_23}Alpha start flag.
PROP_ALPHA_FINISH{@link EntityPropertyFlags|PROP_DERIVED_24}Alpha finish flag.
PROP_EMITTER_SHOULD_TRAIL{@link EntityPropertyFlags|PROP_DERIVED_25}Emitter should trail - * flag.
PROP_PARTICLE_SPIN{@link EntityPropertyFlags|PROP_DERIVED_26}Particle spin flag.
PROP_SPIN_START{@link EntityPropertyFlags|PROP_DERIVED_27}Spin start flag.
PROP_SPIN_FINISH{@link EntityPropertyFlags|PROP_DERIVED_28}Spin finish flag.
PROP_SPIN_SPREAD{@link EntityPropertyFlags|PROP_DERIVED_29}Spin spread flag.
PROP_PARTICLE_ROTATE_WITH_ENTITY{@link EntityPropertyFlags|PROP_DERIVED_30}Particle rotate - * with entity flag.
PROP_MODEL_URL{@link EntityPropertyFlags|PROP_DERIVED_0}Model url flag. First Model - * entity-specific property.
- * {@link ModelEntityItem|ModelEntity}
PROP_MODEL_SCALE{@link EntityPropertyFlags|PROP_DERIVED_1}Model scale flag.
PROP_JOINT_ROTATIONS_SET{@link EntityPropertyFlags|PROP_DERIVED_2}Joint rotations set - * flag.
PROP_JOINT_ROTATIONS{@link EntityPropertyFlags|PROP_DERIVED_3}Joint rotations flag.
PROP_JOINT_TRANSLATIONS_SET{@link EntityPropertyFlags|PROP_DERIVED_4}Joint translations - * set flag.
PROP_JOINT_TRANSLATIONS{@link EntityPropertyFlags|PROP_DERIVED_5}Joint translations flag. - *
PROP_RELAY_PARENT_JOINTS{@link EntityPropertyFlags|PROP_DERIVED_6}Relay parent joints - * flag.
PROP_GROUP_CULLED{@link EntityPropertyFlags|PROP_DERIVED_7}Group culled flag.
PROP_BLENDSHAPE_COEFFICIENTS{@link EntityPropertyFlags|PROP_DERIVED_8}Blendshape - * coefficients flag.
PROP_USE_ORIGINAL_PIVOT{@link EntityPropertyFlags|PROP_DERIVED_9}Use original pivot flag. - *
PROP_ANIMATION_URL{@link EntityPropertyFlags|PROP_DERIVED_10}Animation url flag.
PROP_ANIMATION_ALLOW_TRANSLATION{@link EntityPropertyFlags|PROP_DERIVED_11}Animation allow - * translation flag.
PROP_ANIMATION_FPS{@link EntityPropertyFlags|PROP_DERIVED_12}Animation fps flag.
PROP_ANIMATION_FRAME_INDEX{@link EntityPropertyFlags|PROP_DERIVED_13}Animation frame index - * flag.
PROP_ANIMATION_PLAYING{@link EntityPropertyFlags|PROP_DERIVED_14}Animation playing flag. - *
PROP_ANIMATION_LOOP{@link EntityPropertyFlags|PROP_DERIVED_15}Animation loop flag.
PROP_ANIMATION_FIRST_FRAME{@link EntityPropertyFlags|PROP_DERIVED_16}Animation first frame - * flag.
PROP_ANIMATION_LAST_FRAME{@link EntityPropertyFlags|PROP_DERIVED_17}Animation last frame - * flag.
PROP_ANIMATION_HOLD{@link EntityPropertyFlags|PROP_DERIVED_18}Animation hold flag.
PROP_SHAPE{@link EntityPropertyFlags|PROP_DERIVED_0}Shape flag.
- * @typedef {number} EntityPropertyFlags + * The EntityPropertyList namespace provides the positions of the flags in {@link PropertyFlags}. + * @namespace EntityPropertyList + * @property {number} PROP_PAGED_PROPERTY - 0 - Paged property flag. + * @property {number} PROP_CUSTOM_PROPERTIES_INCLUDED - 1 - Custom properties included flag. + * @property {number} PROP_SIMULATION_OWNER - 2 - Simulation owner flag. + * @property {number} PROP_PARENT_ID - 3 - Parent id flag. + * @property {number} PROP_PARENT_JOINT_INDEX - 4 - Parent joint index flag. + * @property {number} PROP_VISIBLE - 5 - Visible flag. + * @property {number} PROP_NAME - 6 - Name flag. + * @property {number} PROP_LOCKED - 7 - Locked flag. + * @property {number} PROP_USER_DATA - 8 - User data flag. + * @property {number} PROP_PRIVATE_USER_DATA - 9 - Private user data flag. + * @property {number} PROP_HREF - 10 - Href flag. + * @property {number} PROP_DESCRIPTION - 11 - Description flag. + * @property {number} PROP_POSITION - 12 - Position flag. + * @property {number} PROP_DIMENSIONS - 13 - Dimensions flag. + * @property {number} PROP_ROTATION - 14 - Rotation flag. + * @property {number} PROP_REGISTRATION_POINT - 15 - Registration point flag. + * @property {number} PROP_CREATED - 16 - Created flag. + * @property {number} PROP_LAST_EDITED_BY - 17 - Last edited by flag. + * @property {number} PROP_ENTITY_HOST_TYPE - 18 - Entity host type flag. + * @property {number} PROP_OWNING_AVATAR_ID - 19 - Owning avatar id flag. + * @property {number} PROP_QUERY_AA_CUBE - 20 - Query aa cube flag. + * @property {number} PROP_CAN_CAST_SHADOW - 21 - Can cast shadow flag. + * @property {number} PROP_VISIBLE_IN_SECONDARY_CAMERA - 22 - Visible in secondary camera flag. + * @property {number} PROP_RENDER_LAYER - 23 - Render layer flag. + * @property {number} PROP_PRIMITIVE_MODE - 24 - Primitive mode flag. + * @property {number} PROP_IGNORE_PICK_INTERSECTION - 25 - Ignore pick intersection flag. + * @property {number} PROP_RENDER_WITH_ZONES - 26 - Render with zones flag. + * @property {number} PROP_BILLBOARD_MODE - 27 - Billboard mode flag. + * @property {number} PROP_GRAB_GRABBABLE - 28 - Grab grabbable flag. + * @property {number} PROP_GRAB_KINEMATIC - 29 - Grab kinematic flag. + * @property {number} PROP_GRAB_FOLLOWS_CONTROLLER - 30 - Grab follows controller flag. + * @property {number} PROP_GRAB_TRIGGERABLE - 31 - Grab triggerable flag. + * @property {number} PROP_GRAB_EQUIPPABLE - 32 - Grab equippable flag. + * @property {number} PROP_GRAB_DELEGATE_TO_PARENT - 33 - Grab delegate to parent flag. + * @property {number} PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET - 34 - Grab left equippable position offset flag. + * @property {number} PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET - 35 - Grab left equippable rotation offset flag. + * @property {number} PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET - 36 - Grab right equippable position offset + * flag. + * @property {number} PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET - 37 - Grab right equippable rotation offset + * flag. + * @property {number} PROP_GRAB_EQUIPPABLE_INDICATOR_URL - 38 - Grab equippable indicator url flag. + * @property {number} PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE - 39 - Grab equippable indicator scale flag. + * @property {number} PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET - 40 - Grab equippable indicator offset flag. + * @property {number} PROP_DENSITY - 41 - Density flag. + * @property {number} PROP_VELOCITY - 42 - Velocity flag. + * @property {number} PROP_ANGULAR_VELOCITY - 43 - Angular velocity flag. + * @property {number} PROP_GRAVITY - 44 - Gravity flag. + * @property {number} PROP_ACCELERATION - 45 - Acceleration flag. + * @property {number} PROP_DAMPING - 46 - Damping flag. + * @property {number} PROP_ANGULAR_DAMPING - 47 - Angular damping flag. + * @property {number} PROP_RESTITUTION - 48 - Restitution flag. + * @property {number} PROP_FRICTION - 49 - Friction flag. + * @property {number} PROP_LIFETIME - 50 - Lifetime flag. + * @property {number} PROP_COLLISIONLESS - 51 - Collisionless flag. + * @property {number} PROP_COLLISION_MASK - 52 - Collision mask flag. + * @property {number} PROP_DYNAMIC - 53 - Dynamic flag. + * @property {number} PROP_COLLISION_SOUND_URL - 54 - Collision sound url flag. + * @property {number} PROP_ACTION_DATA - 55 - Action data flag. + * @property {number} PROP_CLONEABLE - 56 - Cloneable flag. + * @property {number} PROP_CLONE_LIFETIME - 57 - Clone lifetime flag. + * @property {number} PROP_CLONE_LIMIT - 58 - Clone limit flag. + * @property {number} PROP_CLONE_DYNAMIC - 59 - Clone dynamic flag. + * @property {number} PROP_CLONE_AVATAR_ENTITY - 60 - Clone avatar entity flag. + * @property {number} PROP_CLONE_ORIGIN_ID - 61 - Clone origin id flag. + * @property {number} PROP_SCRIPT - 62 - Script flag. + * @property {number} PROP_SCRIPT_TIMESTAMP - 63 - Script timestamp flag. + * @property {number} PROP_SERVER_SCRIPTS - 64 - Server scripts flag. + * @property {number} PROP_ITEM_NAME - 65 - Item name flag. + * @property {number} PROP_ITEM_DESCRIPTION - 66 - Item description flag. + * @property {number} PROP_ITEM_CATEGORIES - 67 - Item categories flag. + * @property {number} PROP_ITEM_ARTIST - 68 - Item artist flag. + * @property {number} PROP_ITEM_LICENSE - 69 - Item license flag. + * @property {number} PROP_LIMITED_RUN - 70 - Limited run flag. + * @property {number} PROP_MARKETPLACE_ID - 71 - Marketplace id flag. + * @property {number} PROP_EDITION_NUMBER - 72 - Edition number flag. + * @property {number} PROP_ENTITY_INSTANCE_NUMBER - 73 - Entity instance number flag. + * @property {number} PROP_CERTIFICATE_ID - 74 - Certificate id flag. + * @property {number} PROP_CERTIFICATE_TYPE - 75 - Certificate type flag. + * @property {number} PROP_STATIC_CERTIFICATE_VERSION - 76 - Static certificate version flag. + * @property {number} PROP_LOCAL_POSITION - 77 - Local position flag. + * @property {number} PROP_LOCAL_ROTATION - 78 - Local rotation flag. + * @property {number} PROP_LOCAL_VELOCITY - 79 - Local velocity flag. + * @property {number} PROP_LOCAL_ANGULAR_VELOCITY - 80 - Local angular velocity flag. + * @property {number} PROP_LOCAL_DIMENSIONS - 81 - Local dimensions flag. + * @property {number} PROP_SHAPE_TYPE - 82 - Shape type flag. + * @property {number} PROP_COMPOUND_SHAPE_URL - 83 - Compound shape url flag. + * @property {number} PROP_COLOR - 84 - Color flag. + * @property {number} PROP_ALPHA - 85 - Alpha flag. + * @property {number} PROP_PULSE_MIN - 86 - Pulse min flag. + * @property {number} PROP_PULSE_MAX - 87 - Pulse max flag. + * @property {number} PROP_PULSE_PERIOD - 88 - Pulse period flag. + * @property {number} PROP_PULSE_COLOR_MODE - 89 - Pulse color mode flag. + * @property {number} PROP_PULSE_ALPHA_MODE - 90 - Pulse alpha mode flag. + * @property {number} PROP_TEXTURES - 91 - Textures flag. + * @property {number} PROP_DERIVED_0 - 92 - Derived 0 flag. + * @property {number} PROP_DERIVED_1 - 93 - Derived 1 flag. + * @property {number} PROP_DERIVED_2 - 94 - Derived 2 flag. + * @property {number} PROP_DERIVED_3 - 95 - Derived 3 flag. + * @property {number} PROP_DERIVED_4 - 96 - Derived 4 flag. + * @property {number} PROP_DERIVED_5 - 97 - Derived 5 flag. + * @property {number} PROP_DERIVED_6 - 98 - Derived 6 flag. + * @property {number} PROP_DERIVED_7 - 99 - Derived 7 flag. + * @property {number} PROP_DERIVED_8 - 100 - Derived 8 flag. + * @property {number} PROP_DERIVED_9 - 101 - Derived 9 flag. + * @property {number} PROP_DERIVED_10 - 102 - Derived 10 flag. + * @property {number} PROP_DERIVED_11 - 103 - Derived 11 flag. + * @property {number} PROP_DERIVED_12 - 104 - Derived 12 flag. + * @property {number} PROP_DERIVED_13 - 105 - Derived 13 flag. + * @property {number} PROP_DERIVED_14 - 106 - Derived 14 flag. + * @property {number} PROP_DERIVED_15 - 107 - Derived 15 flag. + * @property {number} PROP_DERIVED_16 - 108 - Derived 16 flag. + * @property {number} PROP_DERIVED_17 - 109 - Derived 17 flag. + * @property {number} PROP_DERIVED_18 - 110 - Derived 18 flag. + * @property {number} PROP_DERIVED_19 - 111 - Derived 19 flag. + * @property {number} PROP_DERIVED_20 - 112 - Derived 20 flag. + * @property {number} PROP_DERIVED_21 - 113 - Derived 21 flag. + * @property {number} PROP_DERIVED_22 - 114 - Derived 22 flag. + * @property {number} PROP_DERIVED_23 - 115 - Derived 23 flag. + * @property {number} PROP_DERIVED_24 - 116 - Derived 24 flag. + * @property {number} PROP_DERIVED_25 - 117 - Derived 25 flag. + * @property {number} PROP_DERIVED_26 - 118 - Derived 26 flag. + * @property {number} PROP_DERIVED_27 - 119 - Derived 27 flag. + * @property {number} PROP_DERIVED_28 - 120 - Derived 28 flag. + * @property {number} PROP_DERIVED_29 - 121 - Derived 29 flag. + * @property {number} PROP_DERIVED_30 - 122 - Derived 30 flag. + * @property {number} PROP_DERIVED_31 - 123 - Derived 31 flag. + * @property {number} PROP_DERIVED_32 - 124 - Derived 32 flag. + * @property {number} PROP_DERIVED_33 - 125 - Derived 33 flag. + * @property {number} PROP_DERIVED_34 - 126 - Derived 34 flag. + * @property {number} PROP_AFTER_LAST_ITEM - 127 - After last item flag. + * @property {number} PROP_MAX_PARTICLES - {@link EntityPropertyList|PROP_DERIVED_0} - Max particles flag. First + * ParticleEffect entity-specific property. + * @property {number} PROP_LIFESPAN - {@link EntityPropertyList|PROP_DERIVED_1} - Lifespan flag. + * @property {number} PROP_EMITTING_PARTICLES - {@link EntityPropertyList|PROP_DERIVED_2} - Emitting_particles + * flag. + * @property {number} PROP_EMIT_RATE - {@link EntityPropertyList|PROP_DERIVED_3} - Emit rate flag. + * @property {number} PROP_EMIT_SPEED - {@link EntityPropertyList|PROP_DERIVED_4} - Emit speed flag. + * @property {number} PROP_SPEED_SPREAD - {@link EntityPropertyList|PROP_DERIVED_5} - Speed spread flag. + * @property {number} PROP_EMIT_ORIENTATION - {@link EntityPropertyList|PROP_DERIVED_6} - Emit orientation flag. + * @property {number} PROP_EMIT_DIMENSIONS - {@link EntityPropertyList|PROP_DERIVED_7} - Emit dimensions flag. + * @property {number} PROP_ACCELERATION_SPREAD - {@link EntityPropertyList|PROP_DERIVED_8} - Acceleration spread + * flag. + * @property {number} PROP_POLAR_START - {@link EntityPropertyList|PROP_DERIVED_9} - Polar start flag. + * @property {number} PROP_POLAR_FINISH - {@link EntityPropertyList|PROP_DERIVED_10} - Polar finish flag. + * @property {number} PROP_AZIMUTH_START - {@link EntityPropertyList|PROP_DERIVED_11} - Azimuth start flag. + * @property {number} PROP_AZIMUTH_FINISH - {@link EntityPropertyList|PROP_DERIVED_12} - Azimuth finish flag. + * @property {number} PROP_EMIT_RADIUS_START - {@link EntityPropertyList|PROP_DERIVED_13} - Emit radius start + * flag. + * @property {number} PROP_EMIT_ACCELERATION - {@link EntityPropertyList|PROP_DERIVED_14} - Emit acceleration + * flag. + * @property {number} PROP_PARTICLE_RADIUS - {@link EntityPropertyList|PROP_DERIVED_15} - Particle radius flag. + * @property {number} PROP_RADIUS_SPREAD - {@link EntityPropertyList|PROP_DERIVED_16} - Radius spread flag. + * @property {number} PROP_RADIUS_START - {@link EntityPropertyList|PROP_DERIVED_17} - Radius start flag. + * @property {number} PROP_RADIUS_FINISH - {@link EntityPropertyList|PROP_DERIVED_18} - Radius finish flag. + * @property {number} PROP_COLOR_SPREAD - {@link EntityPropertyList|PROP_DERIVED_19} - Color spread flag. + * @property {number} PROP_COLOR_START - {@link EntityPropertyList|PROP_DERIVED_20} - Color start flag. + * @property {number} PROP_COLOR_FINISH - {@link EntityPropertyList|PROP_DERIVED_21} - Color finish flag. + * @property {number} PROP_ALPHA_SPREAD - {@link EntityPropertyList|PROP_DERIVED_22} - Alpha spread flag. + * @property {number} PROP_ALPHA_START - {@link EntityPropertyList|PROP_DERIVED_23} - Alpha start flag. + * @property {number} PROP_ALPHA_FINISH - {@link EntityPropertyList|PROP_DERIVED_24} - Alpha finish flag. + * @property {number} PROP_EMITTER_SHOULD_TRAIL - {@link EntityPropertyList|PROP_DERIVED_25} - Emitter should + * trail flag. + * @property {number} PROP_PARTICLE_SPIN - {@link EntityPropertyList|PROP_DERIVED_26} - Particle spin flag. + * @property {number} PROP_SPIN_START - {@link EntityPropertyList|PROP_DERIVED_27} - Spin start flag. + * @property {number} PROP_SPIN_FINISH - {@link EntityPropertyList|PROP_DERIVED_28} - Spin finish flag. + * @property {number} PROP_SPIN_SPREAD - {@link EntityPropertyList|PROP_DERIVED_29} - Spin spread flag. + * @property {number} PROP_PARTICLE_ROTATE_WITH_ENTITY - {@link EntityPropertyList|PROP_DERIVED_30} - Particle + * rotate with entity flag. + * @property {number} PROP_MODEL_URL - {@link EntityPropertyList|PROP_DERIVED_0} - Model url flag. First + * {@link ModelEntityItem|ModelEntity}-specific property. + * @property {number} PROP_MODEL_SCALE - {@link EntityPropertyList|PROP_DERIVED_1} - Model scale flag. + * @property {number} PROP_JOINT_ROTATIONS_SET - {@link EntityPropertyList|PROP_DERIVED_2} - Joint rotations set + * flag. + * @property {number} PROP_JOINT_ROTATIONS - {@link EntityPropertyList|PROP_DERIVED_3} - Joint rotations flag. + * @property {number} PROP_JOINT_TRANSLATIONS_SET - {@link EntityPropertyList|PROP_DERIVED_4} - Joint translations + * set flag. + * @property {number} PROP_JOINT_TRANSLATIONS - {@link EntityPropertyList|PROP_DERIVED_5} - Joint translations + * flag. + * @property {number} PROP_RELAY_PARENT_JOINTS - {@link EntityPropertyList|PROP_DERIVED_6} - Relay parent joints + * flag. + * @property {number} PROP_GROUP_CULLED - {@link EntityPropertyList|PROP_DERIVED_7} - Group culled flag. + * @property {number} PROP_BLENDSHAPE_COEFFICIENTS - {@link EntityPropertyList|PROP_DERIVED_8} - Blendshape + * coefficients flag. + * @property {number} PROP_USE_ORIGINAL_PIVOT - {@link EntityPropertyList|PROP_DERIVED_9} - Use original pivot + * flag. + * @property {number} PROP_ANIMATION_URL - {@link EntityPropertyList|PROP_DERIVED_10} - Animation url flag. + * @property {number} PROP_ANIMATION_ALLOW_TRANSLATION - {@link EntityPropertyList|PROP_DERIVED_11} - Animation + * allow translation flag. + * @property {number} PROP_ANIMATION_FPS - {@link EntityPropertyList|PROP_DERIVED_12} - Animation fps flag. + * @property {number} PROP_ANIMATION_FRAME_INDEX - {@link EntityPropertyList|PROP_DERIVED_13} - Animation frame + * index flag. + * @property {number} PROP_ANIMATION_PLAYING - {@link EntityPropertyList|PROP_DERIVED_14} - Animation playing + * flag. + * @property {number} PROP_ANIMATION_LOOP - {@link EntityPropertyList|PROP_DERIVED_15} - Animation loop flag. + * @property {number} PROP_ANIMATION_FIRST_FRAME - {@link EntityPropertyList|PROP_DERIVED_16} - Animation first + * frame flag. + * @property {number} PROP_ANIMATION_LAST_FRAME - {@link EntityPropertyList|PROP_DERIVED_17} - Animation last + * frame flag. + * @property {number} PROP_ANIMATION_HOLD - {@link EntityPropertyList|PROP_DERIVED_18} - Animation hold flag. + * @property {number} PROP_SHAPE - {@link EntityPropertyList|PROP_DERIVED_0} - Shape flag. */ -enum EntityPropertyFlags { - // C++ EntityPropertyFlags.h +enum EntityPropertyList { + // C++ EntityPropertyList + // EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1); PROP_PAGED_PROPERTY, PROP_CUSTOM_PROPERTIES_INCLUDED, @@ -589,7 +575,23 @@ enum EntityPropertyFlags { PROP_MAJOR_TICK_MARKS_LENGTH = PROP_DERIVED_15, PROP_MINOR_TICK_MARKS_LENGTH = PROP_DERIVED_16, PROP_MAJOR_TICK_MARKS_COLOR = PROP_DERIVED_17, - PROP_MINOR_TICK_MARKS_COLOR = PROP_DERIVED_18 + PROP_MINOR_TICK_MARKS_COLOR = PROP_DERIVED_18, + + // Last item + PROP_LAST_ITEM = PROP_AFTER_LAST_ITEM - 1 +} + +/*@devdoc + * The EntityPropertyFlags provides facilities to decode, set and get entity property flags per the + * {@link EntityPropertyList} values. + * + *

C++ typedef PropertyFlags EntityPropertyFlags

+ * @class EntityPropertyFlags + * @extends PropertyFlags + */ +class EntityPropertyFlags extends PropertyFlags { + // C++ typedef PropertyFlags EntityPropertyFlags } -export { EntityPropertyFlags }; +export default EntityPropertyFlags; +export { EntityPropertyList }; diff --git a/src/domain/entities/EntityTypes.ts b/src/domain/entities/EntityTypes.ts index 2e1f35b7..feacec1f 100644 --- a/src/domain/entities/EntityTypes.ts +++ b/src/domain/entities/EntityTypes.ts @@ -10,7 +10,7 @@ // /*@sdkdoc - * The EntityType namespace provides types for entities. + * The EntityType namespace enumerates entity types. * @namespace EntityType * @property {number} Unknown - 0 - Default entity type. * @property {number} Box - 1 - A rectangular prism. This is a synonym of Shape for the case where diff --git a/src/domain/entities/GizmoEntityItem.ts b/src/domain/entities/GizmoEntityItem.ts index 7afa4537..f9faf0a5 100644 --- a/src/domain/entities/GizmoEntityItem.ts +++ b/src/domain/entities/GizmoEntityItem.ts @@ -10,8 +10,7 @@ // import { CommonEntityProperties } from "../networking/packets/EntityData"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import RingGizmoPropertyGroup from "./RingGizmoPropertyGroup"; @@ -30,7 +29,7 @@ type GizmoEntitySubclassData = { class GizmoEntityItem { // C++ class GizmoEntityItem : public EntityItem - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): GizmoEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): GizmoEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int GizmoEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -39,7 +38,7 @@ class GizmoEntityItem { let dataPosition = position; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GIZMO_TYPE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GIZMO_TYPE)) { // WEBRTC TODO: Read gizmoType property. dataPosition += 4; } diff --git a/src/domain/entities/GrabPropertyGroup.ts b/src/domain/entities/GrabPropertyGroup.ts new file mode 100644 index 00000000..3226d787 --- /dev/null +++ b/src/domain/entities/GrabPropertyGroup.ts @@ -0,0 +1,367 @@ +// +// GrabPropertyGroup.ts +// +// Created by David Rowe on 13 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { EntityProperties } from "../networking/packets/EntityData"; +import UDT from "../networking/udt/UDT"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; +import GLMHelpers from "../shared/GLMHelpers"; +import { quat } from "../shared/Quat"; +import { vec3 } from "../shared/Vec3"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; + + +type GrabProperties = { + grabbable: boolean | undefined, + grabKinematic: boolean | undefined, + grabFollowsController: boolean | undefined, + triggerable: boolean | undefined, + equippable: boolean | undefined, + grabDelegateToParent: boolean | undefined, + equippableLeftPositionOffset: vec3 | undefined, + equippableLeftRotationOffset: quat | undefined, + equippableRightPositionOffset: vec3 | undefined, + equippableRightRotationOffset: quat | undefined, + equippableIndicatorURL: string | undefined, + equippableIndicatorScale: vec3 | undefined, + equippableIndicatorOffset: vec3 | undefined +}; + +type GrabPropertyGroupSubclassData = { + bytesRead: number, + properties: GrabProperties +}; + + +/*@devdoc + * The GrabPropertyGroup class provides facilities for handling grab properties of an entity. + *

C++: class GrabPropertyGroup : public PropertyGroup

+ * @class GrabPropertyGroup + */ +class GrabPropertyGroup { + // C++ class GrabPropertyGroup : public PropertyGroup + + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ class GrabPropertyGroup : public PropertyGroup + ["grabbable", EntityPropertyList.PROP_GRAB_GRABBABLE], + ["grabKinematic", EntityPropertyList.PROP_GRAB_KINEMATIC], + ["grabFollowsController", EntityPropertyList.PROP_GRAB_FOLLOWS_CONTROLLER], + ["triggerable", EntityPropertyList.PROP_GRAB_TRIGGERABLE], + ["equippable", EntityPropertyList.PROP_GRAB_EQUIPPABLE], + ["grabDelegateToParent", EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT], + ["equippableLeftPosition", EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET], + ["equippableLeftRotation", EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET], + ["equippableRightPosition", EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET], + ["equippableRightRotation", EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET], + ["equippableIndicatorURL", EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_URL], + ["equippableIndicatorScale", EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE], + ["equippableIndicatorOffset", EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET] + ]); + + + /*@sdkdoc + * Defines an entity's grab properties. + * @typedef {object} GrabProperties + * @property {boolean|undefined} grabbable - true if the entity can be grabbed, false if it + * can't be. + * @property {boolean|undefined} grabKinematic - true if the entity will be updated in a kinematic manner + * when grabbed; false if it will be grabbed using a tractor action. A kinematic grab will make the item + * appear more tightly held but will cause it to behave poorly when interacting with dynamic entities. + * @property {boolean|undefined} grabFollowsController - true if the entity will follow the motions of + * the hand controller even if the avatar's hand can't get to the implied position, false if it will + * follow the motions of the avatar's hand. This should be set true for tools, pens, etc. and false for + * things meant to decorate the hand. + * @property {boolean|undefined} triggerable - true if the entity will receive calls to trigger + * Controller entity methods, false if it won't. + * @property {boolean|undefined} equippable - true if the entity can be equipped, false if + * it cannot. + * @property {boolean|undefined} grabDelegateToParent - true if when the entity is grabbed, the grab will be + * transferred to its parent entity if there is one; false if the grab won't be transferred, so a child + * entity can be grabbed and moved relative to its parent. + * @property {vec3|undefined} equippableLeftPositionOffset - Positional offset from the left hand, when equipped. + * @property {quat|undefined} equippableLeftRotationOffset - Rotational offset from the left hand, when equipped. + * @property {vec3|undefined} equippableRightPositionOffset - Positional offset from the right hand, when equipped. + * @property {quat|undefined} equippableRightRotationOffset - Rotational offset from the right hand, when equipped. + * @property {string|undefined} equippableIndicatorURL - If non-empty, this model will be used to indicate that an entity + * is equippable, rather than the default. + * @property {vec3|undefined} equippableIndicatorScale - If equippableIndicatorURL is non-empty, this controls the scale + * of the displayed indicator. + * @property {vec3|undefined} equippableIndicatorOffset - If equippableIndicatorURL is non-empty, this controls the + * relative offset of the displayed object from the equippable entity. + */ + + /*@devdoc + * A wrapper for providing {@link GrabProperties} and the number of bytes read. + * @typedef {object} GrabPropertyGroupSubclassData + * @property {number} bytesRead - The number of bytes read. + * @property {GrabProperties} properties - The grab properties. + */ + + /*@devdoc + * Reads, if present, an entity's grab properties in an {@link PacketType(1)|EntityData} packet. + *

Static

+ * @param {DataView} data - The {@link Packets|EntityData} message data to read. + * @param {number} position - The position of the entity's grab properties in the {@link Packets|EntityData} message data. + * @param {PropertyFlags} propertyFlags - The property flags. + * @returns {GrabPropertyGroupSubclassData} The entity's grab properties and the number of bytes read. + */ + static readEntitySubclassDataFromBuffer(data: DataView, position: number, + propertyFlags: EntityPropertyFlags): GrabPropertyGroupSubclassData { + // C++ int GrabPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + // bool& somethingChanged) + + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + let dataPosition = position; + + let grabbable: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE)) { + grabbable = Boolean(data.getUint8(dataPosition)); + dataPosition += 1; + } + + let grabKinematic: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_KINEMATIC)) { + grabKinematic = Boolean(data.getUint8(dataPosition)); + dataPosition += 1; + } + + let grabFollowsController: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_FOLLOWS_CONTROLLER)) { + grabFollowsController = Boolean(data.getUint8(dataPosition)); + dataPosition += 1; + } + + let triggerable: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE)) { + triggerable = Boolean(data.getUint8(dataPosition)); + dataPosition += 1; + } + + let equippable: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE)) { + equippable = Boolean(data.getUint8(dataPosition)); + dataPosition += 1; + } + + let grabDelegateToParent: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT)) { + grabDelegateToParent = Boolean(data.getUint8(dataPosition)); + dataPosition += 1; + } + + let equippableLeftPositionOffset: vec3 | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET)) { + equippableLeftPositionOffset = { + x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), + y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), + z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) + }; + dataPosition += 12; + } + + let equippableLeftRotationOffset: quat | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET)) { + equippableLeftRotationOffset + = GLMHelpers.unpackOrientationQuatFromBytes(data, dataPosition); + dataPosition += 8; + } + + let equippableRightPositionOffset: vec3 | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET)) { + equippableRightPositionOffset = { + x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), + y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), + z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) + }; + dataPosition += 12; + } + + let equippableRightRotationOffset: quat | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET)) { + equippableRightRotationOffset + = GLMHelpers.unpackOrientationQuatFromBytes(data, dataPosition); + dataPosition += 8; + } + + let equippableIndicatorURL: string | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_URL)) { + const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); + dataPosition += 2; + + if (length > 0) { + const textDecoder = new TextDecoder(); + equippableIndicatorURL = textDecoder.decode( + new Uint8Array(data.buffer, data.byteOffset + dataPosition, length) + ); + dataPosition += length; + } + } + + let equippableIndicatorScale: vec3 | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE)) { + equippableIndicatorScale = { + x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), + y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), + z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) + }; + dataPosition += 12; + } + + let equippableIndicatorOffset: vec3 | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)) { + equippableIndicatorOffset = { + x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), + y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), + z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) + }; + dataPosition += 12; + } + + return { + bytesRead: dataPosition - position, + properties: { + grabbable, + grabKinematic, + grabFollowsController, + triggerable, + equippable, + grabDelegateToParent, + equippableLeftPositionOffset, + equippableLeftRotationOffset, + equippableRightPositionOffset, + equippableRightRotationOffset, + equippableIndicatorURL, + equippableIndicatorScale, + equippableIndicatorOffset + } + }; + + /* eslint-enable @typescript-eslint/no-magic-numbers */ + } + + /*@devdoc + * Gets property flags for entity grab properties set in the entity properties object passed in, assuming that + * there may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity grab properties included in the entity + * properties object. + */ + static getChangedProperties(properties: EntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags getChangedProperties() const + const changedProperties = new EntityPropertyFlags(); + if (properties.grab) { + const propertyNames = Object.keys(properties.grab); + for (const propertyName of propertyNames) { + const propertyValue = GrabPropertyGroup.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + } + return changedProperties; + } + + /*@devdoc + * Writes GrabPropertyGroup properties to a buffer as are able to fit. + * @param {DataView} data - The buffer to write to. + * @param {number} dataPosition - The position to start writing at. + * @param {EntityProperties} entityProperties - A set of entity properties and values. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendToEditPacket(data: DataView, dataPosition: number, entityProperties: EntityProperties, + packetContext: OctreePacketContext): number { + // C++ bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData, + // EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, + // EntityPropertyFlags& propertiesDidntFit, int& propertyCount, OctreeElement::AppendState& appendState) const + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + let bytesWritten = 0; + const requestedProperties = packetContext.propertiesToWrite; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_GRABBABLE, + entityProperties.grab!.grabbable!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_KINEMATIC)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_KINEMATIC, + entityProperties.grab!.grabKinematic!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_FOLLOWS_CONTROLLER)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_FOLLOWS_CONTROLLER, + entityProperties.grab!.grabFollowsController!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_TRIGGERABLE, + entityProperties.grab!.triggerable!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_EQUIPPABLE, + entityProperties.grab!.equippable!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT, + entityProperties.grab!.grabDelegateToParent!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET)) { + bytesWritten += OctreePacketData.appendVec3Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, + entityProperties.grab!.equippableLeftPositionOffset!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET)) { + bytesWritten += OctreePacketData.appendQuatValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, + entityProperties.grab!.equippableLeftRotationOffset!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET)) { + bytesWritten += OctreePacketData.appendVec3Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, + entityProperties.grab!.equippableRightPositionOffset!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET)) { + bytesWritten += OctreePacketData.appendQuatValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET, + entityProperties.grab!.equippableRightRotationOffset!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_URL)) { + bytesWritten += OctreePacketData.appendStringValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_URL, + entityProperties.grab!.equippableIndicatorURL!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE)) { + bytesWritten += OctreePacketData.appendVec3Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE, + entityProperties.grab!.equippableIndicatorScale!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)) { + bytesWritten += OctreePacketData.appendVec3Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, + entityProperties.grab!.equippableIndicatorOffset!, packetContext); + } + + return bytesWritten; + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } + +} + +export default GrabPropertyGroup; +export type { GrabPropertyGroupSubclassData, GrabProperties }; diff --git a/src/domain/entities/GridEntityItem.ts b/src/domain/entities/GridEntityItem.ts index 4d9eb385..c36ff767 100644 --- a/src/domain/entities/GridEntityItem.ts +++ b/src/domain/entities/GridEntityItem.ts @@ -10,8 +10,7 @@ // import { CommonEntityProperties } from "../networking/packets/EntityData"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import PulsePropertyGroup from "./PulsePropertyGroup"; @@ -30,7 +29,7 @@ type GridEntitySubclassData = { class GridEntityItem { // C++ class GridEntityItem : public EntityItem - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): GridEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): GridEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int GridEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -39,12 +38,12 @@ class GridEntityItem { let dataPosition = position; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { // WEBRTC TODO: Read color property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA)) { // WEBRTC TODO: Read alpha property. dataPosition += 4; } @@ -53,17 +52,17 @@ class GridEntityItem { // Ignore deprecated pulse property. dataPosition += pulseProperties.bytesRead; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRID_FOLLOW_CAMERA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRID_FOLLOW_CAMERA)) { // WEBRTC TODO: Read gridFollowCamera property. dataPosition += 1; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MAJOR_GRID_EVERY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MAJOR_GRID_EVERY)) { // WEBRTC TODO: Read majorGridEvery property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MINOR_GRID_EVERY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MINOR_GRID_EVERY)) { // WEBRTC TODO: Read minorGridEvery property. dataPosition += 4; } diff --git a/src/domain/entities/HazePropertyGroup.ts b/src/domain/entities/HazePropertyGroup.ts index d4138f02..72c8124a 100644 --- a/src/domain/entities/HazePropertyGroup.ts +++ b/src/domain/entities/HazePropertyGroup.ts @@ -10,9 +10,10 @@ // import UDT from "../networking/udt/UDT"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; import type { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; +import { ZoneEntityProperties } from "./ZoneEntityItem"; type HazeProperties = { @@ -44,6 +45,23 @@ type HazePropertyGroupSubclassData = { class HazePropertyGroup { // C++ class HazePropertyGroup : public PropertyGroup + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ EntityPropertyFlags HazePropertyGroup::getChangedProperties() const + ["range", EntityPropertyList.PROP_HAZE_RANGE], + ["color", EntityPropertyList.PROP_HAZE_COLOR], + ["glareColor", EntityPropertyList.PROP_HAZE_GLARE_COLOR], + ["enableGlare", EntityPropertyList.PROP_HAZE_ENABLE_GLARE], + ["glareAngle", EntityPropertyList.PROP_HAZE_GLARE_ANGLE], + ["altitudeEffect", EntityPropertyList.PROP_HAZE_ALTITUDE_EFFECT], + ["ceiling", EntityPropertyList.PROP_HAZE_CEILING], + ["caseRef", EntityPropertyList.PROP_HAZE_BASE_REF], + ["cackgroundBlend", EntityPropertyList.PROP_HAZE_BACKGROUND_BLEND], + ["attenuateKeyLight", EntityPropertyList.PROP_HAZE_ATTENUATE_KEYLIGHT], + ["keyLightRange", EntityPropertyList.PROP_HAZE_KEYLIGHT_RANGE], + ["keyLightAltitude", EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE] + ]); + + /*@sdkdoc * Defines the haze in a zone. * @typedef {object} HazeProperties @@ -90,7 +108,7 @@ class HazePropertyGroup { * @returns {HazePropertyGroupSubclassData} The Zone entity's haze properties and the number of bytes read. */ static readEntitySubclassDataFromBuffer(data: DataView, position: number, - propertyFlags: PropertyFlags): HazePropertyGroupSubclassData { + propertyFlags: EntityPropertyFlags): HazePropertyGroupSubclassData { // C++ int HazePropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -100,13 +118,13 @@ class HazePropertyGroup { let dataPosition = position; let range: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_RANGE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_RANGE)) { range = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -116,7 +134,7 @@ class HazePropertyGroup { } let glareColor: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_GLARE_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_GLARE_COLOR)) { glareColor = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -126,57 +144,55 @@ class HazePropertyGroup { } let enableGlare: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_ENABLE_GLARE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_ENABLE_GLARE)) { enableGlare = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let glareAngle: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_GLARE_ANGLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_GLARE_ANGLE)) { glareAngle = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let altitudeEffect: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_ALTITUDE_EFFECT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_ALTITUDE_EFFECT)) { altitudeEffect = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let ceiling: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_CEILING)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_CEILING)) { ceiling = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let base: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_BASE_REF)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_BASE_REF)) { base = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let backgroundBlend: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_BACKGROUND_BLEND)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_BACKGROUND_BLEND)) { backgroundBlend = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); - // WEBRTC TODO: Read hazeBackgroundBlend property. dataPosition += 4; } let attenuateKeyLight: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_ATTENUATE_KEYLIGHT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_ATTENUATE_KEYLIGHT)) { attenuateKeyLight = Boolean(data.getUint8(dataPosition)); - // WEBRTC TODO: Read hazeAttenuateKeylight property. dataPosition += 1; } let keyLightRange: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_KEYLIGHT_RANGE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_RANGE)) { keyLightRange = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let keyLightAltitude: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_KEYLIGHT_ALTITUDE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE)) { keyLightAltitude = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -202,6 +218,115 @@ class HazePropertyGroup { /* eslint-enable @typescript-eslint/no-magic-numbers */ } + /*@devdoc + * Gets property flags for Zone haze properties set in the entity properties object passed in, + * assuming that there may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity properties and values. + * @returns {EntityPropertyFlags} Flags for all the Zone haze properties included in the entity + * properties object. + */ + static getChangedProperties(properties: ZoneEntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags getChangedProperties() const + const changedProperties = new EntityPropertyFlags(); + if (properties.haze) { + const propertyNames = Object.keys(properties.haze); + for (const propertyName of propertyNames) { + const propertyValue = HazePropertyGroup.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + } + return changedProperties; + } + + /*@devdoc + * Writes HazePropertyGroup properties to a buffer as are able to fit. + * @param {DataView} data - The buffer to write to. + * @param {number} dataPosition - The position to start writing at. + * @param {EntityProperties} entityProperties - A set of entity properties and values. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendToEditPacket(data: DataView, dataPosition: number, entityProperties: ZoneEntityProperties, + packetContext: OctreePacketContext): number { + // C++ bool appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& requestedProperties, + // EntityPropertyFlags & propertyFlags, EntityPropertyFlags& propertiesDidntFit, int& propertyCount, + // OctreeElement:: AppendState & appendState) const + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + let bytesWritten = 0; + const requestedProperties = packetContext.propertiesToWrite; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_RANGE)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_RANGE, + entityProperties.haze!.range!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_COLOR)) { + bytesWritten += OctreePacketData.appendColorValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_COLOR, + entityProperties.haze!.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_GLARE_COLOR)) { + bytesWritten += OctreePacketData.appendColorValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_GLARE_COLOR, + entityProperties.haze!.glareColor!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_ENABLE_GLARE)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_ENABLE_GLARE, + entityProperties.haze!.enableGlare!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_GLARE_ANGLE)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_GLARE_ANGLE, + entityProperties.haze!.glareAngle!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_ALTITUDE_EFFECT)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_ALTITUDE_EFFECT, + entityProperties.haze!.altitudeEffect!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_CEILING)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_CEILING, + entityProperties.haze!.ceiling!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_BASE_REF)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_BASE_REF, + entityProperties.haze!.base!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_BACKGROUND_BLEND)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_BACKGROUND_BLEND, + entityProperties.haze!.backgroundBlend!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_ATTENUATE_KEYLIGHT)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_ATTENUATE_KEYLIGHT, + entityProperties.haze!.attenuateKeyLight!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_RANGE)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_KEYLIGHT_RANGE, + entityProperties.haze!.keyLightRange!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE, + entityProperties.haze!.keyLightAltitude!, UDT.LITTLE_ENDIAN, packetContext); + } + + return bytesWritten; + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } + } export default HazePropertyGroup; diff --git a/src/domain/entities/ImageEntityItem.ts b/src/domain/entities/ImageEntityItem.ts index 3af844d3..462921de 100644 --- a/src/domain/entities/ImageEntityItem.ts +++ b/src/domain/entities/ImageEntityItem.ts @@ -12,9 +12,8 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import type { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; import { rect } from "../shared/Rect"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import PulsePropertyGroup from "./PulsePropertyGroup"; @@ -46,10 +45,10 @@ class ImageEntityItem { /*@sdkdoc * Defines a rectangular portion of an image or screen, or similar. * @typedef {object} rect - * @property {number} x - Left, x-coordinate value. - * @property {number} y - Top, y-coordinate value. - * @property {number} width - Width of the rectangle. - * @property {number} height - Height of the rectangle. + * @property {number} x - Left, x-coordinate value. Unsigned integer. + * @property {number} y - Top, y-coordinate value. Unsigned integer. + * @property {number} width - Width of the rectangle. Unsigned integer. + * @property {number} height - Height of the rectangle. Unsigned integer. */ /*@sdkdoc @@ -83,7 +82,7 @@ class ImageEntityItem { * @param {PropertyFlags} propertyFlags - The property flags. * @returns {ImageEntitySubclassData} The Image entity properties and the number of bytes read. */ - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): ImageEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): ImageEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -95,7 +94,7 @@ class ImageEntityItem { const textDecoder = new TextDecoder(); let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -105,7 +104,7 @@ class ImageEntityItem { } let alpha: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA)) { alpha = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -115,7 +114,7 @@ class ImageEntityItem { dataPosition += pulseProperties.bytesRead; let imageURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_IMAGE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_IMAGE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -127,19 +126,19 @@ class ImageEntityItem { } let emissive: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMISSIVE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMISSIVE)) { emissive = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let keepAspectRatio: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEEP_ASPECT_RATIO)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEEP_ASPECT_RATIO)) { keepAspectRatio = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let subImage: rect | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SUB_IMAGE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SUB_IMAGE)) { subImage = { x: data.getUint32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getUint32(dataPosition + 4, UDT.LITTLE_ENDIAN), diff --git a/src/domain/entities/KeyLightPropertyGroup.ts b/src/domain/entities/KeyLightPropertyGroup.ts index 1f9c0968..14dc0e96 100644 --- a/src/domain/entities/KeyLightPropertyGroup.ts +++ b/src/domain/entities/KeyLightPropertyGroup.ts @@ -10,10 +10,11 @@ // import UDT from "../networking/udt/UDT"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; import type { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; import type { vec3 } from "../shared/Vec3"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; +import { ZoneEntityProperties } from "./ZoneEntityItem"; type KeyLightProperties = { @@ -40,6 +41,16 @@ type KeyLightPropertyGroupSubclassData = { class KeyLightPropertyGroup { // C++ class KeyLightPropertyGroup : public PropertyGroup + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const + ["color", EntityPropertyList.PROP_KEYLIGHT_COLOR], + ["intensity", EntityPropertyList.PROP_KEYLIGHT_INTENSITY], + ["direction", EntityPropertyList.PROP_KEYLIGHT_DIRECTION], + ["castShadows", EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW], + ["shadowBias", EntityPropertyList.PROP_KEYLIGHT_SHADOW_BIAS], + ["shadowMaxDistance", EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE] + ]); + /*@sdkdoc * Defines the key light in a zone. * @typedef {object} KeyLightProperties @@ -73,7 +84,7 @@ class KeyLightPropertyGroup { * @returns {KeyLightPropertyGroupSubclassData} The Zone entity's key light properties and the number of bytes read. */ static readEntitySubclassDataFromBuffer(data: DataView, position: number, - propertyFlags: PropertyFlags): KeyLightPropertyGroupSubclassData { + propertyFlags: EntityPropertyFlags): KeyLightPropertyGroupSubclassData { // C++ int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -83,7 +94,7 @@ class KeyLightPropertyGroup { let dataPosition = position; let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEYLIGHT_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -93,13 +104,13 @@ class KeyLightPropertyGroup { } let intensity: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEYLIGHT_INTENSITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_INTENSITY)) { intensity = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let direction: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEYLIGHT_DIRECTION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_DIRECTION)) { direction = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -109,19 +120,19 @@ class KeyLightPropertyGroup { } let castShadows: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEYLIGHT_CAST_SHADOW)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW)) { castShadows = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let shadowBias: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEYLIGHT_SHADOW_BIAS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_BIAS)) { shadowBias = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let shadowMaxDistance: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)) { shadowMaxDistance = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -141,6 +152,85 @@ class KeyLightPropertyGroup { /* eslint-enable @typescript-eslint/no-magic-numbers */ } + /*@devdoc + * Gets property flags for Zone keyLight properties set in the entity properties object passed in, assuming + * that there may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity properties and values. + * @returns {EntityPropertyFlags} Flags for all the Zone keyLight properties included in the entity properties + * object. + */ + static getChangedProperties(properties: ZoneEntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags getChangedProperties() const + const changedProperties = new EntityPropertyFlags(); + if (properties.keyLight) { + const propertyNames = Object.keys(properties.keyLight); + for (const propertyName of propertyNames) { + const propertyValue = KeyLightPropertyGroup.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + } + return changedProperties; + } + + /*@devdoc + * Writes KeyLightPropertyGroup properties to a buffer as are able to fit. + * @param {DataView} data - The buffer to write to. + * @param {number} dataPosition - The position to start writing at. + * @param {EntityProperties} entityProperties - A set of entity properties and values. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendToEditPacket(data: DataView, dataPosition: number, entityProperties: ZoneEntityProperties, + packetContext: OctreePacketContext): number { + // C++ bool appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& requestedProperties, + // EntityPropertyFlags & propertyFlags, EntityPropertyFlags& propertiesDidntFit, int& propertyCount, + // OctreeElement:: AppendState & appendState) const + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + let bytesWritten = 0; + const requestedProperties = packetContext.propertiesToWrite; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR)) { + bytesWritten += OctreePacketData.appendColorValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_KEYLIGHT_COLOR, + entityProperties.keyLight!.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_INTENSITY)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_KEYLIGHT_INTENSITY, + entityProperties.keyLight!.intensity!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_DIRECTION)) { + bytesWritten += OctreePacketData.appendVec3Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_KEYLIGHT_DIRECTION, + entityProperties.keyLight!.direction!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW)) { + bytesWritten += OctreePacketData.appendBooleanValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW, + entityProperties.keyLight!.castShadows!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_BIAS)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_KEYLIGHT_SHADOW_BIAS, + entityProperties.keyLight!.shadowBias!, UDT.LITTLE_ENDIAN, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)) { + bytesWritten += OctreePacketData.appendFloat32Value(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE, + entityProperties.keyLight!.shadowMaxDistance!, UDT.LITTLE_ENDIAN, packetContext); + } + + return bytesWritten; + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } + } export default KeyLightPropertyGroup; diff --git a/src/domain/entities/LightEntityItem.ts b/src/domain/entities/LightEntityItem.ts index 670705f7..9ef524f9 100644 --- a/src/domain/entities/LightEntityItem.ts +++ b/src/domain/entities/LightEntityItem.ts @@ -12,8 +12,7 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import type { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; type LightEntitySubclassProperties = { @@ -75,7 +74,7 @@ class LightEntityItem { * @returns {LightEntitySubclassData} The Light entity properties and the number of bytes read. */ static readEntitySubclassDataFromBuffer(data: DataView, position: number, - propertyFlags: PropertyFlags): LightEntitySubclassData { + propertyFlags: EntityPropertyFlags): LightEntitySubclassData { // C++ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -85,7 +84,7 @@ class LightEntityItem { let dataPosition = position; let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -95,31 +94,31 @@ class LightEntityItem { } let isSpotlight: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_IS_SPOTLIGHT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_IS_SPOTLIGHT)) { isSpotlight = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let intensity: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_INTENSITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_INTENSITY)) { intensity = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let exponent: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EXPONENT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EXPONENT)) { exponent = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let cutoff: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CUTOFF)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CUTOFF)) { cutoff = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let falloffRadius: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_FALLOFF_RADIUS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_FALLOFF_RADIUS)) { falloffRadius = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } diff --git a/src/domain/entities/LineEntityItem.ts b/src/domain/entities/LineEntityItem.ts index 7acd9ebc..d38692a8 100644 --- a/src/domain/entities/LineEntityItem.ts +++ b/src/domain/entities/LineEntityItem.ts @@ -11,8 +11,7 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; // WEBRTC TODO: Replace Record with LineEntityItem's special properties. type LineEntitySubclassProperties = Record; @@ -29,7 +28,7 @@ type LineEntitySubclassData = { class LineEntityItem { // C++ class LineEntityItem : public EntityItem - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): LineEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): LineEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -38,12 +37,12 @@ class LineEntityItem { let dataPosition = position; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { // WEBRTC TODO: Read color property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LINE_POINTS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LINE_POINTS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; diff --git a/src/domain/entities/MaterialEntityItem.ts b/src/domain/entities/MaterialEntityItem.ts index 42eecda4..610edf49 100644 --- a/src/domain/entities/MaterialEntityItem.ts +++ b/src/domain/entities/MaterialEntityItem.ts @@ -11,9 +11,8 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; -import PropertyFlags from "../shared/PropertyFlags"; import { vec2 } from "../shared/Vec2"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; /*@sdkdoc @@ -281,7 +280,7 @@ class MaterialEntityItem { * @param {PropertyFlags} propertyFlags - The property flags. * @returns {MaterialEntitySubclassData} The Material entity properties and the number of bytes read. */ - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): MaterialEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): MaterialEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -293,7 +292,7 @@ class MaterialEntityItem { const textDecoder = new TextDecoder(); let materialURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -305,19 +304,19 @@ class MaterialEntityItem { } let materialMappingMode: MaterialMappingMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_MAPPING_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_MODE)) { materialMappingMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let priority: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_PRIORITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_PRIORITY)) { priority = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; } let parentMaterialName: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PARENT_MATERIAL_NAME)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PARENT_MATERIAL_NAME)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -329,7 +328,7 @@ class MaterialEntityItem { } let materialMappingPos: vec2 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_MAPPING_POS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)) { materialMappingPos = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN) @@ -338,7 +337,7 @@ class MaterialEntityItem { } let materialMappingScale: vec2 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_MAPPING_SCALE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_SCALE)) { materialMappingScale = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN) @@ -347,13 +346,13 @@ class MaterialEntityItem { } let materialMappingRot: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_MAPPING_ROT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_ROT)) { materialMappingRot = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let materialData: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_DATA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_DATA)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -365,7 +364,7 @@ class MaterialEntityItem { } let materialRepeat: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MATERIAL_REPEAT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MATERIAL_REPEAT)) { materialRepeat = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } diff --git a/src/domain/entities/ModelEntityItem.ts b/src/domain/entities/ModelEntityItem.ts index 8a68786d..4e8a0f65 100644 --- a/src/domain/entities/ModelEntityItem.ts +++ b/src/domain/entities/ModelEntityItem.ts @@ -13,23 +13,22 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import type { color } from "../shared/Color"; import GLMHelpers from "../shared/GLMHelpers"; -import PropertyFlags from "../shared/PropertyFlags"; import type { quat } from "../shared/Quat"; import ShapeType from "../shared/ShapeType"; import type { vec3 } from "../shared/Vec3"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; type AnimationProperties = { - animationURL: string | undefined; - animationAllowTranslation: boolean | undefined; - animationFPS: number | undefined; - animationFrameIndex: number | undefined; - animationPlaying: boolean | undefined; - animationLoop: boolean | undefined; - animationFirstFrame: number | undefined; - animationLastFrame: number | undefined; - animationHold: boolean | undefined; + url: string | undefined; + allowTranslation: boolean | undefined; + fps: number | undefined; + currentFrame: number | undefined; + running: boolean | undefined; + loop: boolean | undefined; + firstFrame: number | undefined; + lastFrame: number | undefined; + hold: boolean | undefined; }; type ModelEntitySubclassProperties = { @@ -69,20 +68,20 @@ class ModelEntityItem { /*@sdkdoc * An animation is configured by the following properties. * @typedef {object} AnimationProperties - * @property {string|undefined} animationURL="" - The URL of the glTF or FBX file that has the animation. glTF files may be + * @property {string|undefined} url="" - The URL of the glTF or FBX file that has the animation. glTF files may be * in JSON or binary format (".gltf" or ".glb" URLs respectively). - * @property {boolean|undefined} animationAllowTranslation=true - true to enable translations contained in the + * @property {boolean|undefined} allowTranslation=true - true to enable translations contained in the * animation to be played, false to disable translations. - * @property {number|undefined} animationFPS=30 - The speed in frames/s that the animation is played at. - * @property {number|undefined} animationFirstFrame=0 - The first frame to play in the animation. - * @property {number|undefined} animationLastFrame=100000 - The last frame to play in the animation. - * @property {number|undefined} animationFrameIndex=0 - The current frame being played in the animation. - * @property {boolean|undefined} animationPlaning=false - true if the animation should play, + * @property {number|undefined} fps=30 - The speed in frames/s that the animation is played at. + * @property {number|undefined} firstFrame=0 - The first frame to play in the animation. + * @property {number|undefined} lastFrame=100000 - The last frame to play in the animation. + * @property {number|undefined} currentFrame=0 - The current frame being played in the animation. + * @property {boolean|undefined} running=false - true if the animation should run, * false if it shouldn't. - * @property {boolean|undefined} animationLoop=true - true if the animation is continuously repeated in a + * @property {boolean|undefined} loop=true - true if the animation is continuously repeated in a * loop, false if it isn't. - * @property {boolean|undefined} animationHold=false - true if the rotations and translations of the last - * frame played are maintained when the animation stops playing, false if they aren't. + * @property {boolean|undefined} hold=false - true if the rotations and translations of the last + * frame played are maintained when the animation stops running, false if they aren't. */ /*@sdkdoc @@ -142,7 +141,7 @@ class ModelEntityItem { * @returns {ModelEntitySubclassData} The Model entity properties and the number of bytes read. */ // eslint-disable-next-line max-len - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): ModelEntitySubclassData { // eslint-disable-line class-methods-use-this + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): ModelEntitySubclassData { // eslint-disable-line class-methods-use-this // C++ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -154,13 +153,13 @@ class ModelEntityItem { const textDecoder = new TextDecoder(); let shapeType: ShapeType | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SHAPE_TYPE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SHAPE_TYPE)) { shapeType = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let compoundShapeURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COMPOUND_SHAPE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COMPOUND_SHAPE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -174,7 +173,7 @@ class ModelEntityItem { } let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -184,7 +183,7 @@ class ModelEntityItem { } let textures: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXTURES)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXTURES)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -198,7 +197,7 @@ class ModelEntityItem { } let modelURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MODEL_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MODEL_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -212,7 +211,7 @@ class ModelEntityItem { } let modelScale: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MODEL_SCALE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MODEL_SCALE)) { modelScale = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -222,7 +221,7 @@ class ModelEntityItem { } let jointRotationsSet: boolean[] | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_JOINT_ROTATIONS_SET)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -243,7 +242,7 @@ class ModelEntityItem { } let jointRotations: quat[] | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_JOINT_ROTATIONS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -261,7 +260,7 @@ class ModelEntityItem { } let jointTranslationsSet: boolean[] | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_JOINT_TRANSLATIONS_SET)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS_SET)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -282,7 +281,7 @@ class ModelEntityItem { } let jointTranslations: vec3[] | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_JOINT_TRANSLATIONS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -303,19 +302,19 @@ class ModelEntityItem { } let relayParentJoints: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RELAY_PARENT_JOINTS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RELAY_PARENT_JOINTS)) { relayParentJoints = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let groupCulled: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GROUP_CULLED)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GROUP_CULLED)) { groupCulled = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let blendShapeCoefficients: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BLENDSHAPE_COEFFICIENTS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BLENDSHAPE_COEFFICIENTS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -329,70 +328,70 @@ class ModelEntityItem { } let useOriginalPivot: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_USE_ORIGINAL_PIVOT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_USE_ORIGINAL_PIVOT)) { useOriginalPivot = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } - let animationURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_URL)) { + let url: string | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { - animationURL = textDecoder.decode( + url = textDecoder.decode( new Uint8Array(data.buffer, data.byteOffset + dataPosition, length) ); dataPosition += length; } else { - animationURL = ""; + url = ""; } } - let animationAllowTranslation: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_ALLOW_TRANSLATION)) { - animationAllowTranslation = Boolean(data.getUint8(dataPosition)); + let allowTranslation: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_ALLOW_TRANSLATION)) { + allowTranslation = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } - let animationFPS: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_FPS)) { - animationFPS = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); + let fps: number | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_FPS)) { + fps = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } - let animationFrameIndex: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_FRAME_INDEX)) { - animationFrameIndex = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); + let currentFrame: number | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_FRAME_INDEX)) { + currentFrame = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } - let animationPlaying: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_PLAYING)) { - animationPlaying = Boolean(data.getUint8(dataPosition)); + let running: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_PLAYING)) { + running = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } - let animationLoop: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_LOOP)) { - animationLoop = Boolean(data.getUint8(dataPosition)); + let loop: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)) { + loop = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } - let animationFirstFrame: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_FIRST_FRAME)) { - animationFirstFrame = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); + let firstFrame: number | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_FIRST_FRAME)) { + firstFrame = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } - let animationLastFrame: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_LAST_FRAME)) { - animationLastFrame = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); + let lastFrame: number | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_LAST_FRAME)) { + lastFrame = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } - let animationHold: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANIMATION_HOLD)) { - animationHold = Boolean(data.getUint8(dataPosition)); + let hold: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)) { + hold = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } @@ -414,15 +413,15 @@ class ModelEntityItem { blendShapeCoefficients, useOriginalPivot, animation: { - animationURL, - animationAllowTranslation, - animationFPS, - animationFrameIndex, - animationPlaying, - animationLoop, - animationFirstFrame, - animationLastFrame, - animationHold + url, + allowTranslation, + fps, + currentFrame, + running, + loop, + firstFrame, + lastFrame, + hold } } }; diff --git a/src/domain/entities/ParticleEffectEntityItem.ts b/src/domain/entities/ParticleEffectEntityItem.ts index 3a0ce6e1..d68b698f 100644 --- a/src/domain/entities/ParticleEffectEntityItem.ts +++ b/src/domain/entities/ParticleEffectEntityItem.ts @@ -13,11 +13,10 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import type { color } from "../shared/Color"; import GLMHelpers from "../shared/GLMHelpers"; -import PropertyFlags from "../shared/PropertyFlags"; import { quat } from "../shared/Quat"; import ShapeType from "../shared/ShapeType"; import { vec3 } from "../shared/Vec3"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import PulsePropertyGroup from "./PulsePropertyGroup"; @@ -203,7 +202,7 @@ class ParticleEffectEntityItem { * @param {PropertyFlags} propertyFlags - The property flags. * @returns {ParticleEffectEntitySubclassData} The ParticleEffect entity properties and the number of bytes read. */ - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): ParticleEffectEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): ParticleEffectEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -215,13 +214,13 @@ class ParticleEffectEntityItem { const textDecoder = new TextDecoder(); let shapeType: ShapeType | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SHAPE_TYPE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SHAPE_TYPE)) { shapeType = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let compoundShapeURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COMPOUND_SHAPE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COMPOUND_SHAPE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -233,7 +232,7 @@ class ParticleEffectEntityItem { } let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -243,7 +242,7 @@ class ParticleEffectEntityItem { } let alpha: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA)) { alpha = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -253,7 +252,7 @@ class ParticleEffectEntityItem { dataPosition += pulseProperties.bytesRead; let textures: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXTURES)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXTURES)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -265,49 +264,49 @@ class ParticleEffectEntityItem { } let maxParticles: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MAX_PARTICLES)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MAX_PARTICLES)) { maxParticles = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let lifespan: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LIFESPAN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LIFESPAN)) { lifespan = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let isEmitting: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMITTING_PARTICLES)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMITTING_PARTICLES)) { isEmitting = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let emitRate: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMIT_RATE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMIT_RATE)) { emitRate = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let emitSpeed: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMIT_SPEED)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMIT_SPEED)) { emitSpeed = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let speedSpread: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SPEED_SPREAD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SPEED_SPREAD)) { speedSpread = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let emitOrientation: quat | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMIT_ORIENTATION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMIT_ORIENTATION)) { emitOrientation = GLMHelpers.unpackOrientationQuatFromBytes(data, dataPosition); dataPosition += 8; } let emitDimensions: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMIT_DIMENSIONS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMIT_DIMENSIONS)) { emitDimensions = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -317,37 +316,37 @@ class ParticleEffectEntityItem { } let emitRadiusStart: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMIT_RADIUS_START)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMIT_RADIUS_START)) { emitRadiusStart = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let polarStart: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_POLAR_START)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_POLAR_START)) { polarStart = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let polarFinish: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_POLAR_FINISH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_POLAR_FINISH)) { polarFinish = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let azimuthStart: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_AZIMUTH_START)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_AZIMUTH_START)) { azimuthStart = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let azimuthFinish: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_AZIMUTH_FINISH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_AZIMUTH_FINISH)) { azimuthFinish = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let emitAcceleration: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMIT_ACCELERATION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMIT_ACCELERATION)) { emitAcceleration = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -357,7 +356,7 @@ class ParticleEffectEntityItem { } let accelerationSpread: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ACCELERATION_SPREAD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ACCELERATION_SPREAD)) { accelerationSpread = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -367,31 +366,31 @@ class ParticleEffectEntityItem { } let particleRadius: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PARTICLE_RADIUS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PARTICLE_RADIUS)) { particleRadius = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let radiusSpread: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RADIUS_SPREAD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RADIUS_SPREAD)) { radiusSpread = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let radiusStart: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RADIUS_START)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RADIUS_START)) { radiusStart = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let radiusFinish: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RADIUS_FINISH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RADIUS_FINISH)) { radiusFinish = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let colorSpread: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR_SPREAD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR_SPREAD)) { colorSpread = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -401,7 +400,7 @@ class ParticleEffectEntityItem { } let colorStart: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR_START)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR_START)) { colorStart = { red: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), green: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -411,7 +410,7 @@ class ParticleEffectEntityItem { } let colorFinish: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR_FINISH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR_FINISH)) { colorFinish = { red: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), green: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -421,55 +420,55 @@ class ParticleEffectEntityItem { } let alphaSpread: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA_SPREAD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA_SPREAD)) { alphaSpread = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let alphaStart: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA_START)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA_START)) { alphaStart = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let alphaFinish: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA_FINISH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA_FINISH)) { alphaFinish = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let emitterShouldTrail: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EMITTER_SHOULD_TRAIL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EMITTER_SHOULD_TRAIL)) { emitterShouldTrail = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let particleSpin: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PARTICLE_SPIN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PARTICLE_SPIN)) { particleSpin = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let spinSpread: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SPIN_SPREAD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SPIN_SPREAD)) { spinSpread = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let spinStart: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SPIN_START)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SPIN_START)) { spinStart = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let spinFinish: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SPIN_FINISH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SPIN_FINISH)) { spinFinish = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let rotateWithEntity: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PARTICLE_ROTATE_WITH_ENTITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PARTICLE_ROTATE_WITH_ENTITY)) { rotateWithEntity = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } diff --git a/src/domain/entities/PolyLineEntityItem.ts b/src/domain/entities/PolyLineEntityItem.ts index fba0b74e..313205d2 100644 --- a/src/domain/entities/PolyLineEntityItem.ts +++ b/src/domain/entities/PolyLineEntityItem.ts @@ -11,8 +11,7 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; // WEBRTC TODO: Replace Record with PolyLineEntityItem's special properties. @@ -30,7 +29,7 @@ type PolyLineEntitySubclassData = { class PolyLineEntityItem { // C++ class PolyLineEntityItem : public EntityItem - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): PolyLineEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): PolyLineEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -39,12 +38,12 @@ class PolyLineEntityItem { let dataPosition = position; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { // WEBRTC TODO: Read color property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXTURES)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXTURES)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -54,7 +53,7 @@ class PolyLineEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LINE_POINTS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LINE_POINTS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -64,7 +63,7 @@ class PolyLineEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_STROKE_WIDTHS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_STROKE_WIDTHS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -74,7 +73,7 @@ class PolyLineEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_STROKE_NORMALS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_STROKE_NORMALS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -84,7 +83,7 @@ class PolyLineEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_STROKE_COLORS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_STROKE_COLORS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -94,17 +93,17 @@ class PolyLineEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_IS_UV_MODE_STRETCH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_IS_UV_MODE_STRETCH)) { // WEBRTC TODO: Read isUVModeStretch property. dataPosition += 1; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LINE_GLOW)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LINE_GLOW)) { // WEBRTC TODO: Read lineGlow property. dataPosition += 1; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LINE_FACE_CAMERA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LINE_FACE_CAMERA)) { // WEBRTC TODO: Read lineFaceCamera property. dataPosition += 1; } diff --git a/src/domain/entities/PolyVoxEntityItem.ts b/src/domain/entities/PolyVoxEntityItem.ts index c40ff330..885d3867 100644 --- a/src/domain/entities/PolyVoxEntityItem.ts +++ b/src/domain/entities/PolyVoxEntityItem.ts @@ -11,8 +11,7 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; // WEBRTC TODO: Replace Record with PolyVoxEntityItem's special properties. @@ -30,7 +29,7 @@ type PolyVoxEntitySubclassData = { class PolyVoxEntityItem { // C++ class PolyVoxEntityItem : public EntityItem - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): PolyVoxEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): PolyVoxEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -39,12 +38,12 @@ class PolyVoxEntityItem { let dataPosition = position; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_VOXEL_VOLUME_SIZE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_VOXEL_VOLUME_SIZE)) { // WEBRTC TODO: Read voxelVolumeSize property. dataPosition += 12; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_VOXEL_DATA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_VOXEL_DATA)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -56,12 +55,12 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_VOXEL_SURFACE_STYLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_VOXEL_SURFACE_STYLE)) { // WEBRTC TODO: Read voxelSurfaceStyle property. dataPosition += 2; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_X_TEXTURE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_X_TEXTURE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -71,7 +70,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_Y_TEXTURE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_Y_TEXTURE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -81,7 +80,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_Z_TEXTURE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_Z_TEXTURE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -91,7 +90,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_X_N_NEIGHBOR_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_X_N_NEIGHBOR_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -101,7 +100,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_Y_N_NEIGHBOR_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_Y_N_NEIGHBOR_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -111,7 +110,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_Z_N_NEIGHBOR_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_Z_N_NEIGHBOR_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -121,7 +120,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_X_P_NEIGHBOR_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_X_P_NEIGHBOR_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -131,7 +130,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_Y_P_NEIGHBOR_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_Y_P_NEIGHBOR_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -141,7 +140,7 @@ class PolyVoxEntityItem { } } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_Z_P_NEIGHBOR_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_Z_P_NEIGHBOR_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; diff --git a/src/domain/entities/PulsePropertyGroup.ts b/src/domain/entities/PulsePropertyGroup.ts index 87d534a4..8355c3f0 100644 --- a/src/domain/entities/PulsePropertyGroup.ts +++ b/src/domain/entities/PulsePropertyGroup.ts @@ -9,8 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; + + +// NOTE: Pulse properties are deprecated and so are not implemented in the Web SDK. // WEBRTC TODO: Replace Record with PulsePropertyGroupProperties. @@ -25,7 +27,7 @@ class PulsePropertyGroup { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): PulsePropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): PulsePropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int PulsePropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -34,27 +36,27 @@ class PulsePropertyGroup { let dataPosition = position; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PULSE_MIN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PULSE_MIN)) { // WEBRTC TODO: Read pulseMin property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PULSE_MAX)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PULSE_MAX)) { // WEBRTC TODO: Read pulseMax property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PULSE_PERIOD)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PULSE_PERIOD)) { // WEBRTC TODO: Read pulsePeriod property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PULSE_COLOR_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PULSE_COLOR_MODE)) { // WEBRTC TODO: Read pulseColorMode property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PULSE_ALPHA_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PULSE_ALPHA_MODE)) { // WEBRTC TODO: Read pulseAlphaMode property. dataPosition += 4; } diff --git a/src/domain/entities/RingGizmoPropertyGroup.ts b/src/domain/entities/RingGizmoPropertyGroup.ts index 0b30fb59..d2ab3d31 100644 --- a/src/domain/entities/RingGizmoPropertyGroup.ts +++ b/src/domain/entities/RingGizmoPropertyGroup.ts @@ -9,8 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; // WEBRTC TODO: Replace Record with RingGizmoPropertyGroupProperties. @@ -25,7 +24,7 @@ class RingGizmoPropertyGroup { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): RingGizmoPropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): RingGizmoPropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int RingGizmoPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -34,92 +33,92 @@ class RingGizmoPropertyGroup { let dataPosition = position; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_START_ANGLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_START_ANGLE)) { // WEBRTC TODO: Read startAngle property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_END_ANGLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_END_ANGLE)) { // WEBRTC TODO: Read endAngle property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_INNER_RADIUS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_INNER_RADIUS)) { // WEBRTC TODO: Read innerRadius property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_INNER_START_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_INNER_START_COLOR)) { // WEBRTC TODO: Read innerStartColor property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_INNER_END_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_INNER_END_COLOR)) { // WEBRTC TODO: Read innerEndColor property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_OUTER_START_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_OUTER_START_COLOR)) { // WEBRTC TODO: Read outerStartColor property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_OUTER_END_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_OUTER_END_COLOR)) { // WEBRTC TODO: Read outerEndColor property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_INNER_START_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_INNER_START_ALPHA)) { // WEBRTC TODO: Read innerStartAlpha property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_INNER_END_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_INNER_END_ALPHA)) { // WEBRTC TODO: Read innerEndAlpha property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_OUTER_START_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_OUTER_START_ALPHA)) { // WEBRTC TODO: Read outerStartAlpha property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_OUTER_END_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_OUTER_END_ALPHA)) { // WEBRTC TODO: Read outerEndAlpha property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAS_TICK_MARKS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAS_TICK_MARKS)) { // WEBRTC TODO: Read hasTickMarks property. dataPosition += 1; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MAJOR_TICK_MARKS_ANGLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MAJOR_TICK_MARKS_ANGLE)) { // WEBRTC TODO: Read majorTickMarksAngle property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MINOR_TICK_MARKS_ANGLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MINOR_TICK_MARKS_ANGLE)) { // WEBRTC TODO: Read minorTickMarksAngle property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MAJOR_TICK_MARKS_LENGTH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MAJOR_TICK_MARKS_LENGTH)) { // WEBRTC TODO: Read majorTickMarksLength property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MINOR_TICK_MARKS_LENGTH)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MINOR_TICK_MARKS_LENGTH)) { // WEBRTC TODO: Read minorTickMarksLength property. dataPosition += 4; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MAJOR_TICK_MARKS_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MAJOR_TICK_MARKS_COLOR)) { // WEBRTC TODO: Read majorTickMarksColor property. dataPosition += 3; } - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MINOR_TICK_MARKS_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MINOR_TICK_MARKS_COLOR)) { // WEBRTC TODO: Read minorTickMarksColor property. dataPosition += 3; } diff --git a/src/domain/entities/ShapeEntityItem.ts b/src/domain/entities/ShapeEntityItem.ts index f535b79e..54b521b7 100644 --- a/src/domain/entities/ShapeEntityItem.ts +++ b/src/domain/entities/ShapeEntityItem.ts @@ -12,8 +12,7 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import type { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import PulsePropertyGroup from "./PulsePropertyGroup"; @@ -115,7 +114,7 @@ class ShapeEntityItem { * @returns {ShapeEntitySubclassData} The Shape entity properties and the number of bytes read. */ // eslint-disable-next-line class-methods-use-this - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): ShapeEntitySubclassData { // eslint-disable-line max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): ShapeEntitySubclassData { // eslint-disable-line max-len // C++ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -125,7 +124,7 @@ class ShapeEntityItem { let dataPosition = position; let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -135,7 +134,7 @@ class ShapeEntityItem { } let alpha: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA)) { alpha = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -147,7 +146,7 @@ class ShapeEntityItem { const textDecoder = new TextDecoder(); let shape: Shape | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SHAPE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SHAPE)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; diff --git a/src/domain/entities/SkyboxPropertyGroup.ts b/src/domain/entities/SkyboxPropertyGroup.ts index 88cb9835..6903f11d 100644 --- a/src/domain/entities/SkyboxPropertyGroup.ts +++ b/src/domain/entities/SkyboxPropertyGroup.ts @@ -10,9 +10,10 @@ // import UDT from "../networking/udt/UDT"; +import OctreePacketData, { OctreePacketContext } from "../octree/OctreePacketData"; import type { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; +import { ZoneEntityProperties } from "./ZoneEntityItem"; type SkyboxProperties = { @@ -34,6 +35,12 @@ type SkyboxPropertyGroupSubclassData = { class SkyboxPropertyGroup { // C++ class SkyboxPropertyGroup : public PropertyGroup + static readonly #_PROPERTY_MAP = new Map([ // Maps property names to EntityPropertyList values. + // C++ EntityPropertyFlags SkyboxPropertyGroup::getChangedProperties() const + ["color", EntityPropertyList.PROP_SKYBOX_COLOR], + ["url", EntityPropertyList.PROP_SKYBOX_URL] + ]); + /*@sdkdoc * Defines the skybox of a zone. * @typedef {object} SkyboxProperties @@ -58,7 +65,7 @@ class SkyboxPropertyGroup { * @param {PropertyFlags} propertyFlags - The property flags. * @returns {SkyboxPropertyGroupSubclassData} The Zone entity's skybox properties and the number of bytes read. */ - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): SkyboxPropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): SkyboxPropertyGroupSubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int SkyboxPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -70,7 +77,7 @@ class SkyboxPropertyGroup { const textDecoder = new TextDecoder(); let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SKYBOX_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -80,7 +87,7 @@ class SkyboxPropertyGroup { } let url: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SKYBOX_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SKYBOX_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -102,6 +109,65 @@ class SkyboxPropertyGroup { /* eslint-enable @typescript-eslint/no-magic-numbers */ } + /*@devdoc + * Gets property flags for Zone skybox properties set in the entity properties object passed in, + * assuming that there may be changes. + *

Note: The SDK doesn't maintain its own entity tree so it doesn't calculate whether the property values have actually + * changed.

+ * @param {EntityPropertyFlags} properties - A set of entity properties and values. + * @returns {EntityPropertyFlags} Flags for all the Zone skybox properties included in the entity + * properties object. + */ + static getChangedProperties(properties: ZoneEntityProperties): EntityPropertyFlags { + // C++ EntityPropertyFlags getChangedProperties() const + const changedProperties = new EntityPropertyFlags(); + if (properties.skybox) { + const propertyNames = Object.keys(properties.skybox); + for (const propertyName of propertyNames) { + const propertyValue = SkyboxPropertyGroup.#_PROPERTY_MAP.get(propertyName); + if (propertyValue !== undefined) { + changedProperties.setHasProperty(propertyValue, true); + } + } + } + return changedProperties; + } + + /*@devdoc + * Writes SkyboxPropertyGroup properties to a buffer as are able to fit. + * @param {DataView} data - The buffer to write to. + * @param {number} dataPosition - The position to start writing at. + * @param {EntityProperties} entityProperties - A set of entity properties and values. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendToEditPacket(data: DataView, dataPosition: number, entityProperties: ZoneEntityProperties, + packetContext: OctreePacketContext): number { + // C++ bool appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& requestedProperties, + // EntityPropertyFlags & propertyFlags, EntityPropertyFlags& propertiesDidntFit, int& propertyCount, + // OctreeElement:: AppendState & appendState) const + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + + let bytesWritten = 0; + const requestedProperties = packetContext.propertiesToWrite; + + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR)) { + bytesWritten += OctreePacketData.appendColorValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_SKYBOX_COLOR, + entityProperties.skybox!.color!, packetContext); + } + if (requestedProperties.getHasProperty(EntityPropertyList.PROP_SKYBOX_URL)) { + bytesWritten += OctreePacketData.appendStringValue(data, dataPosition + bytesWritten, + EntityPropertyList.PROP_SKYBOX_URL, + entityProperties.skybox!.url!, packetContext); + } + + return bytesWritten; + + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } + } export default SkyboxPropertyGroup; diff --git a/src/domain/entities/TextEntityItem.ts b/src/domain/entities/TextEntityItem.ts index 3e587151..9ddf03dd 100644 --- a/src/domain/entities/TextEntityItem.ts +++ b/src/domain/entities/TextEntityItem.ts @@ -12,8 +12,7 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import PulsePropertyGroup from "./PulsePropertyGroup"; /*@sdkdoc @@ -136,7 +135,7 @@ class TextEntityItem { * @param {PropertyFlags} propertyFlags - The property flags. * @returns {TextEntitySubclassData} The Text entity properties and the number of bytes read. */ - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): TextEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): TextEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -152,7 +151,7 @@ class TextEntityItem { dataPosition += pulseProperties.bytesRead; let text: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXT)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -164,13 +163,13 @@ class TextEntityItem { } let lineHeight: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LINE_HEIGHT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LINE_HEIGHT)) { lineHeight = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let textColor: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXT_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXT_COLOR)) { textColor = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -180,13 +179,13 @@ class TextEntityItem { } let textAlpha: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXT_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXT_ALPHA)) { textAlpha = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let backgroundColor: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BACKGROUND_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BACKGROUND_COLOR)) { backgroundColor = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -196,43 +195,43 @@ class TextEntityItem { } let backgroundAlpha: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BACKGROUND_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BACKGROUND_ALPHA)) { backgroundAlpha = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let leftMargin: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LEFT_MARGIN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LEFT_MARGIN)) { leftMargin = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let rightMargin: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RIGHT_MARGIN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RIGHT_MARGIN)) { rightMargin = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let topMargin: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TOP_MARGIN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TOP_MARGIN)) { topMargin = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let bottomMargin: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BOTTOM_MARGIN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BOTTOM_MARGIN)) { bottomMargin = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let unlit: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_UNLIT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_UNLIT)) { unlit = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let font: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_FONT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_FONT)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -244,7 +243,7 @@ class TextEntityItem { } let textEffect: TextEffect | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXT_EFFECT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXT_EFFECT)) { const value = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); switch (value) { case 0: @@ -267,7 +266,7 @@ class TextEntityItem { } let textEffectColor: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXT_EFFECT_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXT_EFFECT_COLOR)) { textEffectColor = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -277,13 +276,13 @@ class TextEntityItem { } let textEffectThickness: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXT_EFFECT_THICKNESS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXT_EFFECT_THICKNESS)) { textEffectThickness = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let textAlignment: TextAlignment | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_TEXT_ALIGNMENT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_TEXT_ALIGNMENT)) { const value = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); switch (value) { case 0: @@ -330,4 +329,5 @@ class TextEntityItem { } export default TextEntityItem; +export { TextAlignment, TextEffect }; export type { TextEntitySubclassData, TextEntityProperties }; diff --git a/src/domain/entities/WebEntityItem.ts b/src/domain/entities/WebEntityItem.ts index 371a023e..7a559a49 100644 --- a/src/domain/entities/WebEntityItem.ts +++ b/src/domain/entities/WebEntityItem.ts @@ -12,8 +12,7 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import type { color } from "../shared/Color"; -import PropertyFlags from "../shared/PropertyFlags"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import PulsePropertyGroup from "./PulsePropertyGroup"; @@ -110,7 +109,7 @@ class WebEntityItem { * @param {PropertyFlags} propertyFlags - The property flags. * @returns {WebEntitySubclassData} The Web entity properties and the number of bytes read. */ - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): WebEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): WebEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -122,7 +121,7 @@ class WebEntityItem { const textDecoder = new TextDecoder(); let color: color | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLOR)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLOR)) { color = { red: data.getUint8(dataPosition), green: data.getUint8(dataPosition + 1), @@ -132,7 +131,7 @@ class WebEntityItem { } let alpha: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ALPHA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ALPHA)) { alpha = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -142,7 +141,7 @@ class WebEntityItem { dataPosition += pulseProperties.bytesRead; let sourceURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SOURCE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SOURCE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -154,13 +153,13 @@ class WebEntityItem { } let dpi: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_DPI)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_DPI)) { dpi = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; } let scriptURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SCRIPT_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SCRIPT_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -172,31 +171,31 @@ class WebEntityItem { } let maxFPS: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MAX_FPS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MAX_FPS)) { maxFPS = data.getUint8(dataPosition); dataPosition += 1; } let inputMode: WebInputMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_INPUT_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_INPUT_MODE)) { inputMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let showKeyboardFocusHighlight: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT)) { showKeyboardFocusHighlight = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let useBackground: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_WEB_USE_BACKGROUND)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_WEB_USE_BACKGROUND)) { useBackground = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let userAgent: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_USER_AGENT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_USER_AGENT)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { diff --git a/src/domain/entities/ZoneEntityItem.ts b/src/domain/entities/ZoneEntityItem.ts index 901b072a..68755b11 100644 --- a/src/domain/entities/ZoneEntityItem.ts +++ b/src/domain/entities/ZoneEntityItem.ts @@ -13,11 +13,10 @@ import { CommonEntityProperties } from "../networking/packets/EntityData"; import UDT from "../networking/udt/UDT"; import AvatarPriorityMode from "../shared/AvatarPriorityMode"; import ComponentMode from "../shared/ComponentMode"; -import PropertyFlags from "../shared/PropertyFlags"; import ShapeType from "../shared/ShapeType"; import AmbientLightPropertyGroup, { AmbientLightProperties } from "./AmbientLightPropertyGroup"; import BloomPropertyGroup, { BloomProperties } from "./BloomPropertyGroup"; -import { EntityPropertyFlags } from "./EntityPropertyFlags"; +import EntityPropertyFlags, { EntityPropertyList } from "./EntityPropertyFlags"; import HazePropertyGroup, { HazeProperties } from "./HazePropertyGroup"; import KeyLightPropertyGroup, { KeyLightProperties } from "./KeyLightPropertyGroup"; import SkyboxPropertyGroup, { SkyboxProperties } from "./SkyboxPropertyGroup"; @@ -116,7 +115,7 @@ class ZoneEntityItem { * @param {PropertyFlags} propertyFlags - The property flags. * @returns {ZoneEntitySubclassData} The Zone entity properties and the number of bytes read. */ - static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: PropertyFlags): ZoneEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len + static readEntitySubclassDataFromBuffer(data: DataView, position: number, propertyFlags: EntityPropertyFlags): ZoneEntitySubclassData { // eslint-disable-line class-methods-use-this, max-len // C++ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, // ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, // bool& somethingChanged) @@ -128,13 +127,13 @@ class ZoneEntityItem { const textDecoder = new TextDecoder(); let shapeType: ShapeType | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SHAPE_TYPE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SHAPE_TYPE)) { shapeType = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let compoundShapeURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COMPOUND_SHAPE_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COMPOUND_SHAPE_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -162,19 +161,19 @@ class ZoneEntityItem { dataPosition += bloomProperties.bytesRead; let flyingAllowed: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_FLYING_ALLOWED)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_FLYING_ALLOWED)) { flyingAllowed = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let ghostingAllowed: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GHOSTING_ALLOWED)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GHOSTING_ALLOWED)) { ghostingAllowed = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let filterURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_FILTER_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_FILTER_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -186,43 +185,43 @@ class ZoneEntityItem { } let keyLightMode: ComponentMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_KEY_LIGHT_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_KEY_LIGHT_MODE)) { keyLightMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let ambientLightMode: ComponentMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_AMBIENT_LIGHT_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_MODE)) { ambientLightMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let skyboxMode: ComponentMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SKYBOX_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SKYBOX_MODE)) { skyboxMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let hazeMode: ComponentMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HAZE_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HAZE_MODE)) { hazeMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let bloomMode: ComponentMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BLOOM_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BLOOM_MODE)) { bloomMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let avatarPriority: AvatarPriorityMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_AVATAR_PRIORITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_AVATAR_PRIORITY)) { avatarPriority = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let screenshare: ComponentMode | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SCREENSHARE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SCREENSHARE)) { screenshare = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } diff --git a/src/domain/networking/LimitedNodeList.ts b/src/domain/networking/LimitedNodeList.ts index 0db44885..41e4314d 100644 --- a/src/domain/networking/LimitedNodeList.ts +++ b/src/domain/networking/LimitedNodeList.ts @@ -162,7 +162,10 @@ class LimitedNodeList { #_packetVersionMismatch = new SignalEmitter(); #_permissions = new NodePermissions(); + #_canRezChanged = new SignalEmitter(); + #_canRezTmpChanged = new SignalEmitter(); #_canKickChanged = new SignalEmitter(); + #_canGetAndSetPrivateUserDataChanged = new SignalEmitter(); constructor(contextID: number) { @@ -727,6 +730,17 @@ class LimitedNodeList { // WEBRTC TODO: Address further C++ code. + if (originalPermissions.can(NodePermissions.Permission.canRezPermanentEntities) + !== newPermissions.can(NodePermissions.Permission.canRezPermanentEntities)) { + this.#_canRezChanged.emit(this.#_permissions.can(NodePermissions.Permission.canRezPermanentEntities)); + } + if (originalPermissions.can(NodePermissions.Permission.canRezTemporaryEntities) + !== newPermissions.can(NodePermissions.Permission.canRezTemporaryEntities)) { + this.#_canRezTmpChanged.emit(this.#_permissions.can(NodePermissions.Permission.canRezTemporaryEntities)); + } + + // WEBRTC TODO: Address further C++ code. + if (originalPermissions.can(NodePermissions.Permission.canKick) !== newPermissions.can(NodePermissions.Permission.canKick)) { this.#_canKickChanged.emit(this.#_permissions.can(NodePermissions.Permission.canKick)); @@ -734,11 +748,41 @@ class LimitedNodeList { // WEBRTC TODO: Address further C++ code. + if (originalPermissions.can(NodePermissions.Permission.canGetAndSetPrivateUserData) + !== newPermissions.can(NodePermissions.Permission.canGetAndSetPrivateUserData)) { + this.#_canGetAndSetPrivateUserDataChanged.emit( + this.#_permissions.can(NodePermissions.Permission.canGetAndSetPrivateUserData) + ); + } + + // WEBRTC TODO: Address further C++ code. + + } + + /*@devdoc + * Gets whether the node has permissions to rez (create) persistent entities in the domain. + * @returns {boolean} true if the node has permissions to rez persistent entities in the domain, + * false if it doesn't. + */ + getThisNodeCanRez(): boolean { + // C++ bool getThisNodeCanRez() const + return this.#_permissions.can(NodePermissions.Permission.canRezPermanentEntities); + } + + /*@devdoc + * Gets whether the node has permissions to rez (create) temporary entities in the domain. Temporary entities are entities + * with a finite lifetime property value set. + * @returns {boolean} true if the node has permissions on the domain to rez temporary entities, + * false if it doesn't. + */ + getThisNodeCanRezTmp(): boolean { + // C++ bool getThisNodeCanRezTmp() const + return this.#_permissions.can(NodePermissions.Permission.canRezTemporaryEntities); } /*@devdoc - * Gets whether the node has permissions on the domain to kick (ban) users. - * @returns {boolean} true if the node has permissions on the domain to kick (ban) users, false + * Gets whether the node has permissions to kick (ban) users in the domain. + * @returns {boolean} true if the node has permissions to kick (ban) users in the domain, false * if it doesn't. */ getThisNodeCanKick(): boolean { @@ -746,6 +790,16 @@ class LimitedNodeList { return this.#_permissions.can(NodePermissions.Permission.canKick); } + /*@devdoc + * Gets whether the node has permissions to get and set entities' privateUserData properties in the domain. + * @returns {boolean} true if the node has permissions to get and set entities' privateUserData + * properties in the domain, false if it doesn't. + */ + getThisNodeCanGetAndSetPrivateUserData(): boolean { + // C++ bool getThisNodeCanGetAndSetPrivateUserData() const + return this.#_permissions.can(NodePermissions.Permission.canGetAndSetPrivateUserData); + } + /*@devdoc * Triggered when the user client's session UUID changes. @@ -817,7 +871,32 @@ class LimitedNodeList { } /*@devdoc - * Triggered when the node's kick permissions on the domain changes. + * Triggered when the node's rez persistent entities permission changes in the domain. + * @function LimitedNodeList.canRezChanged + * @param {boolean} canRez - true if the node has permission to rez persistent entities on the domain, + * false if it doesn't. + * @returns {Signal} + */ + get canRezChanged(): Signal { + // C++ void canRezChanged(bool canRez) + return this.#_canRezChanged.signal(); + } + + /*@devdoc + * Triggered when the node's rez temporary entities permission changes in the domain. Temporary entities are entities with + * a finite lifetime property value set. + * @function LimitedNodeList.canRezTmpChanged + * @param {boolean} canRezTmp - true if the node has permission to rez temporary entities on the domain, + * false if it doesn't. + * @returns {Signal} + */ + get canRezTmpChanged(): Signal { + // C++ void canRezTmpChanged(bool canRezTmp) + return this.#_canRezTmpChanged.signal(); + } + + /*@devdoc + * Triggered when the node's kick permissions changes in the domain. * @function LimitedNodeList.canKickChanged * @param {boolean} canKick - true if the node has permissions on the domain to kick (ban) users, * false if it doesn't. @@ -828,6 +907,19 @@ class LimitedNodeList { return this.#_canKickChanged.signal(); } + /*@devdoc + * Triggered when the node's permission to get and set entities' privateUserData properties changes in the + * domain. + * @function LimitedNodeList.canGetAndSetPrivateUserDataChanged + * @param {boolean} canGetAndSetPrivateUserData - true if the node has permission to get and set entities' + * privateUserData properties, false if it doesn't. + * @returns {Signal} + */ + get canGetAndSetPrivateUserDataChanged(): Signal { + // C++ void canGetAndSetPrivateUserDataChanged(bool canGetAndSetPrivateUserData) + return this.#_canGetAndSetPrivateUserDataChanged.signal(); + } + protected addNewNode(info: NewNodeInfo): void { // eslint-disable-line class-methods-use-this // C++ void addNewNode(NewNodeInfo info); diff --git a/src/domain/networking/packets/EntityData.ts b/src/domain/networking/packets/EntityData.ts index 9cf8996d..2658b9f0 100644 --- a/src/domain/networking/packets/EntityData.ts +++ b/src/domain/networking/packets/EntityData.ts @@ -9,9 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import { EntityPropertyFlags } from "../../entities/EntityPropertyFlags"; +import { HostType } from "../../entities/EntityItem"; +import EntityPropertyFlags, { EntityPropertyList } from "../../entities/EntityPropertyFlags"; import { EntityType } from "../../entities/EntityTypes"; import GizmoEntityItem, { GizmoEntityProperties, GizmoEntitySubclassData } from "../../entities/GizmoEntityItem"; +import GrabPropertyGroup, { GrabProperties } from "../../entities/GrabPropertyGroup"; import GridEntityItem, { GridEntityProperties, GridEntitySubclassData } from "../../entities/GridEntityItem"; import ImageEntityItem, { ImageEntityProperties, ImageEntitySubclassData } from "../../entities/ImageEntityItem"; import LightEntityItem, { LightEntityProperties, LightEntitySubclassData } from "../../entities/LightEntityItem"; @@ -31,7 +33,6 @@ import assert from "../../shared/assert"; import ByteCountCoded from "../../shared/ByteCountCoded"; import "../../shared/DataViewExtensions"; import GLMHelpers from "../../shared/GLMHelpers"; -import PropertyFlags from "../../shared/PropertyFlags"; import { quat } from "../../shared/Quat"; import Uuid from "../../shared/Uuid"; import { vec3 } from "../../shared/Vec3"; @@ -63,6 +64,7 @@ type CommonEntityProperties = { registrationPoint: vec3 | undefined; created: bigint | undefined; lastEditedBy: Uuid | undefined; + entityHostType: HostType | undefined; // Not sent over the wire. queryAACube: AACube | undefined; canCastShadow: boolean | undefined; renderLayer: number | undefined; @@ -70,26 +72,14 @@ type CommonEntityProperties = { ignorePickIntersection: boolean | undefined; renderWithZones: Uuid[] | undefined; billboardMode: number | undefined; - grabbable: boolean | undefined; - grabKinematic: boolean | undefined; - grabFollowsController: boolean | undefined; - triggerable: boolean | undefined; - grabEquippable: boolean | undefined; - delegateToParent: boolean | undefined; - equippableLeftPositionOffset: vec3 | undefined; - equippableLeftRotationOffset: quat | undefined; - equippableRightPositionOffset: vec3 | undefined; - equippableRightRotationOffset: quat | undefined; - equippableIndicatorURL: string | undefined; - equippableIndicatorScale: vec3 | undefined; - equippableIndicatorOffset: vec3 | undefined; + grab: GrabProperties | undefined; density: number | undefined; velocity: vec3 | undefined; angularVelocity: vec3 | undefined; gravity: vec3 | undefined; acceleration: vec3 | undefined; damping: number | undefined; - angularDampling: number | undefined; + angularDamping: number | undefined; restitution: number | undefined; friction: number | undefined; lifetime: number | undefined; @@ -102,7 +92,7 @@ type CommonEntityProperties = { cloneLifetime: number | undefined; cloneLimit: number | undefined; cloneDynamic: boolean | undefined; - cloneAvatarIdentity: boolean | undefined; + cloneAvatarEntity: boolean | undefined; cloneOriginID: Uuid | undefined; script: string | undefined; scriptTimestamp: bigint | undefined; @@ -182,7 +172,7 @@ const EntityData = new class { *

A property value may be undefined if it couldn't fit in the data packet sent by the server.

* @typedef {object} EntityProperties * - * @property {Uuid} entityItemID - The ID of the entity. + * @property {Uuid} entityItemID - The ID of the entity. Read-only. * @property {EntityType} entityType - The entity's type. It cannot be changed after an entity is created. * @property {bigint} createdFromBuffer - Timestamp for when the entity was created. Expressed in number of microseconds * since Unix epoch. @@ -217,6 +207,8 @@ const EntityData = new class { * since Unix * @property {Uuid|undefined} lastEditedBy - The session ID of the avatar or agent that most recently created or edited * the entity. + * @property {HostType|undefined} entityHostType - How the entity is hosted and sent to others for display. The value can + * only be set at entity creation by {@link EntityServer#addEntity|EntityServer.addEntity}. Read-only. * @property {AACube|undefined} queryAACube - The axis-aligned cube that determines where the entity lives in the entity * server's octree. The cube may be considerably larger than the entity in some situations, e.g., when the entity is * grabbed by an avatar: the position of the entity is determined through avatar mixer updates and so the AA cube is @@ -232,32 +224,7 @@ const EntityData = new class { * within one of the zones in this list. * @property {number|undefined} billboardMode - Whether the entity is billboarded to face the camera. Use the rotation * property to control which axis is facing you. - * @property {boolean|undefined} grabbable - true if the entity can be grabbed, false if it - * can't be. - * @property {boolean|undefined} grabKinematic - true if the entity will be updated in a kinematic manner - * when grabbed; false if it will be grabbed using a tractor action. A kinematic grab will make the item - * appear more tightly held but will cause it to behave poorly when interacting with dynamic entities. - * @property {boolean|undefined} grabFollowsController - true if the entity will follow the motions of - * the hand controller even if the avatar's hand can't get to the implied position, false if it will - * follow the motions of the avatar's hand. This should be set true for tools, pens, etc. and false for - * things meant to decorate the hand. - * @property {boolean|undefined} triggerable - true if the entity will receive calls to trigger - * Controller entity methods, false if it won't. - * @property {boolean|undefined} equippable - true if the entity can be equipped, false if - * it cannot. - * @property {boolean|undefined} delegateToParent - true if when the entity is grabbed, the grab will be - * transferred to its parent entity if there is one; false if the grab won't be transferred, so a child - * entity can be grabbed and moved relative to its parent. - * @property {vec3|undefined} equippableLeftPositionOffset - Positional offset from the left hand, when equipped. - * @property {quat|undefined} equippableLeftRotationOffset - Rotational offset from the left hand, when equipped. - * @property {vec3|undefined} equippableRightPositionOffset - Positional offset from the right hand, when equipped. - * @property {quat|undefined} equippableRightRotationOffset - Rotational offset from the right hand, when equipped. - * @property {string|undefined} equippableIndicatorURL - If non-empty, this model will be used to indicate that an entity - * is equippable, rather than the default. - * @property {vec3|undefined} equippableIndicatorScale - If equippableIndicatorURL is non-empty, this controls the scale - * of the displayed indicator. - * @property {vec3|undefined} equippableIndicatorOffset - If equippableIndicatorURL is non-empty, this controls the - * relative offset of the displayed object from the equippable entity. + * @property {GrabProperties|undefined} grab - Properties that control how the entity behaves when grabbed. * @property {number|undefined} density - The density of the entity in kg/m3, range * 100 – 10000. * Examples: 100 for balsa wood, 10000 for silver. The density is used in conjunction with @@ -274,7 +241,7 @@ const EntityData = new class { * 0.01.0. A higher damping value slows down the entity more quickly. The default value is * for an exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow * to 1/e = 0.368 of its initial value. - * @property {number|undefined} angularDampling - How much the angular velocity of an entity slows down over time, range + * @property {number|undefined} angularDamping - How much the angular velocity of an entity slows down over time, range * 0.0 – 1.0. A higher damping value slows down the entity more quickly. The default value is for an * exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to * 1/e = 0.368 of its initial value. @@ -303,7 +270,7 @@ const EntityData = new class { * any given time. * @property {boolean|undefined} cloneDynamic - true if clones created from this entity will have their * dynamic property set to true, false if they won't. - * @property {boolean|undefined} cloneAvatarIdentity - true if clones created from this entity will be + * @property {boolean|undefined} cloneAvatarEntity - true if clones created from this entity will be * created as avatar entities, false if they won't be. * @property {Uuid|undefined} cloneOriginID - The ID of the entity that this entity was cloned from. * @property {string|undefined} script - The URL of the client entity script, if any, that is attached to the entity. @@ -532,7 +499,7 @@ const EntityData = new class { let encodedData = new DataView(data.buffer, data.byteOffset + dataPosition); dataPosition += codec.decode(encodedData, encodedData.byteLength); - const entityType = codec.data; + const entityType = Number(codec.data); const createdFromBuffer = data.getBigUint64(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 8; @@ -542,18 +509,18 @@ const EntityData = new class { encodedData = new DataView(data.buffer, data.byteOffset + dataPosition); dataPosition += codec.decode(encodedData, encodedData.byteLength); - const updateDelta = codec.data; + const updateDelta = Number(codec.data); encodedData = new DataView(data.buffer, data.byteOffset + dataPosition); dataPosition += codec.decode(encodedData, encodedData.byteLength); - const simulatedDelta = codec.data; + const simulatedDelta = Number(codec.data); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); const encodedFlags = new DataView(data.buffer, data.byteOffset + dataPosition); dataPosition += propertyFlags.decode(encodedFlags, encodedFlags.byteLength); let simOwnerData: ArrayBuffer | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SIMULATION_OWNER)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SIMULATION_OWNER)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -570,7 +537,7 @@ const EntityData = new class { } let parentID: Uuid | null | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PARENT_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PARENT_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -583,19 +550,19 @@ const EntityData = new class { } let parentJointIndex: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PARENT_JOINT_INDEX)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)) { parentJointIndex = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; } let visible: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_VISIBLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_VISIBLE)) { visible = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let name: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_NAME)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_NAME)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -607,13 +574,13 @@ const EntityData = new class { } let locked: boolean | undefined = false; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LOCKED)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LOCKED)) { locked = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let userData: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_USER_DATA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_USER_DATA)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -625,7 +592,7 @@ const EntityData = new class { } let privateUserData: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PRIVATE_USER_DATA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PRIVATE_USER_DATA)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -637,7 +604,7 @@ const EntityData = new class { } let href: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_HREF)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_HREF)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -649,7 +616,7 @@ const EntityData = new class { } let description: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_DESCRIPTION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_DESCRIPTION)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; if (length > 0) { @@ -661,7 +628,7 @@ const EntityData = new class { } let position: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_POSITION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_POSITION)) { position = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -671,7 +638,7 @@ const EntityData = new class { } let dimensions: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_DIMENSIONS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_DIMENSIONS)) { dimensions = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -681,13 +648,13 @@ const EntityData = new class { } let rotation: quat | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ROTATION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ROTATION)) { rotation = GLMHelpers.unpackOrientationQuatFromBytes(data, dataPosition); dataPosition += 8; } let registrationPoint: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_REGISTRATION_POINT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_REGISTRATION_POINT)) { registrationPoint = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -697,13 +664,13 @@ const EntityData = new class { } let created: bigint | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CREATED)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CREATED)) { created = data.getBigUint64(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 8; } let lastEditedBy: Uuid | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LAST_EDITED_BY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)) { const lastEditedLength = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -713,8 +680,10 @@ const EntityData = new class { } } + const entityHostType = HostType.DOMAIN; // Not sent over the wire. + let queryAACube: AACube | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_QUERY_AA_CUBE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)) { const corner = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -729,31 +698,31 @@ const EntityData = new class { } let canCastShadow: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CAN_CAST_SHADOW)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CAN_CAST_SHADOW)) { canCastShadow = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let renderLayer: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RENDER_LAYER)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)) { renderLayer = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let primitiveMode: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_PRIMITIVE_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_PRIMITIVE_MODE)) { primitiveMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let ignorePickIntersection: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_IGNORE_PICK_INTERSECTION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_IGNORE_PICK_INTERSECTION)) { ignorePickIntersection = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let renderWithZones: Uuid[] | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RENDER_WITH_ZONES)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -769,122 +738,23 @@ const EntityData = new class { } let billboardMode: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_BILLBOARD_MODE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_BILLBOARD_MODE)) { billboardMode = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } - let grabbable: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_GRABBABLE)) { - grabbable = Boolean(data.getUint8(dataPosition)); - dataPosition += 1; - } - - let grabKinematic: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_KINEMATIC)) { - grabKinematic = Boolean(data.getUint8(dataPosition)); - dataPosition += 1; - } - - let grabFollowsController: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_FOLLOWS_CONTROLLER)) { - grabFollowsController = Boolean(data.getUint8(dataPosition)); - dataPosition += 1; - } - - let triggerable: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_TRIGGERABLE)) { - triggerable = Boolean(data.getUint8(dataPosition)); - dataPosition += 1; - } - - let grabEquippable: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_EQUIPPABLE)) { - grabEquippable = Boolean(data.getUint8(dataPosition)); - dataPosition += 1; - } - - let delegateToParent: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_DELEGATE_TO_PARENT)) { - delegateToParent = Boolean(data.getUint8(dataPosition)); - dataPosition += 1; - } - - let equippableLeftPositionOffset: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET)) { - equippableLeftPositionOffset = { - x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), - y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), - z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) - }; - dataPosition += 12; - } - - let equippableLeftRotationOffset: quat | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET)) { - equippableLeftRotationOffset - = GLMHelpers.unpackOrientationQuatFromBytes(data, dataPosition); - dataPosition += 8; - } - - let equippableRightPositionOffset: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET)) { - equippableRightPositionOffset = { - x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), - y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), - z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) - }; - dataPosition += 12; - } - - let equippableRightRotationOffset: quat | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET)) { - equippableRightRotationOffset - = GLMHelpers.unpackOrientationQuatFromBytes(data, dataPosition); - dataPosition += 8; - } - - let equippableIndicatorURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_EQUIPPABLE_INDICATOR_URL)) { - const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); - dataPosition += 2; - - if (length > 0) { - equippableIndicatorURL = textDecoder.decode( - new Uint8Array(data.buffer, data.byteOffset + dataPosition, length) - ); - dataPosition += length; - } - } - - let equippableIndicatorScale: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE)) { - equippableIndicatorScale = { - x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), - y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), - z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) - }; - dataPosition += 12; - } - - let equippableIndicatorOffset: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)) { - equippableIndicatorOffset = { - x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), - y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), - z: data.getFloat32(dataPosition + 8, UDT.LITTLE_ENDIAN) - }; - dataPosition += 12; - } + const grabSubclassData = GrabPropertyGroup.readEntitySubclassDataFromBuffer(data, dataPosition, propertyFlags); + dataPosition += grabSubclassData.bytesRead; + const grab = grabSubclassData.bytesRead > 0 ? grabSubclassData.properties : undefined; let density: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_DENSITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_DENSITY)) { density = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let velocity: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_VELOCITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_VELOCITY)) { velocity = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -894,7 +764,7 @@ const EntityData = new class { } let angularVelocity: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANGULAR_VELOCITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANGULAR_VELOCITY)) { angularVelocity = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -904,7 +774,7 @@ const EntityData = new class { } let gravity: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_GRAVITY)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_GRAVITY)) { gravity = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -914,7 +784,7 @@ const EntityData = new class { } let acceleration: vec3 | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ACCELERATION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ACCELERATION)) { acceleration = { x: data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN), y: data.getFloat32(dataPosition + 4, UDT.LITTLE_ENDIAN), @@ -924,55 +794,55 @@ const EntityData = new class { } let damping: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_DAMPING)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_DAMPING)) { damping = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } - let angularDampling: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ANGULAR_DAMPING)) { - angularDampling = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); + let angularDamping: number | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ANGULAR_DAMPING)) { + angularDamping = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let restitution: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_RESTITUTION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_RESTITUTION)) { restitution = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let friction: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_FRICTION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_FRICTION)) { friction = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let lifetime: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LIFETIME)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LIFETIME)) { lifetime = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let collisionless: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLLISIONLESS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLLISIONLESS)) { collisionless = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let collisionMask: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLLISION_MASK)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLLISION_MASK)) { collisionMask = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; } let dynamic: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_DYNAMIC)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_DYNAMIC)) { dynamic = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let collisionSoundURL: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_COLLISION_SOUND_URL)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_COLLISION_SOUND_URL)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -985,7 +855,7 @@ const EntityData = new class { } let actionData: ArrayBuffer | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ACTION_DATA)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1001,37 +871,37 @@ const EntityData = new class { } let cloneable: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CLONEABLE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CLONEABLE)) { cloneable = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let cloneLifetime: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CLONE_LIFETIME)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CLONE_LIFETIME)) { cloneLifetime = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let cloneLimit: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CLONE_LIMIT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CLONE_LIMIT)) { cloneLimit = data.getFloat32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let cloneDynamic: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CLONE_DYNAMIC)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CLONE_DYNAMIC)) { cloneDynamic = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } - let cloneAvatarIdentity: boolean | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CLONE_AVATAR_ENTITY)) { - cloneAvatarIdentity = Boolean(data.getUint8(dataPosition)); + let cloneAvatarEntity: boolean | undefined = undefined; + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CLONE_AVATAR_ENTITY)) { + cloneAvatarEntity = Boolean(data.getUint8(dataPosition)); dataPosition += 1; } let cloneOriginID: Uuid | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CLONE_ORIGIN_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CLONE_ORIGIN_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1042,7 +912,7 @@ const EntityData = new class { } let script: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SCRIPT)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SCRIPT)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1055,13 +925,13 @@ const EntityData = new class { } let scriptTimestamp: bigint | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SCRIPT_TIMESTAMP)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SCRIPT_TIMESTAMP)) { scriptTimestamp = data.getBigUint64(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 8; } let serverScripts: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_SERVER_SCRIPTS)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_SERVER_SCRIPTS)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1074,7 +944,7 @@ const EntityData = new class { } let itemName: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ITEM_NAME)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ITEM_NAME)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1087,7 +957,7 @@ const EntityData = new class { } let itemDescription: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ITEM_DESCRIPTION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ITEM_DESCRIPTION)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1100,7 +970,7 @@ const EntityData = new class { } let itemCategories: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ITEM_CATEGORIES)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ITEM_CATEGORIES)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1113,7 +983,7 @@ const EntityData = new class { } let itemArtist: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ITEM_ARTIST)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ITEM_ARTIST)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1126,7 +996,7 @@ const EntityData = new class { } let itemLicense: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ITEM_LICENSE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ITEM_LICENSE)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1139,13 +1009,13 @@ const EntityData = new class { } let limitedRun: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_LIMITED_RUN)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_LIMITED_RUN)) { limitedRun = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let marketplaceID: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_MARKETPLACE_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_MARKETPLACE_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1158,19 +1028,19 @@ const EntityData = new class { } let editionNumber: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_EDITION_NUMBER)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_EDITION_NUMBER)) { editionNumber = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let entityInstanceNumber: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_ENTITY_INSTANCE_NUMBER)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_ENTITY_INSTANCE_NUMBER)) { entityInstanceNumber = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } let certificateID: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CERTIFICATE_ID)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CERTIFICATE_ID)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1183,7 +1053,7 @@ const EntityData = new class { } let certificateType: string | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_CERTIFICATE_TYPE)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_CERTIFICATE_TYPE)) { const length = data.getUint16(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 2; @@ -1196,7 +1066,7 @@ const EntityData = new class { } let staticCertificateVersion: number | undefined = undefined; - if (propertyFlags.getHasProperty(EntityPropertyFlags.PROP_STATIC_CERTIFICATE_VERSION)) { + if (propertyFlags.getHasProperty(EntityPropertyList.PROP_STATIC_CERTIFICATE_VERSION)) { staticCertificateVersion = data.getUint32(dataPosition, UDT.LITTLE_ENDIAN); dataPosition += 4; } @@ -1297,6 +1167,7 @@ const EntityData = new class { registrationPoint, created, lastEditedBy, + entityHostType, queryAACube, canCastShadow, renderLayer, @@ -1304,26 +1175,14 @@ const EntityData = new class { ignorePickIntersection, renderWithZones, billboardMode, - grabbable, - grabKinematic, - grabFollowsController, - triggerable, - grabEquippable, - delegateToParent, - equippableLeftPositionOffset, - equippableLeftRotationOffset, - equippableRightPositionOffset, - equippableRightRotationOffset, - equippableIndicatorURL, - equippableIndicatorScale, - equippableIndicatorOffset, + grab, density, velocity, angularVelocity, gravity, acceleration, damping, - angularDampling, + angularDamping, restitution, friction, lifetime, @@ -1336,7 +1195,7 @@ const EntityData = new class { cloneLifetime, cloneLimit, cloneDynamic, - cloneAvatarIdentity, + cloneAvatarEntity, cloneOriginID, script, scriptTimestamp, @@ -1369,7 +1228,7 @@ const EntityData = new class { } // Implemented recursively in the C++ code, numberOfThreeBitSectionsInCode is here implemented iteratively. - #numberOfThreeBitSectionsInCode(data: DataView, dataPosition: number, maxBytes: number): number { + #numberOfThreeBitSectionsInCode(data: DataView, dataPosition: number, maxBytes = this.#_UNKNOWN_OCTCODE_LENGTH): number { // C++ int OctalCode::numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes) if (maxBytes === this.#_OVERFLOWED_OCTCODE_BUFFER) { @@ -1408,7 +1267,6 @@ const EntityData = new class { return 1 + Math.ceil(threeBitCodes * 3 / 8.0); } - }(); export default EntityData; diff --git a/src/domain/networking/packets/EntityEdit.ts b/src/domain/networking/packets/EntityEdit.ts new file mode 100644 index 00000000..987f81e5 --- /dev/null +++ b/src/domain/networking/packets/EntityEdit.ts @@ -0,0 +1,104 @@ +// +// EntityEdit.ts +// +// Created by David Rowe on 21 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityItemProperties from "../../entities/EntityItemProperties"; +import EntityPropertyFlags from "../../entities/EntityPropertyFlags"; +import { AppendState } from "../../octree/OctreeElement"; +import Uuid from "../../shared/Uuid"; +import PacketTypeValue from "../udt/PacketHeaders"; +import UDT from "../udt/UDT"; +import NLPacket from "../NLPacket"; +import { EntityProperties } from "./EntityData"; + + +type EntityEditDetails = { + entityID: Uuid, + properties: EntityProperties, + requestedProperties: EntityPropertyFlags, + didntFitProperties: EntityPropertyFlags +}; + +const EntityEdit = new class { + // C++ N/A + + /*@devdoc + * Information needed for {@link PacketScribe|writing} a {@link PacketType(1)|EntityEdit} packet. + * @typedef {object} PacketScribe.EntityEditDetails + * @property {Uuid} entityID - The ID of the entity to edit. + * @property {EntityProperties} properties - Required and changed properties for the entity edit. + * @property {EntityPropertyFlags} requestedProperties - The properties requested to be included in the packet. + * @property {EntityPropertyFlags} didntFitProperties - Properties that couldn't be included in the packet write because + * they didn't fit. Must be empty when passed in. + */ + + /*@devdoc + * Writes an {@link PacketType(1)|EntityEdit} packet, ready for sending reliably. + * @function PacketScribe.EntityEdit.write + * @param {PacketScribe.EntityEditDetails} info - The information needed for writing the packet. This information is + * updated with any properties that didn't fit in the packet. + * @return {NLPacket} The packet, ready for sending, or null if the packet couldn't be written because a + * property couldn't fit. + */ + write(info: EntityEditDetails): NLPacket | null { /* eslint-disable-line class-methods-use-this */ + // C++ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, + // const EntityItemProperties& properties, QByteArray & buffer, EntityPropertyFlags requestedProperties, + // EntityPropertyFlags & didntFitProperties) + // C++ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& editMessage) + + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + const packet = NLPacket.create(PacketTypeValue.EntityEdit, -1, true); + const messageData = packet.getMessageData(); + const data = messageData.data; + let dataPosition = messageData.dataPosition; + + + // C++ OctreeEditPacketSender::initializePacket(...) + + // Leave space for the sequence number which is written when the packet is sent. + dataPosition += 2; + + // WEBRTC TODO: Address further C++ code - clock skew. + + // WEBRTC TODO: High precision timestamp. + const MS_TO_CLOCK_TICKS = 10000000n; + const now = BigInt(Date.now()) * MS_TO_CLOCK_TICKS; + data.setBigInt64(dataPosition, now, UDT.LITTLE_ENDIAN); + dataPosition += 8; + + + // C++ EntityItemProperties::encodeEntityEditPacket(...) + + messageData.dataPosition = dataPosition; + + info.didntFitProperties = new EntityPropertyFlags(); + + const appendState = EntityItemProperties.encodeEntityEditPacket(/* PacketTypeValue.EntityEdit, */ info.entityID, + info.properties, messageData, info.requestedProperties, info.didntFitProperties); + dataPosition = messageData.dataPosition; + + if (appendState === AppendState.NONE) { + return null; + } + + + /* eslint-enable @typescript-eslint/no-magic-numbers */ + + messageData.dataPosition = dataPosition; + messageData.packetSize = dataPosition; + + return packet; + } + +}(); + +export default EntityEdit; +export type { EntityEditDetails }; diff --git a/src/domain/networking/packets/EntityQuery.ts b/src/domain/networking/packets/EntityQuery.ts index 9d48e193..dc44245d 100644 --- a/src/domain/networking/packets/EntityQuery.ts +++ b/src/domain/networking/packets/EntityQuery.ts @@ -47,7 +47,7 @@ const EntityQuery = new class { /*@devdoc * Writes an {@link PacketType(1)|EntityQuery} packet, ready for sending. * @function PacketScribe.EntityQuery.write - * @param {PacketScribe.EntityQueryDetails} info - The information needed for writing the packet list. + * @param {PacketScribe.EntityQueryDetails} info - The information needed for writing the packet. * @return {NLPacket} The packet, ready for sending. */ write(info: EntityQueryDetails): NLPacket { /* eslint-disable-line class-methods-use-this */ diff --git a/src/domain/networking/packets/PacketScribe.ts b/src/domain/networking/packets/PacketScribe.ts index 6de43740..c018a348 100644 --- a/src/domain/networking/packets/PacketScribe.ts +++ b/src/domain/networking/packets/PacketScribe.ts @@ -30,6 +30,7 @@ import NodeIgnoreRequest from "./NodeIgnoreRequest"; import DomainConnectRequest from "./DomainConnectRequest"; import EntityData from "./EntityData"; import EntityQuery from "./EntityQuery"; +import EntityEdit from "./EntityEdit"; import DomainServerConnectionToken from "./DomainServerConnectionToken"; import DomainDisconnectRequest from "./DomainDisconnectRequest"; import DomainServerRemovedNode from "./DomainServerRemovedNode"; @@ -95,6 +96,8 @@ import BulkAvatarTraitsAck from "./BulkAvatarTraitsAck"; * {@link PacketScribe.EntityData.read|EntityData.read} * @property {function} EntityQuery.write - * {@link PacketScribe.EntityQuery.write|EntityQuery.write} + * @property {function} EntityEdit.write - + * {@link PacketScribe.EntityEdit.write|EntityEdit.write} * @property {function} DomainServerConnectionToken.read - * {@link PacketScribe.DomainServerConnectionToken.read|DomainServerConnectionToken.read} * @property {function} DomainDisconnectRequest.write - @@ -156,6 +159,7 @@ const PacketScribe = { DomainConnectRequest, EntityData, EntityQuery, + EntityEdit, DomainServerConnectionToken, DomainDisconnectRequest, DomainServerRemovedNode, diff --git a/src/domain/networking/udt/PacketHeaders.ts b/src/domain/networking/udt/PacketHeaders.ts index f6d3dcbd..675eb6a8 100644 --- a/src/domain/networking/udt/PacketHeaders.ts +++ b/src/domain/networking/udt/PacketHeaders.ts @@ -243,7 +243,9 @@ const enum PacketTypeValue { * {@link PacketScribe.EntityQueryDetails} * @property {PacketType} EntityAdd - 43 * @property {PacketType} EntityErase - 44 - * @property {PacketType} EntityEdit - 45 + * @property {PacketType} EntityEdit - 45 - The user client sends this to the Entity Server to edit an existing + * entity. + * {@link PacketScribe.EntityEditDetails} * @property {PacketType} DomainServerConnectionToken - 46 - The Domain Server sends this to the client when the * client tries to log into the domain.
* {@link PacketScribe.DomainServerConnectionTokenDetails} @@ -663,6 +665,7 @@ const PacketType = new class { return this.#_DomainConnectRequestVersion.SocketTypes; case this.AudioEnvironment: return DEFAULT_VERSION; + case this.EntityEdit: case this.EntityData: return this.#_EntityVersion.LAST_PACKET_TYPE; case this.EntityQuery: diff --git a/src/domain/octree/OctreeEditPacketSender.ts b/src/domain/octree/OctreeEditPacketSender.ts new file mode 100644 index 00000000..6e1dca7c --- /dev/null +++ b/src/domain/octree/OctreeEditPacketSender.ts @@ -0,0 +1,75 @@ +// +// OctreeEditPacketSender.js +// +// Created by David Rowe on 20 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import NLPacket from "../networking/NLPacket"; +import NLPacketList from "../networking/NLPacketList"; +import NodeList from "../networking/NodeList"; +import NodeType from "../networking/NodeType"; +import PacketType from "../networking/udt/PacketHeaders"; +import assert from "../shared/assert"; +import ContextManager from "../shared/ContextManager"; + + +/*@devdoc + * The OctreeEditPacketSender class handles sending entity editing packets. + *

C++: class OctreeEditPacketSender : public PacketSender

+ * + * @class OctreeEditPacketSender + * @param {number} contextID - The {@link ContextManager} context ID. + */ +class OctreeEditPacketSender { + // C++ class OctreeEditPacketSender : public PacketSender + + static readonly contextItemType: "EntityEditPacketSender" | "OctreeEditPacketSender" = "OctreeEditPacketSender"; + + static #_VALID_PACKET_TYPES = [PacketType.EntityAdd, PacketType.EntityEdit, PacketType.EntityClone, PacketType.EntityErase]; + + + // Context + #_nodeList; + + + constructor(contextID: number) { + + // Context + this.#_nodeList = ContextManager.get(contextID, NodeList) as NodeList; + } + + + /*@devdoc + * Sends an {@link NLPacket} or {@link NLPacketList} to the entity server. + *

Note: The packet is sent straight away whereas the C++ queues the packet if not connected to the entity server.

+ * @param {NLPacket | NLPacketList} editMessage - The packet to send. + */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + queueOctreeEditMessage(/* type: PacketTypeValue, */ editMessage: NLPacket | NLPacketList): void { + // C++ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray & editMessage) + + const packetType = editMessage.getType(); + assert(OctreeEditPacketSender.#_VALID_PACKET_TYPES.indexOf(packetType) !== -1, + `queueOctreeEditMessage() unexpected packet type: ${packetType}`); + + // WEBRTC TODO: Address further C++ code - queue edits if not connected to entity server. Perhaps reuse SendQueue? + const entityServer = this.#_nodeList.soloNodeOfType(NodeType.EntityServer); + if (entityServer && entityServer.getActiveSocket()) { + if (editMessage instanceof NLPacket) { + this.#_nodeList.sendPacket(editMessage, entityServer); + } else { + this.#_nodeList.sendPacketList(editMessage, entityServer); + } + } else { + console.warn("[EntityServer] Could not send edit message because not connected."); + } + } +} + +export default OctreeEditPacketSender; diff --git a/src/domain/octree/OctreeElement.ts b/src/domain/octree/OctreeElement.ts new file mode 100644 index 00000000..86c6d088 --- /dev/null +++ b/src/domain/octree/OctreeElement.ts @@ -0,0 +1,27 @@ +// +// OctreeElement.ts +// +// Created by David Rowe on 29 Jun 20923. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +/*@devdoc + * The AppendState namespace provides status values for appending data to a network packet. + * @namespace AppendState + * @property {number} COMPLETED=0 - All data were appended. + * @property {number} PARTIAL=1 - Some data were appended. + * @property {number} NONE=2 - No data were appended, + */ +enum AppendState { + // C++ typedef enum { COMPLETED, PARTIAL, NONE } AppendState; + COMPLETED, + PARTIAL, + NONE +} + +export { AppendState }; diff --git a/src/domain/octree/OctreePacketData.ts b/src/domain/octree/OctreePacketData.ts new file mode 100644 index 00000000..59a71666 --- /dev/null +++ b/src/domain/octree/OctreePacketData.ts @@ -0,0 +1,743 @@ +// +// OctreePacketData.ts +// +// Created by David Rowe on 17 Jul 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags from "../entities/EntityPropertyFlags"; +import UDT from "../networking/udt/UDT"; +import AACube from "../shared/AACube"; +import { color } from "../shared/Color"; +import GLMHelpers from "../shared/GLMHelpers"; +import { quat } from "../shared/Quat"; +import Uuid from "../shared/Uuid"; +import { rect } from "../shared/Rect"; +import { vec2 } from "../shared/Vec2"; +import { vec3 } from "../shared/Vec3"; +import { AppendState } from "./OctreeElement"; + + +type OctreePacketContext = { + // C++ N/A + propertiesToWrite: EntityPropertyFlags, + propertiesWritten: EntityPropertyFlags, + propertyCount: number, + appendState: AppendState +}; + +/*@devdoc + * The OctreePacketData namespace provides methods for writing entity properties to a packet. + *

C++: OctreePacketData

+ * @namespace OctreePacketData + */ +class OctreePacketData { + // C++ class OctreePacketData + + /*@devdoc + * The context of a packet being written. + * @typedef {object} OctreePacketContext + * @property {EntityPropertyFlags} propertiesToWrite - The properties remaining to be written to the packet. + * @property {EntityPropertyFlags} propertiesWritten - The properties that have been written to the packet. + * @property {number} propertyCount - The number of properties written to the packet. + * @property {AppendState} appendState - The status of the append operation. + */ + + + static #_textEncoder = new TextEncoder(); + + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + /*@devdoc + * Appends a {@link AACube} value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {AACube} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendAACubeValue(data: DataView, dataPosition: number, flag: number, value: AACube, + packetContext: OctreePacketContext): number { + // C++ bool appendPosition(const glm::vec3& value) + const MAX_FLOAT32 = 3.4028235e38; + const valid = typeof value.corner === "object" && typeof value.corner.x === "number" + && typeof value.corner.y === "number" && typeof value.corner.z === "number" && typeof value.scale === "number" + && -MAX_FLOAT32 <= value.corner.x && value.corner.x <= MAX_FLOAT32 + && -MAX_FLOAT32 <= value.corner.y && value.corner.y <= MAX_FLOAT32 + && -MAX_FLOAT32 <= value.corner.z && value.corner.z <= MAX_FLOAT32 + && 0.0 <= value.scale && value.scale <= MAX_FLOAT32; + if (!valid) { + console.error("[EntityServer] Cannot write invalid AACube value to packet!"); + return 0; + } + + const NUM_BYTES = 16; // 4 floats. + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setFloat32(dataPosition, value.corner.x, UDT.LITTLE_ENDIAN); + data.setFloat32(dataPosition + 4, value.corner.y, UDT.LITTLE_ENDIAN); + data.setFloat32(dataPosition + 8, value.corner.z, UDT.LITTLE_ENDIAN); + data.setFloat32(dataPosition + 12, value.scale, UDT.LITTLE_ENDIAN); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends an ArrayBuffer value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {ArrayBuffer} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendArrayBufferValue(data: DataView, dataPosition: number, flag: number, value: ArrayBuffer, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const QByteArray& bytes) + const valid = value instanceof ArrayBuffer; + if (!valid) { + console.error("[EntityServer] Cannot write invalid ArrayBuffer value to packet!"); + return 0; + } + + // Max packet length < max uint16 length so no need to check for overflow. + const NUM_BYTES = 2 + value.byteLength; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, value.byteLength, UDT.LITTLE_ENDIAN); + const startIndex = dataPosition + 2; + const dataView = new DataView(value); + for (let i = 0; i < value.byteLength; i += 1) { + data.setUint8(startIndex + i, dataView.getUint8(i)); + } + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends an array of boolean values to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the array at. + * @param {number} flag - The property flag for the value being written. + * @param {boolean[]} value - The array to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendBooleanArray(data: DataView, dataPosition: number, flag: number, value: boolean[], + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const QVector& value) + const valid = Array.isArray(value) && value.length <= 0xffff && value.every((element) => { + return typeof element === "boolean"; + }); + if (!valid) { + console.error("[EntityServer] Cannot write invalid boolean array to packet!"); + return 0; + } + + const NUM_BYTES = 2 + Math.ceil(value.length / 8); + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, value.length, UDT.LITTLE_ENDIAN); + let index = dataPosition + 2; + + let bit = 0; + let current = 0; + for (let i = 0, length = value.length; i < length; i++) { + if (value[i]) { + current |= 1 << bit; + } + bit = (bit + 1) % 8; + if (bit === 0 || i === length - 1) { + data.setUint8(index, current); + index += 1; + current = 0; + } + } + + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a boolean value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {boolean} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendBooleanValue(data: DataView, dataPosition: number, flag: number, value: boolean, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(bool value) + const valid = typeof value === "boolean"; + if (!valid) { + console.error("[EntityServer] Cannot write invalid boolean value to packet!"); + return 0; + } + + const NUM_BYTES = 1; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint8(dataPosition, value ? 1 : 0); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link color} value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {color} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendColorValue(data: DataView, dataPosition: number, flag: number, value: color, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const glm::u8vec3& value); + const valid = typeof value.red === "number" && typeof value.green === "number" && typeof value.blue === "number"; + if (!valid) { + console.error("[EntityServer] Cannot write invalid color value to packet!"); + return 0; + } + + const NUM_BYTES = 3; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint8(dataPosition, value.red); + data.setUint8(dataPosition + 1, value.green); + data.setUint8(dataPosition + 2, value.blue); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a float32 value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {number} value - The value to write. + * @param {boolean} littleEndian - true to write the value in little-endian format, false for + * big-endian. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendFloat32Value(data: DataView, dataPosition: number, flag: number, value: number, littleEndian: boolean, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(float value) + const MAX_FLOAT32 = 3.4028235e38; + const valid = typeof value === "number" && -MAX_FLOAT32 <= value && value <= MAX_FLOAT32; + if (!valid) { + console.error("[EntityServer] Cannot write invalid float32 value to packet!"); + return 0; + } + + const NUM_BYTES = 4; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setFloat32(dataPosition, value, littleEndian); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link quat} array to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the array at. + * @param {number} flag - The property flag for the value being written. + * @param {quat} value - The array to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendQuatArray(data: DataView, dataPosition: number, flag: number, value: quat[], + packetContext: OctreePacketContext): number { + // C++ bool appendValue(QVector& value) + const valid = Array.isArray(value) && value.length <= 0xffff && value.every((element) => { + return typeof element === "object" && typeof element.x === "number" && typeof element.y === "number" + && typeof element.z === "number" && typeof element.w === "number"; + }); + if (!valid) { + console.error("[EntityServer] Cannot write invalid quat array to packet!"); + return 0; + } + + const NUM_BYTES = 2 + value.length * 8; // Packed data. + + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, value.length, UDT.LITTLE_ENDIAN); + let index = dataPosition + 2; + + for (const element of value) { + GLMHelpers.packOrientationQuatToBytes(data, index, element); + index += 8; + } + + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link quat} value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {quat} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendQuatValue(data: DataView, dataPosition: number, flag: number, value: quat, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const glm::quat& value) + const valid = typeof value.x === "number" && typeof value.y === "number" && typeof value.z === "number" + && typeof value.w === "number"; + if (!valid) { + console.error("[EntityServer] Cannot write invalid quat value to packet!"); + return 0; + } + + const NUM_BYTES = 8; // Packed data. + if (dataPosition + NUM_BYTES <= data.byteLength) { + GLMHelpers.packOrientationQuatToBytes(data, dataPosition, value); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link rect} value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {rect} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendRectValue(data: DataView, dataPosition: number, flag: number, value: rect, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const QRect& value) + const valid = typeof value.x === "number" && typeof value.y === "number" && typeof value.width === "number" + && typeof value.height === "number"; + if (!valid) { + console.error("[EntityServer] Cannot write invalid rect value to packet!"); + return 0; + } + + const NUM_BYTES = 16; // 4 floats. + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint32(dataPosition, value.x, UDT.LITTLE_ENDIAN); + data.setUint32(dataPosition + 4, value.y, UDT.LITTLE_ENDIAN); + data.setUint32(dataPosition + 8, value.width, UDT.LITTLE_ENDIAN); + data.setUint32(dataPosition + 12, value.height, UDT.LITTLE_ENDIAN); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a string value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {string} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendStringValue(data: DataView, dataPosition: number, flag: number, value: string, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const QString& string) + const valid = typeof value === "string"; + if (!valid) { + console.error("[EntityServer] Cannot write invalid string value to packet!"); + return 0; + } + + const utf8 = OctreePacketData.#_textEncoder.encode(value); + // Max packet length < max uint16 length so no need to check for overflow. + const NUM_BYTES = 2 + utf8.byteLength; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, utf8.byteLength, UDT.LITTLE_ENDIAN); + const startIndex = dataPosition + 2; + for (let i = 0; i < utf8.length; i += 1) { + data.setUint8(startIndex + i, utf8.at(i) ?? 0); + } + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a uint8 value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {number} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendUint8Value(data: DataView, dataPosition: number, flag: number, value: number, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(uint8_t value) + const valid = typeof value === "number" && 0 <= value && value <= 0xff; + if (!valid) { + console.error("[EntityServer] Cannot write invalid uint8 value to packet!"); + return 0; + } + + const NUM_BYTES = 1; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint8(dataPosition, value); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a uint16 value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {number} value - The value to write. + * @param {boolean} littleEndian - true to write the value in little-endian format, false for + * big-endian. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendUint16Value(data: DataView, dataPosition: number, flag: number, value: number, littleEndian: boolean, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(uint16_t value) + const valid = typeof value === "number" && 0 <= value && value <= 0xffff; + if (!valid) { + console.error("[EntityServer] Cannot write invalid uint16 value to packet!"); + return 0; + } + + const NUM_BYTES = 2; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, value, littleEndian); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a uint32 value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {number} value - The value to write. + * @param {boolean} littleEndian - true to write the value in little-endian format, false for + * big-endian. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendUint32Value(data: DataView, dataPosition: number, flag: number, value: number, littleEndian: boolean, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(uint32_t value) + const valid = typeof value === "number" && 0 <= value && value <= 0xffffffff; + if (!valid) { + console.error("[EntityServer] Cannot write invalid uint32 value to packet!"); + return 0; + } + + const NUM_BYTES = 4; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint32(dataPosition, value, littleEndian); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a uint64 value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {bigint} value - The value to write. + * @param {boolean} littleEndian - true to write the value in little-endian format, false for + * big-endian. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendUint64Value(data: DataView, dataPosition: number, flag: number, value: bigint, littleEndian: boolean, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(quint64 value) + const valid = typeof value === "bigint" && 0 <= value && value <= 0xffffffffffffffffn; + if (!valid) { + console.error("[EntityServer] Cannot write invalid uint64 value to packet!"); + return 0; + } + + const NUM_BYTES = 8; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setBigUint64(dataPosition, value, littleEndian); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link Uuid} array to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the array at. + * @param {number} flag - The property flag for the value being written. + * @param {Uuid[]} value - The array to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendUuidArray(data: DataView, dataPosition: number, flag: number, value: Uuid[], + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const QVector& value) + const valid = Array.isArray(value) && value.length <= 0xffff && value.every((element) => { + return element instanceof Uuid; + }); + if (!valid) { + console.error("[EntityServer] Cannot write invalid UUID array to packet!"); + return 0; + } + + const NUM_BYTES = 2 + value.length * 16; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, value.length, UDT.LITTLE_ENDIAN); + let index = dataPosition + 2; + + value.forEach((uuid) => { + data.setBigUint128(index, uuid.value(), UDT.BIG_ENDIAN); + index += 16; + }); + + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link Uuid} value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {Uuid} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendUuidValue(data: DataView, dataPosition: number, flag: number, value: Uuid, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const QUuid& uuid) + const valid = value instanceof Uuid; + if (!valid) { + console.error("[EntityServer] Cannot write invalid UUID value to packet!"); + return 0; + } + + if (value.isNull()) { + const NUM_BYTES = 2; + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, 0, UDT.LITTLE_ENDIAN); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + } else { + const NUM_LENGTH_BYTES = 2; + const NUM_VALUE_BYTES = 16; + if (dataPosition + NUM_LENGTH_BYTES + NUM_VALUE_BYTES <= data.byteLength) { + data.setUint16(dataPosition, NUM_VALUE_BYTES, UDT.LITTLE_ENDIAN); + data.setBigUint128(dataPosition + NUM_LENGTH_BYTES, value.value(), UDT.BIG_ENDIAN); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_LENGTH_BYTES + NUM_VALUE_BYTES; + } + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link vec2} value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {vec2} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendVec2Value(data: DataView, dataPosition: number, flag: number, value: vec2, + packetContext: OctreePacketContext): number { + // C++ bool appendPosition(const glm::vec2& value) + const MAX_FLOAT32 = 3.4028235e38; + const valid = typeof value.x === "number" && typeof value.y === "number" + && -MAX_FLOAT32 <= value.x && value.x <= MAX_FLOAT32 + && -MAX_FLOAT32 <= value.y && value.y <= MAX_FLOAT32; + if (!valid) { + console.error("[EntityServer] Cannot write invalid vec2 value to packet!"); + return 0; + } + + const NUM_BYTES = 8; // 2 floats. + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setFloat32(dataPosition, value.x, UDT.LITTLE_ENDIAN); + data.setFloat32(dataPosition + 4, value.y, UDT.LITTLE_ENDIAN); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link vec3} array to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the array at. + * @param {number} flag - The property flag for the value being written. + * @param {vec3} value - The array to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendVec3Array(data: DataView, dataPosition: number, flag: number, value: vec3[], + packetContext: OctreePacketContext): number { + // C++ bool appendValue(QVector& value) + const MAX_FLOAT32 = 3.4028235e38; + const valid = Array.isArray(value) && value.length <= 0xffff && value.every((element) => { + return typeof element.x === "number" && typeof element.y === "number" && typeof element.z === "number" + && -MAX_FLOAT32 <= element.x && element.x <= MAX_FLOAT32 + && -MAX_FLOAT32 <= element.y && element.y <= MAX_FLOAT32 + && -MAX_FLOAT32 <= element.z && element.z <= MAX_FLOAT32; + }); + if (!valid) { + console.error("[EntityServer] Cannot write invalid vec3 array to packet!"); + return 0; + } + + const NUM_BYTES = 2 + value.length * 12; // Count + 3 floats per element. + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setUint16(dataPosition, value.length, UDT.LITTLE_ENDIAN); + let index = dataPosition + 2; + + value.forEach((element) => { + data.setFloat32(index, element.x, UDT.LITTLE_ENDIAN); + data.setFloat32(index + 4, element.y, UDT.LITTLE_ENDIAN); + data.setFloat32(index + 8, element.z, UDT.LITTLE_ENDIAN); + index += 12; + }); + + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /*@devdoc + * Appends a {@link vec3} value to a packet and updates the packet context. + * @param {DataView} data - The packet data. + * @param {number} dataPosition - The position to write the value at. + * @param {number} flag - The property flag for the value being written. + * @param {vec3} value - The value to write. + * @param {OctreePacketContext} packetContext - The context of the packet being written. + * @returns {number} The number of bytes written. 0 if the value wouldn't fit. + */ + static appendVec3Value(data: DataView, dataPosition: number, flag: number, value: vec3, + packetContext: OctreePacketContext): number { + // C++ bool appendValue(const glm::vec3& value) + const MAX_FLOAT32 = 3.4028235e38; + const valid = typeof value.x === "number" && typeof value.y === "number" && typeof value.z === "number" + && -MAX_FLOAT32 <= value.x && value.x <= MAX_FLOAT32 + && -MAX_FLOAT32 <= value.y && value.y <= MAX_FLOAT32 + && -MAX_FLOAT32 <= value.z && value.z <= MAX_FLOAT32; + if (!valid) { + console.error("[EntityServer] Cannot write invalid vec3 value to packet!"); + return 0; + } + + const NUM_BYTES = 12; // 3 floats. + if (dataPosition + NUM_BYTES <= data.byteLength) { + data.setFloat32(dataPosition, value.x, UDT.LITTLE_ENDIAN); + data.setFloat32(dataPosition + 4, value.y, UDT.LITTLE_ENDIAN); + data.setFloat32(dataPosition + 8, value.z, UDT.LITTLE_ENDIAN); + packetContext.propertiesToWrite.setHasProperty(flag, false); + packetContext.propertiesWritten.setHasProperty(flag, true); + packetContext.propertyCount += 1; + return NUM_BYTES; + } + packetContext.appendState = AppendState.PARTIAL; + return 0; + } + + /* eslint-enable @typescript-eslint/no-magic-numbers */ +} + +export default OctreePacketData; +export type { OctreePacketContext }; diff --git a/src/domain/shared/ByteCountCoded.ts b/src/domain/shared/ByteCountCoded.ts index 23f14dea..357359c2 100644 --- a/src/domain/shared/ByteCountCoded.ts +++ b/src/domain/shared/ByteCountCoded.ts @@ -11,11 +11,12 @@ /*@devdoc - * The ByteCountCoded class provides facilities to decode data. + * The ByteCountCoded class provides facilities to encode and decode byte count coded integer data. + * Up to 64-bit integer values are supported. *

C++: template<typename T> class ByteCountCoded

* @class ByteCountCoded * - * @property {number} data - The decoded byte count encoded data. + * @property {bigint} data - The integer value to encode or the decoded integer value. */ // WEBRTC TODO: Make the class generic. class ByteCountCoded { @@ -24,17 +25,21 @@ class ByteCountCoded { // WEBRTC TODO: Move to NumericalConstants.ts readonly #BITS_IN_BYTE = 8; - #_data = 0; + #_data = 0n; // BigInt required for 64-bit values. - get data(): number { + get data(): bigint { return this.#_data; } + set data(data: bigint) { + this.#_data = data; + } + /*@devdoc - * Decode the encoded data. + * Decodes the encoded data. * @param {DataView} encodedBuffer - The data to decode. * @param {number} encodedSize - The maximum size of the data to decode. - * @returns {number} The number of bytes processed. + * @returns {number} The number of bytes processed. The decoded value is provided in the data property. */ decode(encodedBuffer: DataView, encodedSize: number): number { // C++ template inline size_t ByteCountCoded::decode(const char* encodedBuffer, int encodedSize) @@ -58,7 +63,7 @@ class ByteCountCoded { * b. 0011 0000 -> The decoded value is 6. */ - this.#_data = 0; + this.#_data = 0n; let bytesConsumed = 0; const bitCount = this.#BITS_IN_BYTE * encodedSize; @@ -103,7 +108,7 @@ class ByteCountCoded { } if (bitIsSet) { - this.#_data += bitValue; + this.#_data += BigInt(bitValue); } bitValue *= 2; } @@ -118,7 +123,71 @@ class ByteCountCoded { return bytesConsumed; } + /** + * Encodes the numeric value set in the data property into the buffer. + * @param data - The buffer to write the encoded value into. + * @returns The number of bytes written. + */ + encode(data: DataView): number { + // C++ template inline QByteArray ByteCountCoded::encode() const + + const totalBits = 64; // Up to 64-bit integers. + let valueBits = totalBits; + let firstValueFound = false; + let temp = this.#_data; + const lastBitMask = 1n << BigInt(totalBits - 1); + + // determine the number of bits that the value takes + for (let bitAt = 0; bitAt < totalBits; bitAt++) { + const bitValue = (temp & lastBitMask) === lastBitMask; + if (!firstValueFound) { + if (!bitValue) { + valueBits -= 1; + } else { + firstValueFound = true; + } + } + temp = temp << 1n; + } + + // Calculate the number of total bytes, including our header. + // BITS_IN_BYTE-1 because we need to code the number of bytes in the header + // + 1 because we always take at least 1 byte, even if number of bits is less than a bytes worth + const BITS_IN_BYTE = 8; + const numberOfBytes = Math.trunc(valueBits / (BITS_IN_BYTE - 1)) + 1; + + // Fill data with initial 0s. + for (let i = 0; i < numberOfBytes; i++) { + data.setUint8(i, 0); + } + + // Next, pack the number of header bits in, the first N-1 to be set to 1, the last to be set to 0. + for (let i = 0; i < numberOfBytes; i++) { + const outputIndex = i; + const bitValue = i < numberOfBytes - 1 ? 1 : 0; + const original = data.getUint8(outputIndex / BITS_IN_BYTE); + const shiftBy = BITS_IN_BYTE - (outputIndex % BITS_IN_BYTE + 1); + const thisBit = bitValue << shiftBy; + data.setUint8(i / BITS_IN_BYTE, original | thisBit); + } + + // finally pack the actual bits from the bit array. + temp = this.#_data; + for (let i = numberOfBytes; i < numberOfBytes + valueBits; i++) { + const outputIndex = i; + const bitValue = temp & 1n; + const original = data.getUint8(outputIndex / BITS_IN_BYTE); + const shiftBy = Math.trunc(BITS_IN_BYTE - (outputIndex % BITS_IN_BYTE + 1)); + const thisBit = bitValue << BigInt(shiftBy); + data.setUint8(i / BITS_IN_BYTE, original | Number(thisBit)); + temp = temp >> 1n; + } + + return numberOfBytes; + } + // WEBRTC TODO: Address further C++ code. + } export default ByteCountCoded; diff --git a/src/domain/shared/DataViewExtensions.ts b/src/domain/shared/DataViewExtensions.ts index 7e20f5cf..7ab36305 100644 --- a/src/domain/shared/DataViewExtensions.ts +++ b/src/domain/shared/DataViewExtensions.ts @@ -12,7 +12,7 @@ /*@devdoc * The DataView namespace comprises methods added to the prototype of JavaScript's * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView|DataView} object, for - * handling reading and writing large bigint values. These methods are added only if they aren't already present + * handling reading and writing large bigint values. These methods are added only if they aren't already present * in the browser's DataView implementation. *

C++: N/A

* @namespace DataView diff --git a/src/domain/shared/GLMHelpers.ts b/src/domain/shared/GLMHelpers.ts index 749ef98e..0dc861d6 100644 --- a/src/domain/shared/GLMHelpers.ts +++ b/src/domain/shared/GLMHelpers.ts @@ -11,7 +11,7 @@ import UDT from "../networking/udt/UDT"; import assert from "./assert"; -import { quat } from "./Quat"; +import Quat, { quat } from "./Quat"; import { vec3 } from "./Vec3"; @@ -35,6 +35,29 @@ const GLMHelpers = new class { readonly #_INT16_MIN = -32768.0; + /*@devdoc + * Writes a quaternion value to a packet, packing it into 8 bytes. + * @function GLMHelpers.packOrientationQuatToBytes + * @param {DataView} data - The packet data to write. + * @param {number} dataPosition - The data position to write the value at. + * @param {quat} quatInput - The quaternion value to write. + */ + // eslint-disable-next-line class-methods-use-this + packOrientationQuatToBytes(data: DataView, dataPosition: number, quatInput: quat): void { + // C++ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput) + const quatNormalized = Quat.normalize(quatInput); + const QUAT_PART_CONVERSION_RATIO = this.#_UINT16_MAX / 2.0; + + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + data.setUint16(dataPosition, Math.floor((quatNormalized.x + 1.0) * QUAT_PART_CONVERSION_RATIO), UDT.LITTLE_ENDIAN); + data.setUint16(dataPosition + 2, Math.floor((quatNormalized.y + 1.0) * QUAT_PART_CONVERSION_RATIO), UDT.LITTLE_ENDIAN); + data.setUint16(dataPosition + 4, Math.floor((quatNormalized.z + 1.0) * QUAT_PART_CONVERSION_RATIO), UDT.LITTLE_ENDIAN); + data.setUint16(dataPosition + 6, Math.floor((quatNormalized.w + 1.0) * QUAT_PART_CONVERSION_RATIO), UDT.LITTLE_ENDIAN); + + /* eslint-enable @typescript-eslint/no-magic-numbers */ + } + /*@devdoc * Reads a quaternion value from a packet, unpacking it from 8 bytes. * @function GLMHelpers.unpackOrientationQuatFromBytes diff --git a/src/domain/shared/JSONExtensions.ts b/src/domain/shared/JSONExtensions.ts new file mode 100644 index 00000000..17647dcb --- /dev/null +++ b/src/domain/shared/JSONExtensions.ts @@ -0,0 +1,60 @@ +// +// JSONExtensions.ts +// +// Created by David Rowe on 3 Jul 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Uuid from "./Uuid"; + + +/*@devdoc + * The JSONExtensions namespace provides "replacer" and "reviver" methods for use when stringifying and parsing + JSON with bigint values. + *

C++: N/A

+ * @namespace JSONExtensions + */ + +/*@devdoc + * Replaces a bigint value in a JSON object being stringified. Use as the second parameter in the + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify|JSON.stringify()} + * call. + * @function JSONExtensions.bigintReplacer + * @param {string} key - The key of the property being processed. + * @param {unknown} value - The value of the property being processed. + * @returns {string|unknown} A numeric string ending with "n" if the value is a bigint or + * {@link Uuid}, otherwise the unaltered value passed in. + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +export function bigintReplacer(key: string, value: unknown): unknown { + if (typeof value === "bigint") { + return value.toString() + "n"; + } + if (value instanceof Uuid) { + return value.value().toString() + "n"; + } + return value; +} + +/*@devdoc + * Revives a bigint value in a stringified JSON object. Use as the second parameter in the + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse|JSON.parse()} call. + * @function JSONExtensions.bigintReviver + * @param {string} key - The key of the property being processed. + * @param {unknown} value - The stringified value of the property being processed. + * @returns {bigint|unknown} The bigint value of the value if it's a numeric string ending with + * "n", otherwise the unaltered value passed in. + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +export function bigintReviver(key: string, value: unknown): unknown { + if (typeof value === "string" && value.endsWith("n")) { + return BigInt(value.slice(0, -1)); + } + return value; +} diff --git a/src/domain/shared/PropertyFlags.ts b/src/domain/shared/PropertyFlags.ts index 0c5782dd..a1aa91df 100644 --- a/src/domain/shared/PropertyFlags.ts +++ b/src/domain/shared/PropertyFlags.ts @@ -14,18 +14,66 @@ * The PropertyFlags class provides facilities to decode, set and get property flags. *

C++: template<typename Enum> class PropertyFlags

* @class PropertyFlags + * @property {number} length - The number of flags. */ // WEBRTC TODO: Make the class generic. class PropertyFlags { // C++ templateclass PropertyFlags - // WEBRTC TODO: Move to NumericalConstants.ts - readonly #BITS_IN_BYTE = 8; - - #_flags = new Uint8Array(0); + #_flags: boolean[] = []; #_maxFlag = Number.MIN_SAFE_INTEGER; #_minFlag = Number.MAX_SAFE_INTEGER; #_trailingFlipped = false; + #_encodedLength = 0; + + constructor(otherPropertyFlags?: PropertyFlags) { + if (otherPropertyFlags) { + for (let i = 0, length = otherPropertyFlags.length(); i < length; i += 1) { + this.setHasProperty(i, otherPropertyFlags.getHasProperty(i)); + } + } + } + + + /*@devdoc + * Copies the flags from another instance. + * @param {PropertyFlags} other - The other instance to copy the flags from. + */ + copy(other: PropertyFlags): void { + // C++ operator = (const PropertyFlags& other) + this.#clear(); + for (let i = 0, length = other.length(); i < length; i++) { + this.setHasProperty(i, other.getHasProperty(i)); + } + } + + /*@devdoc + * Gets whether the property flags are empty. + * @returns {boolean} true if no property flags are set, false if one or more are set. + */ + isEmpty(): boolean { + // C++ bool isEmpty() + return this.#_trailingFlipped === false && this.#_encodedLength === 0; + } + + /*@devdoc + * Gets the number of flags. + * @returns {number} The number of flags. + */ + length(): number { + // C++ int QByteArray::length() const + return this.#_flags.length; + } + + /*@devdoc + * Gets the number of bytes used in the encoding, after calling {@link encode} or {@link decode}. + * @returns {number} The number of bytes used in the encoding, or 0 if {@link encode} or {@link decode} + * haven't been called. + */ + getEncodedLength(): number { + // C++ int getEncodedLength() const + return this.#_encodedLength; + } /*@devdoc * Gets whether the property flag is present in the flags sent by the server. @@ -33,20 +81,14 @@ class PropertyFlags { * @returns {boolean} true if the property flag is present, false if it isn't. */ getHasProperty(flag: number): boolean { - // C++ bool getHasProperty(Enum flag) + // C++ bool getHasProperty(Enum flag) - const bytePos = Math.floor(flag / this.#BITS_IN_BYTE); - - if (bytePos > this.#_maxFlag) { + if (flag > this.#_maxFlag) { // Usually false. return this.#_trailingFlipped; } - const bitPos = flag - bytePos * this.#BITS_IN_BYTE; - const mask = 1 << bitPos; - const tmp = this.#_flags[bytePos] ?? 0; - - return (tmp & mask) > 0; + return this.#_flags[flag] ?? false; } /*@devdoc @@ -55,94 +97,75 @@ class PropertyFlags { * @param {boolean} value - The value to set the flag to. */ setHasProperty(flag: number, value: boolean): void { - // C++ void setHasProperty(Enum flag, bool value = true) + // C++ void setHasProperty(Enum flag, bool value = true) - const bytePos = Math.floor(flag / this.#BITS_IN_BYTE); - - if (bytePos < this.#_minFlag) { + if (flag < this.#_minFlag) { if (value) { - this.#_minFlag = bytePos; + this.#_minFlag = flag; } } - - if (bytePos > this.#_maxFlag) { + if (flag > this.#_maxFlag) { if (value) { - this.#_maxFlag = bytePos; - const newFlags = new Uint8Array(this.#_flags.length + bytePos + 1); - newFlags.set(this.#_flags); - this.#_flags = newFlags; + this.#_maxFlag = flag; + this.#resize(this.#_maxFlag + 1); } else { - // Bail early, we're setting a flag outside of our current _maxFlag to false, which is already the default. + // We're setting a flag outside of our current _maxFlag to false, which is already the default. return; } } + this.#_flags[flag] = value; - // flag represents the position of the bit to set. - // We subtract bytePos * 8 bits from flag because we store the bit representation of flags as an array of bytes, and we - // want to update a single bit within a byte. - const bitPosInByte = flag - bytePos * this.#BITS_IN_BYTE; - const byteMask = 1 << bitPosInByte; - let byteValue = this.#_flags[bytePos] ?? 0; - - if (value) { - byteValue |= byteMask; - } else { - byteValue &= ~byteMask; + if (flag === this.#_maxFlag && !value) { + this.#shrinkIfNeeded(); } + } - this.#_flags.set([byteValue], bytePos); + /*@devdoc + * "Or"s the property flags with another instance. + * @param {PropertyFlags} other - The other instance to "or" the flags with. + */ + or(other: PropertyFlags): void { + // C++ PropertyFlags& PropertyFlags::operator|=(const PropertyFlags& other) + for (let i = 0, length = other.length(); i < length; i++) { + this.setHasProperty(i, this.getHasProperty(i) || other.getHasProperty(i)); + } } /*@devdoc - * Decode the encoded property flags. + * Decodes property flags from input data. * @param {DataView} data - The data to decode. * @param {number} size - The maximum size of the data to decode. * @returns {number} The number of bytes processed. */ decode(data: DataView, size: number): number { - // C++ size_t decode(const uint8_t* data, size_t length) - - /* Process each bit of each byte until the stop condition is reached. - * Starts from the leftmost bit. - * Advance through the lead bits (contiguous 1s starting from the left) until the first non lead bit is found (first 0 - * encountered). - * Compute the position of the last bit. - * Continue iterating through the next bits and set the flag property corresponding to the current value of the variable - * flag when a bit is set. - * Stop when the lead bits AND the last bit have been processed (at the position denoted by lastValueBit). - * - * The number of lead bits represents the number of bytes to decode. - */ + // C++ size_t PropertyFlags::decode(const uint8_t* data, size_t size) + this.#clear(); let bytesConsumed = 0; - const bitCount = this.#BITS_IN_BYTE * size; - // There is at least 1 byte (after the leadBits). - let encodedByteCount = 1; - // There is always at least 1 lead bit. - let leadBits = 1; + const BITS_IN_BYTE = 8; + const bitCount = BITS_IN_BYTE * size; + + let encodedByteCount = 1; // There is at least 1 byte (after the leadBits). + let leadBits = 1; // There is always at least 1 lead bit. let inLeadBits = true; let bitAt = 0; - let expectedBitCount = 0; + let expectedBitCount = 0; // Unknown at this stage. let lastValueBit = 0; - for (let byte = 0; byte < size; byte++) { const originalByte = data.getUint8(byte); bytesConsumed += 1; - // Left Most Bit set. - let maskBit = 128; - for (let bit = 0; bit < this.#BITS_IN_BYTE; bit++) { - const bitIsSet: boolean = (originalByte & maskBit) !== 0; - + let maskBit = 0x80; // Left-most bit set. + for (let bit = 0; bit < BITS_IN_BYTE; bit++) { + const bitIsSet = originalByte & maskBit; // Processing of the lead bits. if (inLeadBits) { if (bitIsSet) { encodedByteCount += 1; leadBits += 1; } else { - // Once we hit our first 0, we know we're out of the lead bits. - inLeadBits = false; - expectedBitCount = encodedByteCount * this.#BITS_IN_BYTE - leadBits; + inLeadBits = false; // Once we hit our first 0, we know we're out of the lead bits. + expectedBitCount = encodedByteCount * BITS_IN_BYTE - leadBits; lastValueBit = expectedBitCount + bitAt; // Check to see if the remainder of our buffer is sufficient. @@ -156,31 +179,119 @@ class PropertyFlags { } if (bitIsSet) { - const flag = bitAt - leadBits; - this.setHasProperty(flag, true); + this.setHasProperty(bitAt - leadBits, true); } } bitAt += 1; - maskBit = maskBit >> 1; + maskBit >>= 1; } if (!inLeadBits && bitAt > lastValueBit) { break; } } + this.#_encodedLength = bytesConsumed; + return bytesConsumed; + } - // WEBRTC TODO: Address further C++ code. + /*@devdoc + * Encodes the property flags into a buffer. + * @param {DataView} output - The section of buffer to write to. + * @returns {number} The number of bytes processed. + */ + encode(output: DataView): number { + // C++ QByteArray PropertyFlags::encode() - return bytesConsumed; + if (this.#_maxFlag < this.#_minFlag) { + output.setUint8(0, 0); + return 1; // No flags... nothing to encode. + } + + const BITS_PER_BYTE = 8; + const lengthInBytes = Math.floor(this.#_maxFlag / (BITS_PER_BYTE - 1) + 1); + + for (let i = 0; i < lengthInBytes; i++) { + output.setUint8(i, 0); + } + + // Pack the number of header bits in, the first N-1 to be set to 1, the last to be set to 0. + for (let i = 0; i < lengthInBytes; i++) { + const outputIndex = i; + const bitValue = i < lengthInBytes - 1 ? 1 : 0; + const original = output.getUint8(outputIndex / BITS_PER_BYTE); + const shiftBy = BITS_PER_BYTE - (outputIndex % BITS_PER_BYTE + 1); + const thisBit = bitValue << shiftBy; + output.setUint8(i / BITS_PER_BYTE, original | thisBit); + } + + // Pack the actual bits from the bit array. + for (let i = lengthInBytes; i < lengthInBytes + this.#_maxFlag + 1; i++) { + const flagIndex = i - lengthInBytes; + const outputIndex = i; + const bitValue = this.#_flags[flagIndex] ? 1 : 0; + const original = output.getUint8(outputIndex / BITS_PER_BYTE); + const shiftBy = BITS_PER_BYTE - (outputIndex % BITS_PER_BYTE + 1); + const thisBit = bitValue << shiftBy; + output.setUint8(i / BITS_PER_BYTE, original | thisBit); + } + + this.#_encodedLength = lengthInBytes; + return lengthInBytes; + } + + /*@devdoc + * Outputs debug information about the property flags to the console. + * @param {string} [prefix] - A string to prefix the debug output with. + */ + debugDumpBits(prefix?: string): void { + // C++ void PropertyFlags::debugDumpBits() + // console.debug("#_minFlag =", this.#_minFlag); + // console.debug("#_maxFlag =", this.#_maxFlag); + // console.debug("#_trailingFlipped =", this.#_trailingFlipped); + // console.debug("#_encodedLength =", this.#_encodedLength); + // console.debug("#_flags.length =", this.#_flags.length); + let bits = ""; + for (let i = 0; i < this.#_flags.length; i++) { + bits += this.#_flags[i] ? "1" : "0"; + } + console.debug(`${prefix ? prefix + " " : ""}bits:`, bits); } #clear(): void { - // C++ void clear() - this.#_flags = new Uint8Array(0); + // C++ void clear() + this.#_flags = []; this.#_maxFlag = Number.MIN_SAFE_INTEGER; this.#_minFlag = Number.MAX_SAFE_INTEGER; this.#_trailingFlipped = false; + this.#_encodedLength = 0; + } + + #resize(size: number): void { + // C++ void QBitArray::resize(qsizetype size) + if (size < this.#_flags.length) { + for (let i = this.#_flags.length; i > size; i--) { + this.#_flags.pop(); + } + } else if (size > this.#_flags.length) { + for (let i = this.#_flags.length; i < size; i++) { + this.#_flags[i] = false; + } + } + const BITS_PER_BYTE = 8; + this.#_encodedLength = Math.ceil(this.#_flags.length / BITS_PER_BYTE); + } - // WEBRTC TODO: Address further C++ code. + #shrinkIfNeeded(): void { + // C++ template inline void PropertyFlags::shrinkIfNeeded() + const maxFlagWas = this.#_maxFlag; + while (this.#_maxFlag >= 0) { + if (this.#_flags[this.#_maxFlag]) { + break; + } + this.#_maxFlag -= 1; + } + if (maxFlagWas !== this.#_maxFlag) { + this.#resize(this.#_maxFlag + 1); + } } } diff --git a/tests/EntityServer.unit.test.js b/tests/EntityServer.unit.test.js index a24d2f97..7c1aa165 100644 --- a/tests/EntityServer.unit.test.js +++ b/tests/EntityServer.unit.test.js @@ -15,6 +15,9 @@ AccountManagerMock.mock(); import { webcrypto } from "crypto"; globalThis.crypto = webcrypto; +import { HostType } from "../src/domain/entities/EntityItem"; +import { EntityType } from "../src/domain/entities/EntityTypes"; +import Uuid from "../src/domain/shared/Uuid"; import Camera from "../src/Camera"; import DomainServer from "../src/DomainServer"; import EntityServer from "../src/EntityServer"; @@ -30,4 +33,130 @@ describe("EntityServer - unit tests", () => { expect(entityServer instanceof EntityServer).toBe(true); }); + test("Can get user permissions", () => { + + const domainServer = new DomainServer(); + const camera = new Camera(domainServer.contextID); // eslint-disable-line @typescript-eslint/no-unused-vars + const entityServer = new EntityServer(domainServer.contextID); + + expect(typeof entityServer.canRez).toBe("boolean"); + expect(typeof entityServer.canRezChanged.connect).toBe("function"); + expect(typeof entityServer.canRezChanged.disconnect).toBe("function"); + expect(typeof entityServer.canRezTemp).toBe("boolean"); + expect(typeof entityServer.canRezTempChanged.connect).toBe("function"); + expect(typeof entityServer.canRezTempChanged.disconnect).toBe("function"); + expect(typeof entityServer.canGetAndSetPrivateUserData).toBe("boolean"); + expect(typeof entityServer.canGetAndSetPrivateUserDataChanged.connect).toBe("function"); + expect(typeof entityServer.canGetAndSetPrivateUserDataChanged.disconnect).toBe("function"); + }); + + test("Calling addEntity() with invalid parameters generates errors", () => { + const domainServer = new DomainServer(); + const camera = new Camera(domainServer.contextID); // eslint-disable-line @typescript-eslint/no-unused-vars + const entityServer = new EntityServer(domainServer.contextID); + + let errorMessage = ""; + const error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + // No parameters. + let uuid = entityServer.addEntity(); + expect(errorMessage).toBe("[EntityServer] addEntity() called with invalid entity properties!"); + expect(uuid.isNull()).toBe(true); + + // Missing entity type. + uuid = entityServer.addEntity({ name: "something" }); + expect(errorMessage).toBe("[EntityServer] addEntity() called with invalid entity type!"); + expect(uuid.isNull()).toBe(true); + + // Invalid host type. + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + uuid = entityServer.addEntity({ entityType: EntityType.Shape }, 7); + expect(errorMessage).toBe("[EntityServer] addEntity() called with invalid entity hostType!"); + expect(uuid.isNull()).toBe(true); + + // Unsupported host type. + uuid = entityServer.addEntity({ entityType: EntityType.Shape }, HostType.AVATAR); + expect(errorMessage).toBe("[EntityServer] addEntity() for avatar entities not implemented!"); + expect(uuid.isNull()).toBe(true); + uuid = entityServer.addEntity({ entityType: EntityType.Shape }, HostType.LOCAL); + expect(errorMessage).toBe("[EntityServer] addEntity() for local entities not implemented!"); + expect(uuid.isNull()).toBe(true); + + // Successful call. + errorMessage = ""; + uuid = entityServer.addEntity({ entityType: EntityType.Shape }, HostType.DOMAIN); + expect(errorMessage).toBe(""); + expect(uuid.isNull()).toBe(false); + + error.mockRestore(); + }); + + test("Calling editEntity() with invalid parameters generates errors", () => { + const domainServer = new DomainServer(); + const camera = new Camera(domainServer.contextID); // eslint-disable-line @typescript-eslint/no-unused-vars + const entityServer = new EntityServer(domainServer.contextID); + + let warnMessage = ""; + const warn = jest.spyOn(console, "warn").mockImplementation((...message) => { + warnMessage = message.join(" "); + }); + let errorMessage = ""; + const error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + // No parameters. + let uuid = entityServer.editEntity(); + expect(warnMessage).toBe(""); + expect(errorMessage).toBe("[EntityServer] editEntity() called with invalid entity ID!"); + expect(uuid.isNull()).toBe(true); + + // Invalid ID. + uuid = entityServer.editEntity("string"); + expect(warnMessage).toBe(""); + expect(errorMessage).toBe("[EntityServer] editEntity() called with invalid entity ID!"); + expect(uuid.isNull()).toBe(true); + + // Invalid properties. + uuid = entityServer.editEntity(Uuid.createUuid(), "string"); + expect(warnMessage).toBe(""); + expect(errorMessage).toBe("[EntityServer] editEntity() called with invalid entity properties!"); + expect(uuid.isNull()).toBe(true); + + // Required properties missing. + uuid = entityServer.editEntity(Uuid.createUuid(), { + color: { red: 255, green: 255, blue: 255 } + }); + expect(warnMessage).toBe(""); + expect(errorMessage).toBe("[EntityServer] editEntity() called with invalid entity type value!"); + expect(uuid.isNull()).toBe(true); + uuid = entityServer.editEntity(Uuid.createUuid(), { + entityType: EntityType.Box, + color: { red: 255, green: 255, blue: 255 } + }); + expect(warnMessage).toBe(""); + expect(errorMessage).toBe("[EntityServer] editEntity() called with invalid lastEdited value!"); + expect(uuid.isNull()).toBe(true); + + // Valid properties. + errorMessage = ""; + const MS_TO_TICKS = 10000n; + uuid = entityServer.editEntity(Uuid.createUuid(), { + entityType: EntityType.Box, + lastEdited: BigInt(Date.now()) * MS_TO_TICKS, + color: { red: 255, green: 255, blue: 255 } + }); + expect(warnMessage).toBe("[EntityServer] Could not send edit message because not connected."); + expect(errorMessage).toBe(""); + expect(uuid.isNull()).toBe(true); + + // Invalid property values. + // Tested in EntityEditPacketSender.unit.test.js. + + warn.mockRestore(); + error.mockRestore(); + }); + }); diff --git a/tests/domain/entities/AmbientLightPropertyGroup.unit.test.js b/tests/domain/entities/AmbientLightPropertyGroup.unit.test.js new file mode 100644 index 00000000..6530e09c --- /dev/null +++ b/tests/domain/entities/AmbientLightPropertyGroup.unit.test.js @@ -0,0 +1,121 @@ +// +// AmbientLightPropertyGroup.unit.test.js +// +// Created by David Rowe on 7 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import AmbientLightPropertyGroup from "../../../src/domain/entities/AmbientLightPropertyGroup"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + +import { buffer2hex } from "../../testUtils"; + + +describe("AmbientLightPropertyGroup - unit test", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let entityProperties = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let packetContext = null; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + packetContext = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + + test("Can calculate changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + ambientLight: { + intensity: 1250, + url: "abcd" + } + }; + const changedProperties = AmbientLightPropertyGroup.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + + // Not AmbientLightPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + + // AmbientLightPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL)).toBe(true); + }); + + test("Can append properties to a buffer", () => { + entityProperties = { + ambientLight: { + intensity: 1250, + url: "abcd" + }, + color: { red: 10, green: 20, blue: 30 } + }; + + // Successful write of all. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = AmbientLightPropertyGroup.appendToEditPacket(data, 2, entityProperties, packetContext); + expect(bytesWritten).toBe(10); + expect(buffer2hex(data.buffer)).toEqual("000000409c4404006162636400000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(2); + expect(packetContext.appendState).toBe(AppendState.COMPLETED); + expect(errorMessage).toBe(""); + + // Successful write of only those that can fit. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = AmbientLightPropertyGroup.appendToEditPacket(data, 9, entityProperties, packetContext); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000409c44000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL)).toBe(true); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_INTENSITY)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_AMBIENT_LIGHT_URL)).toBe(false); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(1); + expect(packetContext.appendState).toBe(AppendState.PARTIAL); + expect(errorMessage).toBe(""); + + tearDown(); + }); + +}); diff --git a/tests/domain/entities/AnimationPropertyGroup.unit.test.js b/tests/domain/entities/AnimationPropertyGroup.unit.test.js new file mode 100644 index 00000000..4b52c31e --- /dev/null +++ b/tests/domain/entities/AnimationPropertyGroup.unit.test.js @@ -0,0 +1,129 @@ +// +// AnimationPropertyGroup.unit.test.js +// +// Created by David Rowe on 2 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import AnimationPropertyGroup from "../../../src/domain/entities/AnimationPropertyGroup"; +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + +import { buffer2hex } from "../../testUtils"; + + +describe("AnimationPropertyGroup - unit test", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let entityProperties = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let packetContext = null; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + packetContext = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + + test("Can calculate changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + animation: { + url: "http://foo.com/bar.fbx", + hold: true + } + }; + const changedProperties = AnimationPropertyGroup.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + + // Not AnimationPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + + // AnimationPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)).toBe(false); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)).toBe(true); + }); + + test("Can append properties to a buffer", () => { + entityProperties = { + animation: { + url: "abcd", + loop: true, + hold: true + }, + color: { red: 10, green: 20, blue: 30 } + }; + + // Successful write of all. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ANIMATION_URL, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-animation + bytesWritten = AnimationPropertyGroup.appendToEditPacket(data, 2, entityProperties, packetContext); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("00000400616263640101000000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(3); + expect(packetContext.appendState).toBe(AppendState.COMPLETED); + expect(errorMessage).toBe(""); + + // Successful write of only those that can fit. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ANIMATION_URL, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-animation + bytesWritten = AnimationPropertyGroup.appendToEditPacket(data, 11, entityProperties, packetContext); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000000101000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)).toBe(true); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)).toBe(false); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(2); + expect(packetContext.appendState).toBe(AppendState.PARTIAL); + expect(errorMessage).toBe(""); + + tearDown(); + }); + +}); diff --git a/tests/domain/entities/BloomPropertyGroup.unit.test.js b/tests/domain/entities/BloomPropertyGroup.unit.test.js new file mode 100644 index 00000000..e8c597d8 --- /dev/null +++ b/tests/domain/entities/BloomPropertyGroup.unit.test.js @@ -0,0 +1,122 @@ +// +// BloomPropertyGroup.unit.test.js +// +// Created by David Rowe on 10 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import BloomPropertyGroup from "../../../src/domain/entities/BloomPropertyGroup"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + +import { buffer2hex } from "../../testUtils"; + + +describe("BloomPropertyGroup - unit test", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let entityProperties = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let packetContext = null; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + packetContext = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + + test("Can calculate changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + bloom: { + intensity: 0.5, + size: 1.0 + } + }; + const changedProperties = BloomPropertyGroup.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + + // Not BloomPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + + // BloomPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_BLOOM_THRESHOLD)).toBe(false); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_BLOOM_SIZE)).toBe(true); + }); + + test("Can append properties to a buffer", () => { + entityProperties = { + bloom: { + intensity: 1250, + size: 15 + }, + color: { red: 10, green: 20, blue: 30 } + }; + + // Successful write of all. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_BLOOM_SIZE, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = BloomPropertyGroup.appendToEditPacket(data, 2, entityProperties, packetContext); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("000000409c4400007041000000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_BLOOM_SIZE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_BLOOM_SIZE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(2); + expect(packetContext.appendState).toBe(AppendState.COMPLETED); + expect(errorMessage).toBe(""); + + // Successful write of only those that can fit. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_BLOOM_SIZE, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = BloomPropertyGroup.appendToEditPacket(data, 9, entityProperties, packetContext); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000409c44000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_BLOOM_SIZE)).toBe(true); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_BLOOM_INTENSITY)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_BLOOM_SIZE)).toBe(false); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(1); + expect(packetContext.appendState).toBe(AppendState.PARTIAL); + expect(errorMessage).toBe(""); + + tearDown(); + }); + +}); diff --git a/tests/domain/entities/EntityEditPacketSender.unit.test.js b/tests/domain/entities/EntityEditPacketSender.unit.test.js new file mode 100644 index 00000000..cfa99218 --- /dev/null +++ b/tests/domain/entities/EntityEditPacketSender.unit.test.js @@ -0,0 +1,165 @@ +// +// EntityEditPacketSender.unit.test.js +// +// Created by David Rowe on 19 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { webcrypto } from "crypto"; +globalThis.crypto = webcrypto; + +import EntityEditPacketSender from "../../../src/domain/entities/EntityEditPacketSender"; +import { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import { EntityType } from "../../../src/domain/entities/EntityTypes"; +import PacketScribe from "../../../src/domain/networking/packets/PacketScribe"; +import PacketType from "../../../src/domain/networking/udt/PacketHeaders"; +import AccountManager from "../../../src/domain/networking/AccountManager"; +import AddressManager from "../../../src/domain/networking/AddressManager"; +import NodeList from "../../../src/domain/networking/NodeList"; +import NodePermissions from "../../../src/domain/networking/NodePermissions"; +import ContextManager from "../../../src/domain/shared/ContextManager"; +import Uuid from "../../../src/domain/shared/Uuid"; + + +describe("EntityEditPacketSender - unit tests", () => { + + test("An EntityEditPacketSender can be obtained from the ContextManager", () => { + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, EntityEditPacketSender, contextID); + const entityEditPacketSender = ContextManager.get(contextID, EntityEditPacketSender); + expect(entityEditPacketSender instanceof EntityEditPacketSender).toBe(true); + }); + + test("AVATAR_SELF_ID parentID property value is replaced with the user's session UUID", (done) => { + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, EntityEditPacketSender, contextID); + const entityEditPacketSender = ContextManager.get(contextID, EntityEditPacketSender); + + const log = jest.spyOn(console, "log").mockImplementation((...message) => { + const messageString = message.join(" "); + const EXPECTED_MESSAGE_SLICE = "[networking] NodeList UUID changed from 00000000-0000-0000-0000-000000000000"; + expect(messageString.slice(0, EXPECTED_MESSAGE_SLICE.length)).toBe(EXPECTED_MESSAGE_SLICE); + }); + + const sessionUUID = Uuid.createUuid(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + ContextManager.get(contextID, NodeList).setSessionUUID(sessionUUID); + + log.mockRestore(); + + // eslint-disable-next-line @typescript-eslint/unbound-method + const originalWrite = PacketScribe.EntityEdit.write; + const queueOctreeEditPacket = jest.spyOn(entityEditPacketSender, "queueOctreeEditMessage").mockImplementation(() => { + queueOctreeEditPacket.mockRestore(); + PacketScribe.EntityEdit.write = originalWrite; + done(); + }); + PacketScribe.EntityEdit.write = jest.fn((info) => { + expect(info.properties.parentID).toBe(sessionUUID); + return originalWrite(info); + }); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + entityEditPacketSender.queueEditEntityMessage(PacketType.EntityEdit, Uuid.createUuid(), { + entityType: EntityType.Box, + lastEdited: 0n, + parentID: new Uuid(Uuid.AVATAR_SELF_ID) + }); + }); + + test("Private user data property is removed if user doesn't have necessary permissions", (done) => { + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, EntityEditPacketSender, contextID); + const entityEditPacketSender = ContextManager.get(contextID, EntityEditPacketSender); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + const newPermissions = new NodePermissions(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + ContextManager.get(contextID, NodeList).setPermissions(newPermissions); + + expect.assertions(2); + + let testWithPermissions = false; + + // eslint-disable-next-line @typescript-eslint/unbound-method + const originalWrite = PacketScribe.EntityEdit.write; + const queueOctreeEditPacket = jest.spyOn(entityEditPacketSender, "queueOctreeEditMessage").mockImplementation(() => { + if (testWithPermissions) { + queueOctreeEditPacket.mockRestore(); + PacketScribe.EntityEdit.write = originalWrite; + done(); + } + }); + PacketScribe.EntityEdit.write = jest.fn((info) => { + expect(info.requestedProperties.getHasProperty(EntityPropertyList.PROP_PRIVATE_USER_DATA)) + .toBe(testWithPermissions); + return originalWrite(info); + }); + + testWithPermissions = false; + newPermissions.permissions = NodePermissions.Permission.canAdjustLocks; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + ContextManager.get(contextID, NodeList).setPermissions(newPermissions); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + entityEditPacketSender.queueEditEntityMessage(PacketType.EntityEdit, Uuid.createUuid(), { + entityType: EntityType.Box, + lastEdited: 0n, + privateUserData: "private user data", + color: { red: 0, green: 0, blue: 0 } + }); + + testWithPermissions = true; + newPermissions.permissions = NodePermissions.Permission.canGetAndSetPrivateUserData; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + ContextManager.get(contextID, NodeList).setPermissions(newPermissions); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + entityEditPacketSender.queueEditEntityMessage(PacketType.EntityEdit, Uuid.createUuid(), { + entityType: EntityType.Box, + lastEdited: 0n, + privateUserData: "private user data", + color: { red: 0, green: 0, blue: 0 } + }); + }); + + test("Error is logged when entity property value is invalid", () => { + let errorMessage = ""; + const error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + const warn = jest.spyOn(console, "warn").mockImplementation(() => { /* no-op */ }); + + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, EntityEditPacketSender, contextID); + const entityEditPacketSender = ContextManager.get(contextID, EntityEditPacketSender); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + entityEditPacketSender.queueEditEntityMessage(PacketType.EntityEdit, Uuid.createUuid(), { + entityType: EntityType.Box, + lastEdited: 0n, + parentID: new Uuid(Uuid.AVATAR_SELF_ID), + color: { red: 255, green: 255, blue: "" } + }); + + expect(errorMessage).toBe("[EntityServer] Cannot write invalid color value to packet!"); + + error.mockRestore(); + warn.mockRestore(); + }); + +}); diff --git a/tests/domain/entities/EntityItem.unit.test.js b/tests/domain/entities/EntityItem.unit.test.js new file mode 100644 index 00000000..09885423 --- /dev/null +++ b/tests/domain/entities/EntityItem.unit.test.js @@ -0,0 +1,22 @@ +// +// EntityItem.unit.test.js +// +// Created by David Rowe on 19 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { HostType } from "../../../src/domain/entities/EntityItem"; + +describe("EntityItem - unit tests", () => { + + test("Can get entity host types", () => { + expect(HostType.DOMAIN).toBe(0); + expect(HostType.AVATAR).toBe(1); + expect(HostType.LOCAL).toBe(2); + }); + +}); diff --git a/tests/domain/entities/EntityItemProperties.unit.test.js b/tests/domain/entities/EntityItemProperties.unit.test.js new file mode 100644 index 00000000..e1b76054 --- /dev/null +++ b/tests/domain/entities/EntityItemProperties.unit.test.js @@ -0,0 +1,221 @@ +// +// EntityItemProperties.unit.test.js +// +// Created by David Rowe on 26 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityItemProperties from "../../../src/domain/entities/EntityItemProperties"; +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import { EntityType } from "../../../src/domain/entities/EntityTypes"; +import UDT from "../../../src/domain/networking/udt/UDT"; +import MessageData from "../../../src/domain/networking/MessageData"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; +import Uuid from "../../../src/domain/shared/Uuid"; + +import { buffer2hex } from "../../testUtils"; + + +describe("EntityItemsProperties - unit test", () => { + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + test("No changed properties are calculated for empty properties value", () => { + const properties = {}; + const changedProperties = EntityItemProperties.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(true); + for (const property in EntityPropertyList) { // eslint-disable-line + expect(changedProperties.getHasProperty(EntityPropertyList[property])).toBe(false); + } + }); + + test("Can calculate primary changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + alpha: 0.5 + }; + const changedProperties = EntityItemProperties.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_DIMENSIONS)).toBe(false); + }); + + test("Can calculate property group changed properties", () => { + const properties = { + entityType: EntityType.Model, + position: { x: 0, y: 0, z: 0 }, + alpha: 0.5, + animation: { + url: "http://foo.com/bar.fbx", + hold: true + } + }; + const changedProperties = EntityItemProperties.getChangedProperties(properties); + + // Primary properties. + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ALPHA)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + + // Property group properties. + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_URL)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_HOLD)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_ANIMATION_LOOP)).toBe(false); + }); + + test("Can encode an entity edit with all properties fitting and less than max size property flags", () => { + // eslint-disable-next-line max-len + const EXPECTED_MESSAGE = "00b685f1f20a000600b71d53802fcc483393a79a49670175874000fff000020000000000000000401000a82f40b6ee8946ccb50402b88d72a546f02594"; + + const properties = { + lastEdited: 1688896885851574n, // Date.now() as count of 100ns ticks since the Unix epoch + 44 clock skew. + lastEditedBy: new Uuid("a82f40b6-ee89-46cc-b504-02b88d72a546"), + entityType: EntityType.Box, + color: { red: 240, green: 37, blue: 148 } + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityID = new Uuid("b71d5380-2fcc-4833-93a7-9a4967017587"); + + const messageData = new MessageData(); + messageData.buffer = new Uint8Array(UDT.MAX_PACKET_SIZE); + messageData.dataPosition = 0; + messageData.packetSize = UDT.MAX_PACKET_SIZE; + + const appendState = EntityItemProperties.encodeEntityEditPacket(/* PacketTypeValue.EntityEdit, */ entityID, + properties, messageData, requestedProperties, didntFitProperties); + expect(appendState).toBe(AppendState.COMPLETED); + expect(didntFitProperties.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(false); + expect(didntFitProperties.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(didntFitProperties.isEmpty()).toBe(true); + expect(buffer2hex(messageData.buffer.slice(0, messageData.dataPosition))).toBe(EXPECTED_MESSAGE); + expect(messageData.dataPosition).toBe(EXPECTED_MESSAGE.length / 2); + }); + + test("Can encode an entity edit with some properties that didn't fit", () => { + /* eslint-disable max-len */ + // Full message from previous test: + // "00b685f1f20a000600b71d53802fcc483393a79a49670175874000fff000020000000000000000401000a82f40b6ee8946ccb50402b88d72a546f02594"; + // Partial message without trailing color properties: + const EXPECTED_MESSAGE = "00b685f1f20a000600b71d53802fcc483393a79a49670175874000fff00000000000000000000040f02594"; + // PROP_LAST_EDITED_BY not written so bit flipped to 0: ^ + /* eslint-enable max-len */ + const EXPECTED_MESSAGE_BYTES = EXPECTED_MESSAGE.length / 2; + + const properties = { + lastEdited: 1688896885851574n, // Date.now() as count of 100ns ticks since the Unix epoch + 44 clock skew. + lastEditedBy: new Uuid("a82f40b6-ee89-46cc-b504-02b88d72a546"), + entityType: EntityType.Box, + color: { red: 240, green: 37, blue: 148 } + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityID = new Uuid("b71d5380-2fcc-4833-93a7-9a4967017587"); + + const BUFFER_SIZE = EXPECTED_MESSAGE_BYTES + 6 + 2; // 6 bytes for non-reduced packet flags, 2 bytes extra. + const messageData = new MessageData(); + messageData.buffer = new Uint8Array(BUFFER_SIZE); + messageData.dataPosition = 0; + messageData.packetSize = BUFFER_SIZE; + + const appendState = EntityItemProperties.encodeEntityEditPacket(/* PacketTypeValue.EntityEdit, */ entityID, + properties, messageData, requestedProperties, didntFitProperties); + + expect(appendState).toBe(AppendState.PARTIAL); + expect(didntFitProperties.isEmpty()).toBe(false); + expect(didntFitProperties.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(true); + expect(didntFitProperties.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(buffer2hex(messageData.buffer.slice(0, messageData.dataPosition))).toBe(EXPECTED_MESSAGE); + expect(messageData.dataPosition).toBe(EXPECTED_MESSAGE.length / 2); + }); + + test("If no entity properties can fit in the packet then the encode return value reflects this", () => { + const properties = { + lastEdited: 1688896885851574n, // Date.now() as count of 100ns ticks since the Unix epoch + 44 clock skew. + lastEditedBy: new Uuid("a82f40b6-ee89-46cc-b504-02b88d72a546"), + entityType: EntityType.Box, + color: { red: 240, green: 37, blue: 148 } + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityID = new Uuid("b71d5380-2fcc-4833-93a7-9a4967017587"); + + const BUFFER_SIZE = 48; // 2 available bytes after property flags. + const messageData = new MessageData(); + messageData.buffer = new Uint8Array(BUFFER_SIZE); + messageData.dataPosition = 0; + messageData.packetSize = BUFFER_SIZE; + + const appendState = EntityItemProperties.encodeEntityEditPacket(/* PacketTypeValue.EntityEdit, */ entityID, + properties, messageData, requestedProperties, didntFitProperties); + + expect(appendState).toBe(AppendState.NONE); + expect(didntFitProperties.isEmpty()).toBe(false); + expect(didntFitProperties.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(true); + expect(didntFitProperties.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(messageData.dataPosition).toBe(0); + }); + + test("Can encode entity properties that include entity group properties", () => { + /* eslint-disable-next-line max-len */ + const EXPECTED_MESSAGE = "0078251859f8010600d6f2fb67489a42f38dc01b38e00367a21000fffc000080000000000000000000011000f2ca635c06214311bfe618113cf0eb4200007041"; + + const properties = { + lastEdited: 1691016018535800n, // Date.now() as count of 100ns ticks since the Unix epoch + 44 clock skew. + lastEditedBy: new Uuid("f2ca635c-0621-4311-bfe6-18113cf0eb42"), + entityType: EntityType.Model, + animation: { + fps: 15 + } + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityID = new Uuid("d6f2fb67-489a-42f3-8dc0-1b38e00367a2"); + + const messageData = new MessageData(); + messageData.buffer = new Uint8Array(UDT.MAX_PACKET_SIZE); + messageData.dataPosition = 0; + messageData.packetSize = UDT.MAX_PACKET_SIZE; + + const appendState = EntityItemProperties.encodeEntityEditPacket(/* PacketTypeValue.EntityEdit, */ entityID, + properties, messageData, requestedProperties, didntFitProperties); + expect(appendState).toBe(AppendState.COMPLETED); + expect(didntFitProperties.isEmpty()).toBe(true); + expect(buffer2hex(messageData.buffer.slice(0, messageData.dataPosition))).toBe(EXPECTED_MESSAGE); + expect(messageData.dataPosition).toBe(EXPECTED_MESSAGE.length / 2); + }); + + test("Can encode entity properties that include grab properties", () => { + /* eslint-disable-next-line max-len */ + const EXPECTED_MESSAGE = "004aa309bbd4020600b71d53802fcc483393a79a49670175876000f00002004010008f9e0f5a58c544708eef15d86838191c01"; + + const properties = { + lastEdited: 1691962554557258n, // Date.now() as count of 100ns ticks since the Unix epoch + 44 clock skew. + lastEditedBy: new Uuid("8f9e0f5a-58c5-4470-8eef-15d86838191c"), + entityType: EntityType.Shape, + grab: { + grabbable: true + } + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityID = new Uuid("b71d5380-2fcc-4833-93a7-9a4967017587"); + + const messageData = new MessageData(); + messageData.buffer = new Uint8Array(UDT.MAX_PACKET_SIZE); + messageData.dataPosition = 0; + messageData.packetSize = UDT.MAX_PACKET_SIZE; + + const appendState = EntityItemProperties.encodeEntityEditPacket(/* PacketTypeValue.EntityEdit, */ entityID, + properties, messageData, requestedProperties, didntFitProperties); + expect(appendState).toBe(AppendState.COMPLETED); + expect(didntFitProperties.isEmpty()).toBe(true); + expect(buffer2hex(messageData.buffer.slice(0, messageData.dataPosition))).toBe(EXPECTED_MESSAGE); + expect(messageData.dataPosition).toBe(EXPECTED_MESSAGE.length / 2); + }); + +}); diff --git a/tests/domain/entities/EntityPropertyFlags.unit.test.js b/tests/domain/entities/EntityPropertyFlags.unit.test.js new file mode 100644 index 00000000..f1645f5b --- /dev/null +++ b/tests/domain/entities/EntityPropertyFlags.unit.test.js @@ -0,0 +1,38 @@ +// +// EntityPropertyFlags.unit.test.js +// +// Created by David Rowe on 27 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; + +describe("EntityPropertyFlags - unit test", () => { + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + test("Can access EntityPropertyList values", () => { + expect(EntityPropertyList.PROP_PAGED_PROPERTY).toBe(0); + expect(EntityPropertyList.PROP_DERIVED_34).toBe(126); + expect(EntityPropertyList.PROP_LAST_ITEM).toBe(126); + expect(EntityPropertyList.PROP_AFTER_LAST_ITEM).toBe(127); + expect(EntityPropertyList.PROP_MAX_PARTICLES).toBe(92); + expect(EntityPropertyList.PROP_MINOR_TICK_MARKS_COLOR).toBe(110); + }); + + test("Can use EntityPropertyFlags", () => { + const entityPropertyFlags = new EntityPropertyFlags(); + expect(entityPropertyFlags.isEmpty()).toBe(true); + expect(entityPropertyFlags.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + entityPropertyFlags.setHasProperty(EntityPropertyList.PROP_POSITION, true); + expect(entityPropertyFlags.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(entityPropertyFlags.isEmpty()).toBe(false); + entityPropertyFlags.setHasProperty(EntityPropertyList.PROP_POSITION, false); + expect(entityPropertyFlags.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + expect(entityPropertyFlags.isEmpty()).toBe(true); + }); + +}); diff --git a/tests/domain/entities/EntityTypes.unit.test.js b/tests/domain/entities/EntityTypes.unit.test.js new file mode 100644 index 00000000..ba394d48 --- /dev/null +++ b/tests/domain/entities/EntityTypes.unit.test.js @@ -0,0 +1,22 @@ +// +// EntityTypes.unit.test.js +// +// Created by David Rowe on 19 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { EntityType } from "../../../src/domain/entities/EntityTypes"; + +describe("EntityTypes - unit tests", () => { + + test("Can get entity types", () => { + expect(EntityType.Unknown).toBe(0); + expect(EntityType.Box).toBe(1); + expect(EntityType.NUM_TYPES).toBe(17); // eslint-disable-line @typescript-eslint/no-magic-numbers + }); + +}); diff --git a/tests/domain/entities/GrabPropertyGroup.unit.test.js b/tests/domain/entities/GrabPropertyGroup.unit.test.js new file mode 100644 index 00000000..deb11bb9 --- /dev/null +++ b/tests/domain/entities/GrabPropertyGroup.unit.test.js @@ -0,0 +1,147 @@ +// +// GrabPropertyGroup.unit.test.js +// +// Created by David Rowe on 13 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import GrabPropertyGroup from "../../../src/domain/entities/GrabPropertyGroup"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + +import { buffer2hex } from "../../testUtils"; + + +describe("GrabPropertyGroup - unit test", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let entityProperties = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let packetContext = null; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + packetContext = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + + // readEntitySubclassDataFromBuffer() is tested in EntityData.unit.test.js. + + test("Can calculate changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + grab: { + grabbable: true, + triggerable: false, + grabDelegateToParent: true, + equippableIndicatorOffset: { x: 0.1, y: 0.2, z: 0.3 } + } + }; + const changedProperties = GrabPropertyGroup.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + + // Not GrabPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + + // GrabPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_GRAB_KINEMATIC)).toBe(false); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE)).toBe(false); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)).toBe(true); + }); + + test("Can append properties to a buffer", () => { + entityProperties = { + grab: { + grabbable: true, + triggerable: false, + grabDelegateToParent: true, + equippableIndicatorOffset: { x: -4, y: -10.5, z: 1.2 } + }, + color: { red: 10, green: 20, blue: 30 } + }; + + // Successful write of all. + setUp(24); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-grab + bytesWritten = GrabPropertyGroup.appendToEditPacket(data, 2, entityProperties, packetContext); + expect(bytesWritten).toBe(15); + expect(buffer2hex(data.buffer)).toEqual("0000010001000080c0000028c19a99993f00000000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)) + .toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)) + .toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(4); + expect(packetContext.appendState).toBe(AppendState.COMPLETED); + expect(errorMessage).toBe(""); + + // Successful write of only those that can fit. + setUp(24); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-grab + bytesWritten = GrabPropertyGroup.appendToEditPacket(data, 10, entityProperties, packetContext); + expect(bytesWritten).toBe(3); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000100010000000000000000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)) + .toBe(true); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_GRABBABLE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_TRIGGERABLE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_DELEGATE_TO_PARENT)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET)) + .toBe(false); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(3); + expect(packetContext.appendState).toBe(AppendState.PARTIAL); + expect(errorMessage).toBe(""); + + tearDown(); + }); + +}); diff --git a/tests/domain/entities/HazePropertyGroup.unit.test.js b/tests/domain/entities/HazePropertyGroup.unit.test.js new file mode 100644 index 00000000..9a6fa1db --- /dev/null +++ b/tests/domain/entities/HazePropertyGroup.unit.test.js @@ -0,0 +1,122 @@ +// +// HazePropertyGroup.unit.test.js +// +// Created by David Rowe on 10 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import HazePropertyGroup from "../../../src/domain/entities/HazePropertyGroup"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + +import { buffer2hex } from "../../testUtils"; + + +describe("HazePropertyGroup - unit test", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let entityProperties = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let packetContext = null; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + packetContext = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + + test("Can calculate changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + haze: { + range: 1000, + keyLightAltitude: 200 + } + }; + const changedProperties = HazePropertyGroup.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + + // Not HazePropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + + // HazePropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_RANGE)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_ATTENUATE_KEYLIGHT)).toBe(false); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE)).toBe(true); + }); + + test("Can append properties to a buffer", () => { + entityProperties = { + haze: { + range: 1250, + keyLightAltitude: 15 + }, + color: { red: 10, green: 20, blue: 30 } + }; + + // Successful write of all. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_HAZE_RANGE, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = HazePropertyGroup.appendToEditPacket(data, 2, entityProperties, packetContext); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("000000409c4400007041000000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_HAZE_RANGE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_HAZE_RANGE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(2); + expect(packetContext.appendState).toBe(AppendState.COMPLETED); + expect(errorMessage).toBe(""); + + // Successful write of only those that can fit. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_HAZE_RANGE, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = HazePropertyGroup.appendToEditPacket(data, 9, entityProperties, packetContext); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000409c44000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_HAZE_RANGE)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE)).toBe(true); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_HAZE_RANGE)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_HAZE_KEYLIGHT_ALTITUDE)).toBe(false); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(1); + expect(packetContext.appendState).toBe(AppendState.PARTIAL); + expect(errorMessage).toBe(""); + + tearDown(); + }); + +}); diff --git a/tests/domain/entities/ImageEntityItem.unit.test.js b/tests/domain/entities/ImageEntityItem.unit.test.js index bde3648f..b440d319 100644 --- a/tests/domain/entities/ImageEntityItem.unit.test.js +++ b/tests/domain/entities/ImageEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import ImageEntityItem from "../../../src/domain/entities/ImageEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; describe("ImageEntityItem - unit tests", () => { @@ -24,7 +24,7 @@ describe("ImageEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len diff --git a/tests/domain/entities/KeyLightPropertyGroup.unit.test.js b/tests/domain/entities/KeyLightPropertyGroup.unit.test.js new file mode 100644 index 00000000..16831ec0 --- /dev/null +++ b/tests/domain/entities/KeyLightPropertyGroup.unit.test.js @@ -0,0 +1,133 @@ +// +// KeyLightPropertyGroup.unit.test.js +// +// Created by David Rowe on 2 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import KeyLightPropertyGroup from "../../../src/domain/entities/KeyLightPropertyGroup"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + +import { buffer2hex } from "../../testUtils"; + + +describe("KeyLightPropertyGroup - unit test", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let entityProperties = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let packetContext = null; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + packetContext = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + + test("Can calculate changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + keyLight: { + color: { red: 0, green: 0, blue: 0 }, + shadowMaxDistance: 100 + } + }; + const changedProperties = KeyLightPropertyGroup.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + + // Not KeyLightPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + + // KeyLightPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW)).toBe(false); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)).toBe(true); + }); + + test("Can append properties to a buffer", () => { + entityProperties = { + keyLight: { + color: { red: 4, green: 8, blue: 16 }, + castShadows: true, + shadowMaxDistance: 1250 + }, + color: { red: 10, green: 20, blue: 30 } + }; + + // Successful write of all. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = KeyLightPropertyGroup.appendToEditPacket(data, 2, entityProperties, packetContext); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("00000408100100409c44000000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)) + .toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)) + .toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(3); + expect(packetContext.appendState).toBe(AppendState.COMPLETED); + expect(errorMessage).toBe(""); + + // Successful write of only those that can fit. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW, true); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = KeyLightPropertyGroup.appendToEditPacket(data, 9, entityProperties, packetContext); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000004081001000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)) + .toBe(true); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_CAST_SHADOW)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_KEYLIGHT_SHADOW_MAX_DISTANCE)) + .toBe(false); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(2); + expect(packetContext.appendState).toBe(AppendState.PARTIAL); + expect(errorMessage).toBe(""); + + tearDown(); + }); + +}); diff --git a/tests/domain/entities/LightEntityItem.unit.test.js b/tests/domain/entities/LightEntityItem.unit.test.js index 6f589fc9..a2baa46a 100644 --- a/tests/domain/entities/LightEntityItem.unit.test.js +++ b/tests/domain/entities/LightEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import LightEntityItem from "../../../src/domain/entities/LightEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; describe("LightEntityItem - unit tests", () => { @@ -24,7 +24,7 @@ describe("LightEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len diff --git a/tests/domain/entities/MaterialEntityItem.unit.test.js b/tests/domain/entities/MaterialEntityItem.unit.test.js index eae1e82d..d4337a7f 100644 --- a/tests/domain/entities/MaterialEntityItem.unit.test.js +++ b/tests/domain/entities/MaterialEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import MaterialEntityItem from "../../../src/domain/entities/MaterialEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; describe("MaterialEntityItem - unit tests", () => { @@ -24,7 +24,7 @@ describe("MaterialEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len diff --git a/tests/domain/entities/ModelEntityItem.unit.test.js b/tests/domain/entities/ModelEntityItem.unit.test.js index c45263f8..4c22a09a 100644 --- a/tests/domain/entities/ModelEntityItem.unit.test.js +++ b/tests/domain/entities/ModelEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import ModelEntityItem from "../../../src/domain/entities/ModelEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; import ShapeType from "../../../src/domain/shared/ShapeType"; @@ -25,7 +25,7 @@ describe("ModelEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len @@ -68,15 +68,15 @@ describe("ModelEntityItem - unit tests", () => { expect(modelEntity.properties.groupCulled).toBe(false); expect(modelEntity.properties.blendShapeCoefficients).toBe("{\n}\n"); expect(modelEntity.properties.useOriginalPivot).toBe(false); - expect(modelEntity.properties.animation.animationURL).toBe(""); - expect(modelEntity.properties.animation.animationAllowTranslation).toBe(true); - expect(modelEntity.properties.animation.animationFPS).toBeCloseTo(30, 2); - expect(modelEntity.properties.animation.animationFrameIndex).toBeCloseTo(0, 2); - expect(modelEntity.properties.animation.animationPlaying).toBe(false); - expect(modelEntity.properties.animation.animationLoop).toBe(true); - expect(modelEntity.properties.animation.animationFirstFrame).toBeCloseTo(0, 2); - expect(modelEntity.properties.animation.animationLastFrame).toBeCloseTo(100000, 2); - expect(modelEntity.properties.animation.animationHold).toBe(false); + expect(modelEntity.properties.animation.url).toBe(""); + expect(modelEntity.properties.animation.allowTranslation).toBe(true); + expect(modelEntity.properties.animation.fps).toBeCloseTo(30, 2); + expect(modelEntity.properties.animation.currentFrame).toBeCloseTo(0, 2); + expect(modelEntity.properties.animation.running).toBe(false); + expect(modelEntity.properties.animation.loop).toBe(true); + expect(modelEntity.properties.animation.firstFrame).toBeCloseTo(0, 2); + expect(modelEntity.properties.animation.lastFrame).toBeCloseTo(100000, 2); + expect(modelEntity.properties.animation.hold).toBe(false); }); }); diff --git a/tests/domain/entities/ParticleEffectEntityItem.unit.test.js b/tests/domain/entities/ParticleEffectEntityItem.unit.test.js index 18bbb238..59779e38 100644 --- a/tests/domain/entities/ParticleEffectEntityItem.unit.test.js +++ b/tests/domain/entities/ParticleEffectEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import ParticleEffectEntityItem from "../../../src/domain/entities/ParticleEffectEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; import ShapeType from "../../../src/domain/shared/ShapeType"; @@ -25,7 +25,7 @@ describe("ParticleEffectEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len @@ -90,7 +90,7 @@ describe("ParticleEffectEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len diff --git a/tests/domain/entities/ShapeEntityItem.unit.test.js b/tests/domain/entities/ShapeEntityItem.unit.test.js index 521ea22a..45ecaca2 100644 --- a/tests/domain/entities/ShapeEntityItem.unit.test.js +++ b/tests/domain/entities/ShapeEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import ShapeEntityItem from "../../../src/domain/entities/ShapeEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; describe("ShapeEntityItem - unit tests", () => { @@ -24,7 +24,7 @@ describe("ShapeEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); const bufferHex = "00b4ef0000803f000000000000803f0000803f00000000000000000800547269616e676c65"; diff --git a/tests/domain/entities/SkyboxPropertyGroup.unit.test.js b/tests/domain/entities/SkyboxPropertyGroup.unit.test.js new file mode 100644 index 00000000..28e36d09 --- /dev/null +++ b/tests/domain/entities/SkyboxPropertyGroup.unit.test.js @@ -0,0 +1,121 @@ +// +// SkyboxPropertyGroup.unit.test.js +// +// Created by David Rowe on 8 Aug 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import SkyboxPropertyGroup from "../../../src/domain/entities/SkyboxPropertyGroup"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + +import { buffer2hex } from "../../testUtils"; + + +describe("SkyboxPropertyGroup - unit test", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let entityProperties = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let packetContext = null; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + packetContext = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + + test("Can calculate changed properties", () => { + const properties = { + position: { x: 0, y: 0, z: 0 }, + skybox: { + color: { red: 0, green: 0, blue: 0 }, + url: "abcd" + } + }; + const changedProperties = SkyboxPropertyGroup.getChangedProperties(properties); + expect(changedProperties.isEmpty()).toBe(false); + + // Not SkyboxPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + + // SkyboxPropertyGroup properties... + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR)).toBe(true); + expect(changedProperties.getHasProperty(EntityPropertyList.PROP_SKYBOX_URL)).toBe(true); + }); + + test("Can append properties to a buffer", () => { + entityProperties = { + skybox: { + color: { red: 4, green: 8, blue: 16 }, + url: "abcd" + }, + color: { red: 10, green: 20, blue: 30 } + }; + + // Successful write of all. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_SKYBOX_URL, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = SkyboxPropertyGroup.appendToEditPacket(data, 2, entityProperties, packetContext); + expect(bytesWritten).toBe(9); + expect(buffer2hex(data.buffer)).toEqual("00000408100400616263640000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_SKYBOX_URL)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_SKYBOX_URL)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(2); + expect(packetContext.appendState).toBe(AppendState.COMPLETED); + expect(errorMessage).toBe(""); + + // Successful write of only those that can fit. + setUp(16); + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR, true); // First + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_SKYBOX_URL, true); // Last + packetContext.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); // Non-keyLight + bytesWritten = SkyboxPropertyGroup.appendToEditPacket(data, 9, entityProperties, packetContext); + expect(bytesWritten).toBe(3); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000004081000000000"); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR)).toBe(false); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_SKYBOX_URL)).toBe(true); + expect(packetContext.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_SKYBOX_COLOR)).toBe(true); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_SKYBOX_URL)).toBe(false); + expect(packetContext.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(packetContext.propertyCount).toBe(1); + expect(packetContext.appendState).toBe(AppendState.PARTIAL); + expect(errorMessage).toBe(""); + + tearDown(); + }); + +}); diff --git a/tests/domain/entities/TextEntityItem.unit.test.js b/tests/domain/entities/TextEntityItem.unit.test.js index 4fe15bbd..4065bc7a 100644 --- a/tests/domain/entities/TextEntityItem.unit.test.js +++ b/tests/domain/entities/TextEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import TextEntityItem from "../../../src/domain/entities/TextEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; +import TextEntityItem, { TextAlignment, TextEffect } from "../../../src/domain/entities/TextEntityItem"; describe("TextEntityItem - unit tests", () => { @@ -24,7 +24,7 @@ describe("TextEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len @@ -61,4 +61,17 @@ describe("TextEntityItem - unit tests", () => { expect(textEntity.properties.textAlignment).toBe("left"); }); + test("Can get the index of a TextAlignment value", () => { + expect(Object.values(TextAlignment).indexOf("left")).toBe(0); + expect(Object.values(TextAlignment).indexOf("center")).toBe(1); + expect(Object.values(TextAlignment).indexOf("right")).toBe(2); + }); + + test("Can get the index of a TextEffect value", () => { + expect(Object.values(TextEffect).indexOf("none")).toBe(0); + expect(Object.values(TextEffect).indexOf("outline")).toBe(1); + expect(Object.values(TextEffect).indexOf("outline fill")).toBe(2); + expect(Object.values(TextEffect).indexOf("shadow")).toBe(3); + }); + }); diff --git a/tests/domain/entities/WebEntityItem.unit.test.js b/tests/domain/entities/WebEntityItem.unit.test.js index e5ccff67..a303b7e4 100644 --- a/tests/domain/entities/WebEntityItem.unit.test.js +++ b/tests/domain/entities/WebEntityItem.unit.test.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import WebEntityItem from "../../../src/domain/entities/WebEntityItem"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; describe("WebEntityItem - unit tests", () => { @@ -23,7 +23,7 @@ describe("WebEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len diff --git a/tests/domain/entities/ZoneEntityItem.unit.test.js b/tests/domain/entities/ZoneEntityItem.unit.test.js index 31c7a1ac..69116233 100644 --- a/tests/domain/entities/ZoneEntityItem.unit.test.js +++ b/tests/domain/entities/ZoneEntityItem.unit.test.js @@ -9,10 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import EntityPropertyFlags from "../../../src/domain/entities/EntityPropertyFlags"; import ZoneEntityItem from "../../../src/domain/entities/ZoneEntityItem"; import AvatarPriorityMode from "../../../src/domain/shared/AvatarPriorityMode"; import ComponentMode from "../../../src/domain/shared/ComponentMode"; -import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; import ShapeType from "../../../src/domain/shared/ShapeType"; @@ -27,7 +27,7 @@ describe("ZoneEntityItem - unit tests", () => { })); const encodedFlags = new DataView(bufferArray.buffer); - const propertyFlags = new PropertyFlags(); + const propertyFlags = new EntityPropertyFlags(); propertyFlags.decode(encodedFlags, encodedFlags.byteLength); // eslint-disable-next-line max-len diff --git a/tests/domain/networking/LimitedNodeList.unit.test.js b/tests/domain/networking/LimitedNodeList.unit.test.js index ab9b35e5..df659410 100644 --- a/tests/domain/networking/LimitedNodeList.unit.test.js +++ b/tests/domain/networking/LimitedNodeList.unit.test.js @@ -25,7 +25,7 @@ import ContextManager from "../../../src/domain/shared/ContextManager"; import Uuid from "../../../src/domain/shared/Uuid"; -describe("LimitedNodeList - integration tests", () => { +describe("LimitedNodeList - unit tests", () => { /* eslint-disable @typescript-eslint/no-magic-numbers */ /* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ @@ -328,7 +328,7 @@ describe("LimitedNodeList - integration tests", () => { expect(LimitedNodeList.connectReasonToString(27)).toBe("Invalid"); }); - test("Can set and get domain permissions", (done) => { + test("Can set and get domain kick permissions", (done) => { const limitedNodeList = new LimitedNodeList(contextID); limitedNodeList.canKickChanged.connect(() => { expect(limitedNodeList.getThisNodeCanKick()).toBe(true); @@ -341,6 +341,42 @@ describe("LimitedNodeList - integration tests", () => { limitedNodeList.setPermissions(newPermissions); }); + test("Can set and get domain editing permissions", (done) => { + const limitedNodeList = new LimitedNodeList(contextID); + let hasCanRezChanged = false; + let hasCanRezTempChanged = false; + limitedNodeList.canRezChanged.connect(() => { + hasCanRezChanged = true; + expect(limitedNodeList.getThisNodeCanRez()).toBe(true); + }); + limitedNodeList.canRezTmpChanged.connect(() => { + hasCanRezTempChanged = true; + expect(limitedNodeList.getThisNodeCanRezTmp()).toBe(true); + }); + limitedNodeList.canGetAndSetPrivateUserDataChanged.connect(() => { + expect(limitedNodeList.getThisNodeCanGetAndSetPrivateUserData()).toBe(true); + expect(hasCanRezChanged).toBe(true); + expect(hasCanRezTempChanged).toBe(true); + done(); + }); + + expect(limitedNodeList.getThisNodeCanRez()).toBe(false); + expect(limitedNodeList.getThisNodeCanRezTmp()).toBe(false); + expect(limitedNodeList.getThisNodeCanGetAndSetPrivateUserData()).toBe(false); + let newPermissions = new NodePermissions(); + newPermissions.permissions = NodePermissions.Permission.canRezPermanentEntities; + limitedNodeList.setPermissions(newPermissions); + newPermissions = new NodePermissions(); + newPermissions.permissions = NodePermissions.Permission.canRezPermanentEntities + + NodePermissions.Permission.canRezTemporaryEntities; + limitedNodeList.setPermissions(newPermissions); + newPermissions = new NodePermissions(); + newPermissions.permissions = NodePermissions.Permission.canRezPermanentEntities + + NodePermissions.Permission.canRezTemporaryEntities + + NodePermissions.Permission.canGetAndSetPrivateUserData; + limitedNodeList.setPermissions(newPermissions); + }); + // The following items are tested elsewhere: // - sendPacket() - Tested implicitly by NodeList integration test. diff --git a/tests/domain/networking/packets/EntityData.unit.test.js b/tests/domain/networking/packets/EntityData.unit.test.js index 74aedf5b..145a71db 100644 --- a/tests/domain/networking/packets/EntityData.unit.test.js +++ b/tests/domain/networking/packets/EntityData.unit.test.js @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import { HostType } from "../../../../src/domain/entities/EntityItem"; import EntityData from "../../../../src/domain/networking/packets/EntityData"; import AvatarPriorityMode from "../../../../src/domain/shared/AvatarPriorityMode"; import ComponentMode from "../../../../src/domain/shared/ComponentMode"; @@ -66,6 +67,7 @@ describe("EntityData - unit tests", () => { expect(info[0].created).toBe(1655451515775649n); expect(info[0].lastEditedBy instanceof Uuid).toBe(true); expect(info[0].lastEditedBy.stringify()).toBe("e0a1aa03-b104-4476-a927-28a804e9d44a"); + expect(info[0].entityHostType).toBe(HostType.DOMAIN); expect(info[0].queryAACube.corner.x).toBeCloseTo(0.6662073, 3); expect(info[0].queryAACube.corner.y).toBeCloseTo(-12.5329, 3); expect(info[0].queryAACube.corner.z).toBeCloseTo(2.7988767, 3); @@ -76,33 +78,33 @@ describe("EntityData - unit tests", () => { expect(info[0].ignorePickIntersection).toBe(false); expect(info[0].renderWithZones).toBeUndefined(); expect(info[0].billboardMode).toBe(0); - expect(info[0].grabbable).toBe(false); - expect(info[0].grabKinematic).toBe(true); - expect(info[0].grabFollowsController).toBe(true); - expect(info[0].triggerable).toBe(false); - expect(info[0].grabEquippable).toBe(false); - expect(info[0].delegateToParent).toBe(true); - expect(info[0].equippableLeftPositionOffset.x).toBeCloseTo(0, 2); - expect(info[0].equippableLeftPositionOffset.y).toBeCloseTo(0, 2); - expect(info[0].equippableLeftPositionOffset.z).toBeCloseTo(0, 2); - expect(info[0].equippableLeftRotationOffset.x).toBeCloseTo(-0.0000152588, 8); - expect(info[0].equippableLeftRotationOffset.y).toBeCloseTo(-0.0000152588, 8); - expect(info[0].equippableLeftRotationOffset.z).toBeCloseTo(-0.0000152588, 8); - expect(info[0].equippableLeftRotationOffset.w).toBeCloseTo(1, 2); - expect(info[0].equippableRightPositionOffset.x).toBeCloseTo(0, 2); - expect(info[0].equippableRightPositionOffset.y).toBeCloseTo(0, 2); - expect(info[0].equippableRightPositionOffset.z).toBeCloseTo(0, 2); - expect(info[0].equippableRightRotationOffset.x).toBeCloseTo(-0.0000152588, 8); - expect(info[0].equippableRightRotationOffset.y).toBeCloseTo(-0.0000152588, 8); - expect(info[0].equippableRightRotationOffset.z).toBeCloseTo(-0.0000152588, 8); - expect(info[0].equippableRightRotationOffset.w).toBeCloseTo(1, 2); - expect(info[0].equippableIndicatorURL).toBeUndefined(); - expect(info[0].equippableIndicatorScale.x).toBeCloseTo(1, 2); - expect(info[0].equippableIndicatorScale.y).toBeCloseTo(1, 2); - expect(info[0].equippableIndicatorScale.z).toBeCloseTo(1, 2); - expect(info[0].equippableIndicatorOffset.x).toBeCloseTo(0, 2); - expect(info[0].equippableIndicatorOffset.y).toBeCloseTo(0, 2); - expect(info[0].equippableIndicatorOffset.z).toBeCloseTo(0, 2); + expect(info[0].grab.grabbable).toBe(false); + expect(info[0].grab.grabKinematic).toBe(true); + expect(info[0].grab.grabFollowsController).toBe(true); + expect(info[0].grab.triggerable).toBe(false); + expect(info[0].grab.equippable).toBe(false); + expect(info[0].grab.grabDelegateToParent).toBe(true); + expect(info[0].grab.equippableLeftPositionOffset.x).toBeCloseTo(0, 2); + expect(info[0].grab.equippableLeftPositionOffset.y).toBeCloseTo(0, 2); + expect(info[0].grab.equippableLeftPositionOffset.z).toBeCloseTo(0, 2); + expect(info[0].grab.equippableLeftRotationOffset.x).toBeCloseTo(-0.0000152588, 8); + expect(info[0].grab.equippableLeftRotationOffset.y).toBeCloseTo(-0.0000152588, 8); + expect(info[0].grab.equippableLeftRotationOffset.z).toBeCloseTo(-0.0000152588, 8); + expect(info[0].grab.equippableLeftRotationOffset.w).toBeCloseTo(1, 2); + expect(info[0].grab.equippableRightPositionOffset.x).toBeCloseTo(0, 2); + expect(info[0].grab.equippableRightPositionOffset.y).toBeCloseTo(0, 2); + expect(info[0].grab.equippableRightPositionOffset.z).toBeCloseTo(0, 2); + expect(info[0].grab.equippableRightRotationOffset.x).toBeCloseTo(-0.0000152588, 8); + expect(info[0].grab.equippableRightRotationOffset.y).toBeCloseTo(-0.0000152588, 8); + expect(info[0].grab.equippableRightRotationOffset.z).toBeCloseTo(-0.0000152588, 8); + expect(info[0].grab.equippableRightRotationOffset.w).toBeCloseTo(1, 2); + expect(info[0].grab.equippableIndicatorURL).toBeUndefined(); + expect(info[0].grab.equippableIndicatorScale.x).toBeCloseTo(1, 2); + expect(info[0].grab.equippableIndicatorScale.y).toBeCloseTo(1, 2); + expect(info[0].grab.equippableIndicatorScale.z).toBeCloseTo(1, 2); + expect(info[0].grab.equippableIndicatorOffset.x).toBeCloseTo(0, 2); + expect(info[0].grab.equippableIndicatorOffset.y).toBeCloseTo(0, 2); + expect(info[0].grab.equippableIndicatorOffset.z).toBeCloseTo(0, 2); expect(info[0].density).toBeCloseTo(1000, 2); expect(info[0].velocity.x).toBeCloseTo(0, 2); expect(info[0].velocity.y).toBeCloseTo(0, 2); @@ -117,7 +119,7 @@ describe("EntityData - unit tests", () => { expect(info[0].acceleration.y).toBeCloseTo(0, 2); expect(info[0].acceleration.z).toBeCloseTo(0, 2); expect(info[0].damping).toBeCloseTo(0, 2); - expect(info[0].angularDampling).toBeCloseTo(0, 2); + expect(info[0].angularDamping).toBeCloseTo(0, 2); expect(info[0].restitution).toBeCloseTo(0.5, 3); expect(info[0].friction).toBeCloseTo(0.5, 3); expect(info[0].lifetime).toBeCloseTo(-1, 2); @@ -130,7 +132,7 @@ describe("EntityData - unit tests", () => { expect(info[0].cloneLifetime).toBeCloseTo(300, 2); expect(info[0].cloneLimit).toBeCloseTo(0, 2); expect(info[0].cloneDynamic).toBe(false); - expect(info[0].cloneAvatarIdentity).toBe(false); + expect(info[0].cloneAvatarEntity).toBe(false); expect(info[0].cloneOriginID).toBeUndefined(); expect(info[0].script).toBeUndefined(); expect(info[0].scriptTimestamp).toBe(0n); @@ -167,15 +169,15 @@ describe("EntityData - unit tests", () => { expect(info[0].groupCulled).toBe(false); expect(info[0].blendShapeCoefficients).toBe("{\n}\n"); expect(info[0].useOriginalPivot).toBe(true); - expect(info[0].animation.animationURL).toBe(""); - expect(info[0].animation.animationAllowTranslation).toBe(false); - expect(info[0].animation.animationFPS).toBeCloseTo(30, 2); - expect(info[0].animation.animationFrameIndex).toBeCloseTo(0, 2); - expect(info[0].animation.animationPlaying).toBe(false); - expect(info[0].animation.animationLoop).toBe(true); - expect(info[0].animation.animationFirstFrame).toBeCloseTo(0, 2); - expect(info[0].animation.animationLastFrame).toBeCloseTo(100000, 2); - expect(info[0].animation.animationHold).toBe(false); + expect(info[0].animation.url).toBe(""); + expect(info[0].animation.allowTranslation).toBe(false); + expect(info[0].animation.fps).toBeCloseTo(30, 2); + expect(info[0].animation.currentFrame).toBeCloseTo(0, 2); + expect(info[0].animation.running).toBe(false); + expect(info[0].animation.loop).toBe(true); + expect(info[0].animation.firstFrame).toBeCloseTo(0, 2); + expect(info[0].animation.lastFrame).toBeCloseTo(100000, 2); + expect(info[0].animation.hold).toBe(false); }); test("Can read two Model entities in a packet", () => { @@ -214,6 +216,7 @@ describe("EntityData - unit tests", () => { expect(info[0].registrationPoint).toBeUndefined(); expect(info[0].created).toBeUndefined(); expect(info[0].lastEditedBy).toBeUndefined(); + expect(info[0].entityHostType).toBe(HostType.DOMAIN); expect(info[0].queryAACube).toBeUndefined(); expect(info[0].canCastShadow).toBeUndefined(); expect(info[0].renderLayer).toBeUndefined(); @@ -221,19 +224,7 @@ describe("EntityData - unit tests", () => { expect(info[0].ignorePickIntersection).toBeUndefined(); expect(info[0].renderWithZones).toBeUndefined(); expect(info[0].billboardMode).toBeUndefined(); - expect(info[0].grabbable).toBeUndefined(); - expect(info[0].grabKinematic).toBeUndefined(); - expect(info[0].grabFollowsController).toBeUndefined(); - expect(info[0].triggerable).toBeUndefined(); - expect(info[0].grabEquippable).toBeUndefined(); - expect(info[0].delegateToParent).toBeUndefined(); - expect(info[0].equippableLeftPositionOffset).toBeUndefined(); - expect(info[0].equippableLeftRotationOffset).toBeUndefined(); - expect(info[0].equippableRightPositionOffset).toBeUndefined(); - expect(info[0].equippableRightRotationOffset).toBeUndefined(); - expect(info[0].equippableIndicatorURL).toBeUndefined(); - expect(info[0].equippableIndicatorScale).toBeUndefined(); - expect(info[0].equippableIndicatorOffset).toBeUndefined(); + expect(info[0].grab).toBeUndefined(); expect(info[0].density).toBeUndefined(); expect(info[0].velocity).toBeUndefined(); expect(info[0].angularVelocity).toBeUndefined(); @@ -244,7 +235,7 @@ describe("EntityData - unit tests", () => { expect(info[0].acceleration.y).toBeCloseTo(0, 2); expect(info[0].acceleration.z).toBeCloseTo(0, 2); expect(info[0].damping).toBeUndefined(); - expect(info[0].angularDampling).toBeUndefined(); + expect(info[0].angularDamping).toBeUndefined(); expect(info[0].restitution).toBeCloseTo(0.5, 3); expect(info[0].friction).toBeCloseTo(0.5, 3); expect(info[0].lifetime).toBeCloseTo(-1, 2); @@ -257,7 +248,7 @@ describe("EntityData - unit tests", () => { expect(info[0].cloneLifetime).toBeCloseTo(300, 2); expect(info[0].cloneLimit).toBeCloseTo(0, 2); expect(info[0].cloneDynamic).toBe(false); - expect(info[0].cloneAvatarIdentity).toBe(false); + expect(info[0].cloneAvatarEntity).toBe(false); expect(info[0].cloneOriginID).toBeUndefined(); expect(info[0].script).toBeUndefined(); expect(info[0].scriptTimestamp).toBe(0n); @@ -294,15 +285,15 @@ describe("EntityData - unit tests", () => { expect(info[0].groupCulled).toBe(false); expect(info[0].blendShapeCoefficients).toBe("{\n}\n"); expect(info[0].useOriginalPivot).toBe(true); - expect(info[0].animation.animationURL).toBe(""); - expect(info[0].animation.animationAllowTranslation).toBe(false); - expect(info[0].animation.animationFPS).toBeCloseTo(30, 2); - expect(info[0].animation.animationFrameIndex).toBeCloseTo(0, 2); - expect(info[0].animation.animationPlaying).toBe(false); - expect(info[0].animation.animationLoop).toBe(true); - expect(info[0].animation.animationFirstFrame).toBeCloseTo(0, 2); - expect(info[0].animation.animationLastFrame).toBeCloseTo(100000, 2); - expect(info[0].animation.animationHold).toBe(false); + expect(info[0].animation.url).toBe(""); + expect(info[0].animation.allowTranslation).toBe(false); + expect(info[0].animation.fps).toBeCloseTo(30, 2); + expect(info[0].animation.currentFrame).toBeCloseTo(0, 2); + expect(info[0].animation.running).toBe(false); + expect(info[0].animation.loop).toBe(true); + expect(info[0].animation.firstFrame).toBeCloseTo(0, 2); + expect(info[0].animation.lastFrame).toBeCloseTo(100000, 2); + expect(info[0].animation.hold).toBe(false); // Second Model Entity. expect(info[1].entityItemID instanceof Uuid).toBe(true); @@ -339,6 +330,7 @@ describe("EntityData - unit tests", () => { expect(info[1].created).toBe(1655893407496516n); expect(info[1].lastEditedBy instanceof Uuid).toBe(true); expect(info[1].lastEditedBy.stringify()).toBe("49c19ac5-e413-4579-80e8-a0899444cb01"); + expect(info[1].entityHostType).toBe(HostType.DOMAIN); expect(info[1].queryAACube.corner.x).toBeCloseTo(-1.548620, 3); expect(info[1].queryAACube.corner.y).toBeCloseTo(-2.31339, 3); expect(info[1].queryAACube.corner.z).toBeCloseTo(-0.900225, 3); @@ -349,33 +341,33 @@ describe("EntityData - unit tests", () => { expect(info[1].ignorePickIntersection).toBe(false); expect(info[1].renderWithZones).toBeUndefined(); expect(info[1].billboardMode).toBe(0); - expect(info[1].grabbable).toBe(false); - expect(info[1].grabKinematic).toBe(true); - expect(info[1].grabFollowsController).toBe(true); - expect(info[1].triggerable).toBe(false); - expect(info[1].grabEquippable).toBe(false); - expect(info[1].delegateToParent).toBe(true); - expect(info[1].equippableLeftPositionOffset.x).toBeCloseTo(0, 2); - expect(info[1].equippableLeftPositionOffset.y).toBeCloseTo(0, 2); - expect(info[1].equippableLeftPositionOffset.z).toBeCloseTo(0, 2); - expect(info[1].equippableLeftRotationOffset.x).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableLeftRotationOffset.y).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableLeftRotationOffset.z).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableLeftRotationOffset.w).toBeCloseTo(1, 2); - expect(info[1].equippableRightPositionOffset.x).toBeCloseTo(0, 2); - expect(info[1].equippableRightPositionOffset.y).toBeCloseTo(0, 2); - expect(info[1].equippableRightPositionOffset.z).toBeCloseTo(0, 2); - expect(info[1].equippableRightRotationOffset.x).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableRightRotationOffset.y).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableRightRotationOffset.z).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableRightRotationOffset.w).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorURL).toBeUndefined(); - expect(info[1].equippableIndicatorScale.x).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorScale.y).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorScale.z).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorOffset.x).toBeCloseTo(0, 2); - expect(info[1].equippableIndicatorOffset.y).toBeCloseTo(0, 2); - expect(info[1].equippableIndicatorOffset.z).toBeCloseTo(0, 2); + expect(info[1].grab.grabbable).toBe(false); + expect(info[1].grab.grabKinematic).toBe(true); + expect(info[1].grab.grabFollowsController).toBe(true); + expect(info[1].grab.triggerable).toBe(false); + expect(info[1].grab.equippable).toBe(false); + expect(info[1].grab.grabDelegateToParent).toBe(true); + expect(info[1].grab.equippableLeftPositionOffset.x).toBeCloseTo(0, 2); + expect(info[1].grab.equippableLeftPositionOffset.y).toBeCloseTo(0, 2); + expect(info[1].grab.equippableLeftPositionOffset.z).toBeCloseTo(0, 2); + expect(info[1].grab.equippableLeftRotationOffset.x).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableLeftRotationOffset.y).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableLeftRotationOffset.z).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableLeftRotationOffset.w).toBeCloseTo(1, 2); + expect(info[1].grab.equippableRightPositionOffset.x).toBeCloseTo(0, 2); + expect(info[1].grab.equippableRightPositionOffset.y).toBeCloseTo(0, 2); + expect(info[1].grab.equippableRightPositionOffset.z).toBeCloseTo(0, 2); + expect(info[1].grab.equippableRightRotationOffset.x).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableRightRotationOffset.y).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableRightRotationOffset.z).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableRightRotationOffset.w).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorURL).toBeUndefined(); + expect(info[1].grab.equippableIndicatorScale.x).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorScale.y).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorScale.z).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorOffset.x).toBeCloseTo(0, 2); + expect(info[1].grab.equippableIndicatorOffset.y).toBeCloseTo(0, 2); + expect(info[1].grab.equippableIndicatorOffset.z).toBeCloseTo(0, 2); expect(info[1].density).toBeCloseTo(1000, 2); expect(info[1].velocity.x).toBeCloseTo(0, 2); expect(info[1].velocity.y).toBeCloseTo(0, 2); @@ -390,7 +382,7 @@ describe("EntityData - unit tests", () => { expect(info[1].acceleration.y).toBeCloseTo(0, 2); expect(info[1].acceleration.z).toBeCloseTo(0, 2); expect(info[1].damping).toBeCloseTo(0, 2); - expect(info[1].angularDampling).toBeCloseTo(0, 2); + expect(info[1].angularDamping).toBeCloseTo(0, 2); expect(info[1].restitution).toBeCloseTo(0.5, 3); expect(info[1].friction).toBeCloseTo(0.5, 3); expect(info[1].lifetime).toBeCloseTo(-1, 2); @@ -403,7 +395,7 @@ describe("EntityData - unit tests", () => { expect(info[1].cloneLifetime).toBeCloseTo(300, 2); expect(info[1].cloneLimit).toBeCloseTo(0, 2); expect(info[1].cloneDynamic).toBe(false); - expect(info[1].cloneAvatarIdentity).toBe(false); + expect(info[1].cloneAvatarEntity).toBe(false); expect(info[1].cloneOriginID).toBeUndefined(); expect(info[1].script).toBeUndefined(); expect(info[1].scriptTimestamp).toBe(0n); @@ -440,15 +432,15 @@ describe("EntityData - unit tests", () => { expect(info[1].groupCulled).toBe(false); expect(info[1].blendShapeCoefficients).toBe("{\n}\n"); expect(info[1].useOriginalPivot).toBe(true); - expect(info[1].animation.animationURL).toBe(""); - expect(info[1].animation.animationAllowTranslation).toBe(false); - expect(info[1].animation.animationFPS).toBeCloseTo(30, 2); - expect(info[1].animation.animationFrameIndex).toBeCloseTo(0, 2); - expect(info[1].animation.animationPlaying).toBe(false); - expect(info[1].animation.animationLoop).toBe(true); - expect(info[1].animation.animationFirstFrame).toBeCloseTo(0, 2); - expect(info[1].animation.animationLastFrame).toBeCloseTo(100000, 2); - expect(info[1].animation.animationHold).toBe(false); + expect(info[1].animation.url).toBe(""); + expect(info[1].animation.allowTranslation).toBe(false); + expect(info[1].animation.fps).toBeCloseTo(30, 2); + expect(info[1].animation.currentFrame).toBeCloseTo(0, 2); + expect(info[1].animation.running).toBe(false); + expect(info[1].animation.loop).toBe(true); + expect(info[1].animation.firstFrame).toBeCloseTo(0, 2); + expect(info[1].animation.lastFrame).toBeCloseTo(100000, 2); + expect(info[1].animation.hold).toBe(false); }); test("Can read a Model entity and a Shape entity in the same packet", () => { @@ -497,6 +489,7 @@ describe("EntityData - unit tests", () => { expect(info[0].created).toBe(1657627173666914n); expect(info[0].lastEditedBy instanceof Uuid).toBe(true); expect(info[0].lastEditedBy.stringify()).toBe("1026cbdd-6414-4861-bf88-99a21bed3fd2"); + expect(info[0].entityHostType).toBe(HostType.DOMAIN); expect(info[0].queryAACube.corner.x).toBeCloseTo(-0.68387, 4); expect(info[0].queryAACube.corner.y).toBeCloseTo(-1.85178, 4); expect(info[0].queryAACube.corner.z).toBeCloseTo(0.52001, 4); @@ -507,31 +500,31 @@ describe("EntityData - unit tests", () => { expect(info[0].ignorePickIntersection).toBe(false); expect(info[0].renderWithZones).toBeUndefined(); expect(info[0].billboardMode).toBe(0); - expect(info[0].grabbable).toBe(false); - expect(info[0].grabKinematic).toBe(true); - expect(info[0].grabFollowsController).toBe(true); - expect(info[0].triggerable).toBe(false); - expect(info[0].grabEquippable).toBe(false); - expect(info[0].delegateToParent).toBe(true); - expect(info[0].equippableLeftPositionOffset.x).toBe(0); - expect(info[0].equippableLeftPositionOffset.y).toBe(0); - expect(info[0].equippableLeftPositionOffset.z).toBe(0); - expect(info[0].equippableLeftRotationOffset.x).toBeCloseTo(-0.000015, 6); - expect(info[0].equippableLeftRotationOffset.y).toBeCloseTo(-0.000015, 6); - expect(info[0].equippableLeftRotationOffset.z).toBeCloseTo(-0.000015, 6); - expect(info[0].equippableRightPositionOffset.x).toBe(0); - expect(info[0].equippableRightPositionOffset.y).toBe(0); - expect(info[0].equippableRightPositionOffset.z).toBe(0); - expect(info[0].equippableRightRotationOffset.x).toBeCloseTo(-0.000015, 6); - expect(info[0].equippableRightRotationOffset.y).toBeCloseTo(-0.000015, 6); - expect(info[0].equippableRightRotationOffset.z).toBeCloseTo(-0.000015, 6); - expect(info[0].equippableIndicatorURL).toBeUndefined(); - expect(info[0].equippableIndicatorScale.x).toBe(1); - expect(info[0].equippableIndicatorScale.y).toBe(1); - expect(info[0].equippableIndicatorScale.z).toBe(1); - expect(info[0].equippableIndicatorOffset.x).toBe(0); - expect(info[0].equippableIndicatorOffset.y).toBe(0); - expect(info[0].equippableIndicatorOffset.z).toBe(0); + expect(info[0].grab.grabbable).toBe(false); + expect(info[0].grab.grabKinematic).toBe(true); + expect(info[0].grab.grabFollowsController).toBe(true); + expect(info[0].grab.triggerable).toBe(false); + expect(info[0].grab.equippable).toBe(false); + expect(info[0].grab.grabDelegateToParent).toBe(true); + expect(info[0].grab.equippableLeftPositionOffset.x).toBe(0); + expect(info[0].grab.equippableLeftPositionOffset.y).toBe(0); + expect(info[0].grab.equippableLeftPositionOffset.z).toBe(0); + expect(info[0].grab.equippableLeftRotationOffset.x).toBeCloseTo(-0.000015, 6); + expect(info[0].grab.equippableLeftRotationOffset.y).toBeCloseTo(-0.000015, 6); + expect(info[0].grab.equippableLeftRotationOffset.z).toBeCloseTo(-0.000015, 6); + expect(info[0].grab.equippableRightPositionOffset.x).toBe(0); + expect(info[0].grab.equippableRightPositionOffset.y).toBe(0); + expect(info[0].grab.equippableRightPositionOffset.z).toBe(0); + expect(info[0].grab.equippableRightRotationOffset.x).toBeCloseTo(-0.000015, 6); + expect(info[0].grab.equippableRightRotationOffset.y).toBeCloseTo(-0.000015, 6); + expect(info[0].grab.equippableRightRotationOffset.z).toBeCloseTo(-0.000015, 6); + expect(info[0].grab.equippableIndicatorURL).toBeUndefined(); + expect(info[0].grab.equippableIndicatorScale.x).toBe(1); + expect(info[0].grab.equippableIndicatorScale.y).toBe(1); + expect(info[0].grab.equippableIndicatorScale.z).toBe(1); + expect(info[0].grab.equippableIndicatorOffset.x).toBe(0); + expect(info[0].grab.equippableIndicatorOffset.y).toBe(0); + expect(info[0].grab.equippableIndicatorOffset.z).toBe(0); expect(info[0].density).toBe(1000); expect(info[0].velocity.x).toBe(0); expect(info[0].velocity.y).toBe(0); @@ -546,7 +539,7 @@ describe("EntityData - unit tests", () => { expect(info[0].acceleration.y).toBeCloseTo(0, 2); expect(info[0].acceleration.z).toBeCloseTo(0, 2); expect(info[0].damping).toBe(0); - expect(info[0].angularDampling).toBe(0); + expect(info[0].angularDamping).toBe(0); expect(info[0].restitution).toBeCloseTo(0.5, 3); expect(info[0].friction).toBeCloseTo(0.5, 3); expect(info[0].lifetime).toBeCloseTo(-1, 2); @@ -559,7 +552,7 @@ describe("EntityData - unit tests", () => { expect(info[0].cloneLifetime).toBeCloseTo(300, 2); expect(info[0].cloneLimit).toBeCloseTo(0, 2); expect(info[0].cloneDynamic).toBe(false); - expect(info[0].cloneAvatarIdentity).toBe(false); + expect(info[0].cloneAvatarEntity).toBe(false); expect(info[0].cloneOriginID).toBeUndefined(); expect(info[0].script).toBeUndefined(); expect(info[0].scriptTimestamp).toBe(0n); @@ -596,15 +589,15 @@ describe("EntityData - unit tests", () => { expect(info[0].groupCulled).toBe(false); expect(info[0].blendShapeCoefficients).toBe("{\n}\n"); expect(info[0].useOriginalPivot).toBe(true); - expect(info[0].animation.animationURL).toBe(""); - expect(info[0].animation.animationAllowTranslation).toBe(false); - expect(info[0].animation.animationFPS).toBeCloseTo(30, 2); - expect(info[0].animation.animationFrameIndex).toBeCloseTo(0, 2); - expect(info[0].animation.animationPlaying).toBe(false); - expect(info[0].animation.animationLoop).toBe(true); - expect(info[0].animation.animationFirstFrame).toBeCloseTo(0, 2); - expect(info[0].animation.animationLastFrame).toBeCloseTo(100000, 2); - expect(info[0].animation.animationHold).toBe(false); + expect(info[0].animation.url).toBe(""); + expect(info[0].animation.allowTranslation).toBe(false); + expect(info[0].animation.fps).toBeCloseTo(30, 2); + expect(info[0].animation.currentFrame).toBeCloseTo(0, 2); + expect(info[0].animation.running).toBe(false); + expect(info[0].animation.loop).toBe(true); + expect(info[0].animation.firstFrame).toBeCloseTo(0, 2); + expect(info[0].animation.lastFrame).toBeCloseTo(100000, 2); + expect(info[0].animation.hold).toBe(false); // Shape Entity. expect(info[1].entityItemID instanceof Uuid).toBe(true); @@ -641,6 +634,7 @@ describe("EntityData - unit tests", () => { expect(info[1].created).toBe(1657627191786141n); expect(info[1].lastEditedBy instanceof Uuid).toBe(true); expect(info[1].lastEditedBy.stringify()).toBe("1026cbdd-6414-4861-bf88-99a21bed3fd2"); + expect(info[1].entityHostType).toBe(HostType.DOMAIN); expect(info[1].queryAACube.corner.x).toBeCloseTo(-1.18520, 4); expect(info[1].queryAACube.corner.y).toBeCloseTo(-1.83080, 4); expect(info[1].queryAACube.corner.z).toBeCloseTo(0.49136, 4); @@ -651,33 +645,33 @@ describe("EntityData - unit tests", () => { expect(info[1].ignorePickIntersection).toBe(false); expect(info[1].renderWithZones).toBeUndefined(); expect(info[1].billboardMode).toBe(0); - expect(info[1].grabbable).toBe(false); - expect(info[1].grabKinematic).toBe(true); - expect(info[1].grabFollowsController).toBe(true); - expect(info[1].triggerable).toBe(false); - expect(info[1].grabEquippable).toBe(false); - expect(info[1].delegateToParent).toBe(true); - expect(info[1].equippableLeftPositionOffset.x).toBeCloseTo(0, 2); - expect(info[1].equippableLeftPositionOffset.y).toBeCloseTo(0, 2); - expect(info[1].equippableLeftPositionOffset.z).toBeCloseTo(0, 2); - expect(info[1].equippableLeftRotationOffset.x).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableLeftRotationOffset.y).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableLeftRotationOffset.z).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableLeftRotationOffset.w).toBeCloseTo(1, 2); - expect(info[1].equippableRightPositionOffset.x).toBeCloseTo(0, 2); - expect(info[1].equippableRightPositionOffset.y).toBeCloseTo(0, 2); - expect(info[1].equippableRightPositionOffset.z).toBeCloseTo(0, 2); - expect(info[1].equippableRightRotationOffset.x).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableRightRotationOffset.y).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableRightRotationOffset.z).toBeCloseTo(-0.0000152588, 8); - expect(info[1].equippableRightRotationOffset.w).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorURL).toBeUndefined(); - expect(info[1].equippableIndicatorScale.x).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorScale.y).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorScale.z).toBeCloseTo(1, 2); - expect(info[1].equippableIndicatorOffset.x).toBeCloseTo(0, 2); - expect(info[1].equippableIndicatorOffset.y).toBeCloseTo(0, 2); - expect(info[1].equippableIndicatorOffset.z).toBeCloseTo(0, 2); + expect(info[1].grab.grabbable).toBe(false); + expect(info[1].grab.grabKinematic).toBe(true); + expect(info[1].grab.grabFollowsController).toBe(true); + expect(info[1].grab.triggerable).toBe(false); + expect(info[1].grab.equippable).toBe(false); + expect(info[1].grab.grabDelegateToParent).toBe(true); + expect(info[1].grab.equippableLeftPositionOffset.x).toBeCloseTo(0, 2); + expect(info[1].grab.equippableLeftPositionOffset.y).toBeCloseTo(0, 2); + expect(info[1].grab.equippableLeftPositionOffset.z).toBeCloseTo(0, 2); + expect(info[1].grab.equippableLeftRotationOffset.x).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableLeftRotationOffset.y).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableLeftRotationOffset.z).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableLeftRotationOffset.w).toBeCloseTo(1, 2); + expect(info[1].grab.equippableRightPositionOffset.x).toBeCloseTo(0, 2); + expect(info[1].grab.equippableRightPositionOffset.y).toBeCloseTo(0, 2); + expect(info[1].grab.equippableRightPositionOffset.z).toBeCloseTo(0, 2); + expect(info[1].grab.equippableRightRotationOffset.x).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableRightRotationOffset.y).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableRightRotationOffset.z).toBeCloseTo(-0.0000152588, 8); + expect(info[1].grab.equippableRightRotationOffset.w).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorURL).toBeUndefined(); + expect(info[1].grab.equippableIndicatorScale.x).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorScale.y).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorScale.z).toBeCloseTo(1, 2); + expect(info[1].grab.equippableIndicatorOffset.x).toBeCloseTo(0, 2); + expect(info[1].grab.equippableIndicatorOffset.y).toBeCloseTo(0, 2); + expect(info[1].grab.equippableIndicatorOffset.z).toBeCloseTo(0, 2); expect(info[1].density).toBeCloseTo(1000, 2); expect(info[1].velocity.x).toBeCloseTo(0, 2); expect(info[1].velocity.y).toBeCloseTo(0, 2); @@ -692,7 +686,7 @@ describe("EntityData - unit tests", () => { expect(info[1].acceleration.y).toBeCloseTo(0, 2); expect(info[1].acceleration.z).toBeCloseTo(0, 2); expect(info[1].damping).toBeCloseTo(0, 2); - expect(info[1].angularDampling).toBeCloseTo(0, 2); + expect(info[1].angularDamping).toBeCloseTo(0, 2); expect(info[1].restitution).toBeCloseTo(0.5, 3); expect(info[1].friction).toBeCloseTo(0.5, 3); expect(info[1].lifetime).toBeCloseTo(-1, 2); @@ -705,7 +699,7 @@ describe("EntityData - unit tests", () => { expect(info[1].cloneLifetime).toBeCloseTo(300, 2); expect(info[1].cloneLimit).toBeCloseTo(0, 2); expect(info[1].cloneDynamic).toBe(false); - expect(info[1].cloneAvatarIdentity).toBe(false); + expect(info[1].cloneAvatarEntity).toBe(false); expect(info[1].cloneOriginID).toBeUndefined(); expect(info[1].script).toBeUndefined(); expect(info[1].scriptTimestamp).toBe(0n); diff --git a/tests/domain/networking/packets/EntityEdit.unit.test.js b/tests/domain/networking/packets/EntityEdit.unit.test.js new file mode 100644 index 00000000..2b276f32 --- /dev/null +++ b/tests/domain/networking/packets/EntityEdit.unit.test.js @@ -0,0 +1,155 @@ +// +// EntityEdit.unit.test.js +// +// Created by David Rowe on 23 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityItemProperties from "../../../../src/domain/entities/EntityItemProperties"; +import EntityPropertyFlags from "../../../../src/domain/entities/EntityPropertyFlags"; +import { EntityType } from "../../../../src/domain/entities/EntityTypes"; +import EntityEdit from "../../../../src/domain/networking/packets/EntityEdit"; +import PacketType from "../../../../src/domain/networking/udt/PacketHeaders"; +import UDT from "../../../../src/domain/networking/udt/UDT"; +import NLPacket from "../../../../src/domain/networking/NLPacket"; +import Uuid from "../../../../src/domain/shared/Uuid"; + +import { buffer2hex } from "../../../testUtils"; + + +describe("EntityEdit - unit tests", () => { + + test("Can write an EntityEdit packet that changes top-level properties", () => { + /* eslint-disable @typescript-eslint/no-magic-numbers, max-len */ + + // From C++ with 2-byte octcode: + /* + const EXPECTED_PACKET = "000000002d8500000000000000000000000000000000000000006089f1f20a0006000100b685f1f20a000600b71d53802fcc483393a79a49670175874000fff000020000000000000000401000a82f40b6ee8946ccb50402b88d72a546f02594"; + // With 2-byte octcode replaced with 1-byte 0x00 empty octcode: ^^^^ + // With low-order digits of timestamp zeroed out: ^^^^^^ + // Reliable, not unreliable: ^ + */ + const EXPECTED_PACKET = "000000402d850000000000000000000000000000000000000000003c98f20a00060000b685f1f20a000600b71d53802fcc483393a79a49670175874000fff000020000000000000000401000a82f40b6ee8946ccb50402b88d72a546f02594"; + + /* eslint-enable max-len */ + + const mockDateNow = jest.spyOn(Date, "now").mockImplementation(() => { + return 168889688; + }); + + const properties = { + lastEdited: 1688896885851574n, // Date.now() as count of 100ns ticks since the Unix epoch + 44 clock skew. + lastEditedBy: new Uuid("a82f40b6-ee89-46cc-b504-02b88d72a546"), + entityType: EntityType.Box, + color: { red: 240, green: 37, blue: 148 } + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityEditDetails = { + entityID: new Uuid("b71d5380-2fcc-4833-93a7-9a4967017587"), + properties, + requestedProperties, + didntFitProperties + }; + const packet = EntityEdit.write(entityEditDetails); + + expect(packet instanceof NLPacket).toBe(true); + expect(packet.getType()).toBe(PacketType.EntityEdit); + expect(packet.isReliable()).toBe(true); + const packetSize = packet.getDataSize(); + expect(packetSize).toBe(packet.getMessageData().dataPosition); + expect(packetSize).toBeGreaterThan(0); + expect(packetSize).toBeLessThan(UDT.MAX_PACKET_SIZE); + expect(buffer2hex(packet.getMessageData().buffer.slice(0, packetSize))).toBe(EXPECTED_PACKET); + expect(packetSize).toBe(EXPECTED_PACKET.length / 2); + + expect(entityEditDetails.didntFitProperties.length()).toBe(0); + + mockDateNow.mockReset(); // eslint-disable-line @typescript-eslint/no-unsafe-call + /* eslint-enable @typescript-eslint/no-magic-numbers */ + }); + + test("Can write an EntityEdit packet that changes group properties", () => { + /* eslint-disable @typescript-eslint/no-magic-numbers, max-len */ + + // From C++ with 2-byte octcode: + /* + const EXPECTED_PACKET = "000000002d850000000000000000000000000000000000000000f0ac09bbd402060001008ea409bbd4020600b71d53802fcc483393a79a49670175876000f00002004010008f9e0f5a58c544708eef15d86838191c01"; + // With 2-byte octcode replaced with 1-byte 0x00 empty octcode: ^^^^ + // With low-order digits of timestamp zeroed out: ^^^^^^^^ + // Reliable, not unreliable: ^ + */ + const EXPECTED_PACKET = "000000402d8500000000000000000000000000000000000000008019c4bad4020600008ea409bbd4020600b71d53802fcc483393a79a49670175876000f00002004010008f9e0f5a58c544708eef15d86838191c01"; + + /* eslint-enable max-len */ + + const mockDateNow = jest.spyOn(Date, "now").mockImplementation(() => { + return 169196255; + }); + + const properties = { + lastEdited: 1691962554557582n, // Date.now() as count of 100ns ticks since the Unix epoch + 44 clock skew. + lastEditedBy: new Uuid("8f9e0f5a-58c5-4470-8eef-15d86838191c"), + entityType: EntityType.Shape, + grab: { + grabbable: true + } + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityEditDetails = { + entityID: new Uuid("b71d5380-2fcc-4833-93a7-9a4967017587"), + properties, + requestedProperties, + didntFitProperties + }; + const packet = EntityEdit.write(entityEditDetails); + + expect(packet instanceof NLPacket).toBe(true); + expect(packet.getType()).toBe(PacketType.EntityEdit); + expect(packet.isReliable()).toBe(true); + const packetSize = packet.getDataSize(); + expect(packetSize).toBe(packet.getMessageData().dataPosition); + expect(packetSize).toBeGreaterThan(0); + expect(packetSize).toBeLessThan(UDT.MAX_PACKET_SIZE); + expect(buffer2hex(packet.getMessageData().buffer.slice(0, packetSize))).toBe(EXPECTED_PACKET); + expect(packetSize).toBe(EXPECTED_PACKET.length / 2); + + expect(entityEditDetails.didntFitProperties.length()).toBe(0); + + mockDateNow.mockReset(); // eslint-disable-line @typescript-eslint/no-unsafe-call + /* eslint-enable @typescript-eslint/no-magic-numbers, max-len */ + }); + + test("Writing a property that won't fit in a packet returns null", () => { + /* eslint-disable @typescript-eslint/no-magic-numbers */ + + const mockDateNow = jest.spyOn(Date, "now").mockImplementation(() => { + return 169196255; + }); + + const properties = { + lastEdited: 1691962554557582n, + entityType: EntityType.Shape, + userData: "0123456789".repeat(200) // eslint-disable-line @typescript-eslint/no-magic-numbers + }; + const requestedProperties = EntityItemProperties.getChangedProperties(properties); + const didntFitProperties = new EntityPropertyFlags(); + const entityEditDetails = { + entityID: new Uuid("b71d5380-2fcc-4833-93a7-9a4967017587"), + properties, + requestedProperties, + didntFitProperties + }; + const packet = EntityEdit.write(entityEditDetails); + expect(packet).toBeNull(); + + mockDateNow.mockReset(); // eslint-disable-line @typescript-eslint/no-unsafe-call + /* eslint-enable @typescript-eslint/no-magic-numbers */ + }); + +}); diff --git a/tests/domain/networking/packets/PacketScribe.unit.test.js b/tests/domain/networking/packets/PacketScribe.unit.test.js index dc5be210..d3178842 100644 --- a/tests/domain/networking/packets/PacketScribe.unit.test.js +++ b/tests/domain/networking/packets/PacketScribe.unit.test.js @@ -60,6 +60,8 @@ describe("Packets - unit tests", () => { expect(typeof PacketScribe.EntityData.read).toBe("function"); expect(typeof PacketScribe.EntityQuery).toBe("object"); expect(typeof PacketScribe.EntityQuery.write).toBe("function"); + expect(typeof PacketScribe.EntityEdit).toBe("object"); + expect(typeof PacketScribe.EntityEdit.write).toBe("function"); expect(typeof PacketScribe.DomainServerConnectionToken).toBe("object"); expect(typeof PacketScribe.DomainServerConnectionToken.read).toBe("function"); expect(typeof PacketScribe.DomainDisconnectRequest).toBe("object"); diff --git a/tests/domain/octree/OctreeEditPacketSender.unit.test.js b/tests/domain/octree/OctreeEditPacketSender.unit.test.js new file mode 100644 index 00000000..ca2ad043 --- /dev/null +++ b/tests/domain/octree/OctreeEditPacketSender.unit.test.js @@ -0,0 +1,106 @@ +// +// OctreeEditPacketSender.unit.test.js +// +// Created by David Rowe on 20 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { webcrypto } from "crypto"; +globalThis.crypto = webcrypto; + +import AccountManager from "../../../src/domain/networking/AccountManager"; +import AddressManager from "../../../src/domain/networking/AddressManager"; +import ContextManager from "../../../src/domain/shared/ContextManager"; +import PacketType from "../../../src/domain/networking/udt/PacketHeaders"; +import NLPacket from "../../../src/domain/networking/NLPacket"; +import NodeList from "../../../src/domain/networking/NodeList"; +import SockAddr from "../../../src/domain/networking/SockAddr"; +import OctreeEditPacketSender from "../../../src/domain/octree/OctreeEditPacketSender"; + + +describe("OctreeEditPacketSender - unit tests", () => { + + test("An OctreeEditPacketSender can be obtained from the ContextManager", () => { + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, OctreeEditPacketSender, contextID); + const octreeEditPacketSender = ContextManager.get(contextID, OctreeEditPacketSender); + expect(octreeEditPacketSender instanceof OctreeEditPacketSender).toBe(true); + }); + + test("A warning is logged when try to send a packet without an entity server connection", () => { + let lastWarning = ""; + const warn = jest.spyOn(console, "warn").mockImplementation((message) => { + lastWarning = message; // eslint-disable-line @typescript-eslint/no-unsafe-assignment + }); + + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, OctreeEditPacketSender, contextID); + const octreeEditPacketSender = ContextManager.get(contextID, OctreeEditPacketSender); + + const nlPacket = new NLPacket(PacketType.EntityEdit); + octreeEditPacketSender.queueOctreeEditMessage(nlPacket); // eslint-disable-line @typescript-eslint/no-unsafe-call + expect(lastWarning).toBe("[EntityServer] Could not send edit message because not connected."); + + warn.mockRestore(); + }); + + test("Asserts if try to send a non-entity packet", () => { + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, OctreeEditPacketSender, contextID); + const octreeEditPacketSender = ContextManager.get(contextID, OctreeEditPacketSender); + + let lastAssert = ""; + try { + const nlPacket = new NLPacket(PacketType.DomainListRequest); + octreeEditPacketSender.queueOctreeEditMessage(nlPacket); // eslint-disable-line @typescript-eslint/no-unsafe-call + } catch (e) { + // eslint-disable-next-line + lastAssert = e.message; + } + expect(lastAssert).toBe("Assertion failed! queueOctreeEditMessage() unexpected packet type: 13"); + }); + + test("Can send an NLPacket to the entity server", (done) => { + const contextID = ContextManager.createContext(); + ContextManager.set(contextID, AccountManager, contextID); + ContextManager.set(contextID, AddressManager); + ContextManager.set(contextID, NodeList, contextID); + ContextManager.set(contextID, OctreeEditPacketSender, contextID); + const octreeEditPacketSender = ContextManager.get(contextID, OctreeEditPacketSender); + + const mockSoloNodeOfType = jest.spyOn(ContextManager.get(contextID, NodeList), "soloNodeOfType") + .mockImplementation(() => { + return { + getActiveSocket: () => { + return new SockAddr(); + } + }; // Entity server node. + }); + const mockSendPacket = jest.spyOn(ContextManager.get(contextID, NodeList), "sendPacket") + .mockImplementation((editMessage) => { + expect(editMessage.getType()).toBe(PacketType.EntityEdit); // eslint-disable-line + mockSendPacket.mockRestore(); + mockSoloNodeOfType.mockRestore(); + done(); + }); + + const nlPacket = new NLPacket(PacketType.EntityEdit); + octreeEditPacketSender.queueOctreeEditMessage(nlPacket); // eslint-disable-line @typescript-eslint/no-unsafe-call + }); + + // WEBRTC TODO: Test an NLPacketList can be sent to the entity server using an EntityAdd message. + +}); diff --git a/tests/domain/octree/OctreeElement.unit.test.js b/tests/domain/octree/OctreeElement.unit.test.js new file mode 100644 index 00000000..89da7f9f --- /dev/null +++ b/tests/domain/octree/OctreeElement.unit.test.js @@ -0,0 +1,22 @@ +// +// OctreeElement.unit.test.js +// +// Created by David Rowe on 29 Jun 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import { AppendState } from "../../../src/domain/octree/OctreeElement"; + + +describe("OctreeElement - unit tests", () => { + + test("AppendState values can be accessed", () => { + expect(AppendState.COMPLETED).toBe(0); + expect(AppendState.PARTIAL).toBe(1); + expect(AppendState.NONE).toBe(2); + }); +}); diff --git a/tests/domain/octree/OctreePacketData.unit.test.js b/tests/domain/octree/OctreePacketData.unit.test.js new file mode 100644 index 00000000..4234ff2a --- /dev/null +++ b/tests/domain/octree/OctreePacketData.unit.test.js @@ -0,0 +1,1341 @@ +// +// OctreePacketData.unit.test.js +// +// Created by David Rowe on 17 Jul 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import EntityPropertyFlags, { EntityPropertyList } from "../../../src/domain/entities/EntityPropertyFlags"; +import UDT from "../../../src/domain/networking/udt/UDT"; +import { AppendState } from "../../../src/domain/octree/OctreeElement"; +import OctreePacketData from "../../../src/domain/octree/OctreePacketData"; +import Uuid from "../../../src/domain/shared/Uuid"; + +import "../../../src/domain/shared/DataViewExtensions"; +import { buffer2hex } from "../../testUtils"; + + +describe("OctreePacketData - unit tests", () => { + /* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-member-access, + @typescript-eslint/no-unsafe-call + */ + + let value = null; + let errorMessage = null; + let error = null; + let data = null; + let bytesWritten = null; + let context = null; + + const MAX_FLOAT32 = 3.4028235e38; + + function setUp(bufferLength) { + errorMessage = ""; + error = jest.spyOn(console, "error").mockImplementation((...message) => { + errorMessage = message.join(" "); + }); + + data = new DataView(new ArrayBuffer(bufferLength)); + context = { + propertiesToWrite: new EntityPropertyFlags(), + propertiesWritten: new EntityPropertyFlags(), + propertyCount: 0, + appendState: AppendState.COMPLETED + }; + } + + function tearDown() { + error.mockRestore(); + } + + test("Error if try to write an invalid AACube value", () => { + setUp(20); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendAACubeValue(data, 2, EntityPropertyList.PROP_QUERY_AA_CUBE, value, context); + expect(bytesWritten).toBe(0); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid AACube value to packet!"); + + value = { corner: { x: -4, y: -10.5, z: 1 }, scale: "" }; + bytesWritten = OctreePacketData.appendAACubeValue(data, 2, EntityPropertyList.PROP_QUERY_AA_CUBE, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid AACube value to packet!"); + + value = { corner: { x: -4, y: -10.5, z: -1.1 * MAX_FLOAT32 }, scale: 1 }; + bytesWritten = OctreePacketData.appendAACubeValue(data, 2, EntityPropertyList.PROP_QUERY_AA_CUBE, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid AACube value to packet!"); + + value = { corner: { x: -4, y: -10.5, z: 1 }, scale: 1.1 * MAX_FLOAT32 }; + bytesWritten = OctreePacketData.appendAACubeValue(data, 2, EntityPropertyList.PROP_QUERY_AA_CUBE, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid AACube value to packet!"); + + tearDown(); + }); + + test("Can write an AACube value", () => { + // Successful write. + setUp(20); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE, true); + value = { corner: { x: -10.0, y: 6.4, z: 20.6 }, scale: 1.2 }; + bytesWritten = OctreePacketData.appendAACubeValue(data, 2, EntityPropertyList.PROP_QUERY_AA_CUBE, value, context); + expect(bytesWritten).toBe(16); + expect(buffer2hex(data.buffer)).toEqual("0000000020c1cdcccc40cdcca4419a99993f0000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(20); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE, true); + value = { corner: { x: -10.0, y: 6.4, z: 20.6 }, scale: 1.2 }; + bytesWritten = OctreePacketData.appendAACubeValue(data, 8, EntityPropertyList.PROP_QUERY_AA_CUBE, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("0000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_QUERY_AA_CUBE)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + + test("Error if try to write an invalid ArrayBuffer value", () => { + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ACTION_DATA, true); + + value = 123; + bytesWritten = OctreePacketData.appendArrayBufferValue(data, 2, EntityPropertyList.PROP_ACTION_DATA, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid ArrayBuffer value to packet!"); + + tearDown(); + }); + + test("Can write am ArrayBuffer value", () => { + // Successful write of non-empty ArrayBuff4er. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ACTION_DATA, true); + let array = new Uint8Array([128, 0, 1, 255]); + value = array.buffer; + bytesWritten = OctreePacketData.appendArrayBufferValue(data, 2, EntityPropertyList.PROP_ACTION_DATA, value, context); + expect(bytesWritten).toBe(6); + expect(buffer2hex(data.buffer)).toEqual("00000400800001ff0000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of empty ArrayBuffer. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ACTION_DATA, true); + array = new Uint8Array(0); + value = array.buffer; + bytesWritten = OctreePacketData.appendArrayBufferValue(data, 2, EntityPropertyList.PROP_ACTION_DATA, value, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ACTION_DATA, true); + array = new Uint8Array([128, 0, 0, 0, 1, 255]); + value = array.buffer; + bytesWritten = OctreePacketData.appendArrayBufferValue(data, 5, EntityPropertyList.PROP_ACTION_DATA, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ACTION_DATA)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + + test("Error if try to write an invalid boolean array", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET, true); + + value = "aabbcc"; + errorMessage = ""; + bytesWritten = OctreePacketData.appendBooleanArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS_SET, value, + context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid boolean array to packet!"); + + value = ["aabbcc"]; + errorMessage = ""; + bytesWritten = OctreePacketData.appendBooleanArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS_SET, value, + context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid boolean array to packet!"); + + tearDown(); + }); + + test("Can write a boolean array", () => { + // Successful write of empty array. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET, true); + value = []; + bytesWritten = OctreePacketData.appendBooleanArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS_SET, value, + context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of non-empty array containing < 8 elements. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET, true); + value = [true]; + bytesWritten = OctreePacketData.appendBooleanArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS_SET, value, + context); + expect(bytesWritten).toBe(3); + expect(buffer2hex(data.buffer)).toEqual("00000100010000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of non-empty array containing 8 elements. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET, true); + value = [true, false, false, false, false, false, true, true]; + bytesWritten = OctreePacketData.appendBooleanArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS_SET, value, + context); + expect(bytesWritten).toBe(3); + expect(buffer2hex(data.buffer)).toEqual("00000800c10000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of non-empty array containing > 8 elements. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET, true); + value = [true, false, false, false, false, false, true, true, true, true]; + bytesWritten = OctreePacketData.appendBooleanArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS_SET, value, + context); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("00000a00c10300000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET, true); + value = [true, false, false, false, false, false, true, true, true, true]; + bytesWritten = OctreePacketData.appendBooleanArray(data, 7, EntityPropertyList.PROP_JOINT_ROTATIONS_SET, value, + context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS_SET)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid boolean value", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_VISIBLE, true); + + value = "aabbcc"; + errorMessage = ""; + bytesWritten = OctreePacketData.appendBooleanValue(data, 2, EntityPropertyList.PROP_VISIBLE, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_VISIBLE)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid boolean value to packet!"); + + tearDown(); + }); + + test("Can write a boolean value", () => { + // Successful write of true value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_VISIBLE, true); + value = true; + bytesWritten = OctreePacketData.appendBooleanValue(data, 2, EntityPropertyList.PROP_VISIBLE, value, context); + expect(bytesWritten).toBe(1); + expect(buffer2hex(data.buffer)).toEqual("00000100000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_VISIBLE)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_VISIBLE)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of false value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_VISIBLE, true); + value = false; + bytesWritten = OctreePacketData.appendBooleanValue(data, 2, EntityPropertyList.PROP_VISIBLE, value, context); + expect(bytesWritten).toBe(1); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_VISIBLE)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_VISIBLE)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_VISIBLE, true); + value = true; + bytesWritten = OctreePacketData.appendBooleanValue(data, 10, EntityPropertyList.PROP_VISIBLE, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_VISIBLE)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_VISIBLE)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid color value", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendColorValue(data, 2, EntityPropertyList.PROP_COLOR, value, context); + expect(bytesWritten).toBe(0); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid color value to packet!"); + + value = { red: 100, green: 150, blue: "" }; + bytesWritten = OctreePacketData.appendColorValue(data, 2, EntityPropertyList.PROP_COLOR, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid color value to packet!"); + + tearDown(); + }); + + test("Can write a color value", () => { + // Successful write. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); + value = { red: 100, green: 150, blue: 200 }; + bytesWritten = OctreePacketData.appendColorValue(data, 2, EntityPropertyList.PROP_COLOR, value, context); + expect(bytesWritten).toBe(3); + expect(buffer2hex(data.buffer)).toEqual("00006496c80000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_COLOR, true); + value = { red: 100, green: 150, blue: 200 }; + bytesWritten = OctreePacketData.appendColorValue(data, 8, EntityPropertyList.PROP_COLOR, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_COLOR)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + + test("Error if try to write an invalid float32 value", () => { + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_DENSITY, true); + + value = "aabbcc"; + errorMessage = ""; + bytesWritten = OctreePacketData.appendFloat32Value(data, 2, EntityPropertyList.PROP_DENSITY, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid float32 value to packet!"); + + value = -1.1 * MAX_FLOAT32; + errorMessage = ""; + bytesWritten = OctreePacketData.appendFloat32Value(data, 2, EntityPropertyList.PROP_DENSITY, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid float32 value to packet!"); + + value = 1.1 * MAX_FLOAT32; + errorMessage = ""; + bytesWritten = OctreePacketData.appendFloat32Value(data, 2, EntityPropertyList.PROP_DENSITY, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid float32 value to packet!"); + + tearDown(); + }); + + test("Can write a float32 value", () => { + // Successful write of min value. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_DENSITY, true); + value = -MAX_FLOAT32; + bytesWritten = OctreePacketData.appendFloat32Value(data, 2, EntityPropertyList.PROP_DENSITY, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("0000ffff7fff000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of intermediate value. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_DENSITY, true); + value = 1250; + bytesWritten = OctreePacketData.appendFloat32Value(data, 2, EntityPropertyList.PROP_DENSITY, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("000000409c44000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of max value. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_DENSITY, true); + value = MAX_FLOAT32; + bytesWritten = OctreePacketData.appendFloat32Value(data, 2, EntityPropertyList.PROP_DENSITY, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("0000ffff7f7f000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_DENSITY, true); + value = 1250; + bytesWritten = OctreePacketData.appendFloat32Value(data, 9, EntityPropertyList.PROP_DENSITY, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_DENSITY)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid quat array", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS, true); + + value = "aabbccdd"; + bytesWritten = OctreePacketData.appendQuatArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid quat array to packet!"); + + value = { x: 1, y: 2, z: 3, w: 1 }; + bytesWritten = OctreePacketData.appendQuatArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid quat array to packet!"); + + value = [{ x: 1, y: 2, z: 3, w: "" }]; + bytesWritten = OctreePacketData.appendQuatArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid quat array to packet!"); + + tearDown(); + }); + + test("Can write a quat array", () => { + // Successful write of empty array. + setUp(24); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS, true); + value = []; + bytesWritten = OctreePacketData.appendQuatArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS, value, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of array with one element. + setUp(24); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS, true); + value = [{ x: -0.00913472, y: 0.104486, z: 0.0052514, w: 0.994471 }]; + bytesWritten = OctreePacketData.appendQuatArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS, value, context); + expect(bytesWritten).toBe(10); + expect(buffer2hex(data.buffer)).toEqual("00000100d47e5f8dab8049ff000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of array with two elements. + setUp(24); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS, true); + value = [ + { x: -0.00913472, y: 0.104486, z: 0.0052514, w: 0.994471 }, + { x: -0.00913472, y: 0.104486, z: 0.0052514, w: 0.994471 } + ]; + bytesWritten = OctreePacketData.appendQuatArray(data, 2, EntityPropertyList.PROP_JOINT_ROTATIONS, value, context); + expect(bytesWritten).toBe(18); + expect(buffer2hex(data.buffer)).toEqual("00000200d47e5f8dab8049ffd47e5f8dab8049ff00000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(24); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS, true); + value = [{ x: -0.00913472, y: 0.104486, z: 0.0052514, w: 0.994471 }]; + bytesWritten = OctreePacketData.appendQuatArray(data, 15, EntityPropertyList.PROP_JOINT_ROTATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_ROTATIONS)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid quat value", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ROTATION, true); + + value = { x: 1, y: 2, z: 3 }; + bytesWritten = OctreePacketData.appendQuatValue(data, 2, EntityPropertyList.PROP_ROTATION, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ROTATION)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid quat value to packet!"); + + value = { x: 1, y: 2, z: 3, w: "" }; + bytesWritten = OctreePacketData.appendQuatValue(data, 2, EntityPropertyList.PROP_ROTATION, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ROTATION)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid quat value to packet!"); + + tearDown(); + }); + + test("Can write a quat value", () => { + // Successful write. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ROTATION, true); + value = { x: -0.00913472, y: 0.104486, z: 0.0052514, w: 0.994471 }; + bytesWritten = OctreePacketData.appendQuatValue(data, 2, EntityPropertyList.PROP_ROTATION, value, context); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("0000d47e5f8dab8049ff000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ROTATION)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ROTATION)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_ROTATION, true); + value = { x: -0.00913472, y: 0.104486, z: 0.0052514, w: 0.994471 }; + bytesWritten = OctreePacketData.appendQuatValue(data, 10, EntityPropertyList.PROP_ROTATION, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_ROTATION)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_ROTATION)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + + }); + + test("Error if try to write an invalid rect value", () => { + setUp(20); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_SUB_IMAGE, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendRectValue(data, 2, EntityPropertyList.PROP_SUB_IMAGE, value, context); + expect(bytesWritten).toBe(0); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid rect value to packet!"); + + value = { x: 4, y: 10, width: 120, height: "" }; + bytesWritten = OctreePacketData.appendRectValue(data, 2, EntityPropertyList.PROP_SUB_IMAGE, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_SUB_IMAGE)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid rect value to packet!"); + + tearDown(); + }); + + test("Can write a rect value", () => { + // Successful write. + setUp(20); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_SUB_IMAGE, true); + value = { x: 4, y: 10, width: 120, height: 100 }; + bytesWritten = OctreePacketData.appendRectValue(data, 2, EntityPropertyList.PROP_SUB_IMAGE, value, context); + expect(bytesWritten).toBe(16); + expect(buffer2hex(data.buffer)).toEqual("0000040000000a00000078000000640000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_SUB_IMAGE)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_SUB_IMAGE)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(20); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_SUB_IMAGE, true); + value = { x: 4, y: 10, width: 120, height: 100 }; + bytesWritten = OctreePacketData.appendRectValue(data, 8, EntityPropertyList.PROP_SUB_IMAGE, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("0000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_SUB_IMAGE)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_SUB_IMAGE)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + + test("Error if try to write an invalid string value", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_NAME, true); + + value = 123; + bytesWritten = OctreePacketData.appendStringValue(data, 2, EntityPropertyList.PROP_NAME, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_NAME)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid string value to packet!"); + + tearDown(); + }); + + test("Can write a string value", () => { + // Successful write of non-empty string. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_NAME, true); + value = "abcd"; + bytesWritten = OctreePacketData.appendStringValue(data, 2, EntityPropertyList.PROP_NAME, value, context); + expect(bytesWritten).toBe(6); + expect(buffer2hex(data.buffer)).toEqual("00000400616263640000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_NAME)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_NAME)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of empty string. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_NAME, true); + value = ""; + bytesWritten = OctreePacketData.appendStringValue(data, 2, EntityPropertyList.PROP_NAME, value, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_NAME)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_NAME)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_NAME, true); + value = "abcd"; + bytesWritten = OctreePacketData.appendStringValue(data, 5, EntityPropertyList.PROP_NAME, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_NAME)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_NAME)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + + test("Error if try to write an invalid Uint8 value", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + + value = "aabbcc"; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint8Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint8 value to packet!"); + + value = -1; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint8Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint8 value to packet!"); + + value = 0x10000; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint8Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint8 value to packet!"); + + tearDown(); + }); + + test("Can write a uint8 value", () => { + // Successful write of min value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0; + bytesWritten = OctreePacketData.appendUint8Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, context); + expect(bytesWritten).toBe(1); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of intermediate value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0x12; + bytesWritten = OctreePacketData.appendUint8Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, context); + expect(bytesWritten).toBe(1); + expect(buffer2hex(data.buffer)).toEqual("00001200000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of max value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0xff; + bytesWritten = OctreePacketData.appendUint8Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, context); + expect(bytesWritten).toBe(1); + expect(buffer2hex(data.buffer)).toEqual("0000ff00000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0; + bytesWritten = OctreePacketData.appendUint8Value(data, 10, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid Uint16 value", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + + value = "aabbcc"; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint16Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint16 value to packet!"); + + value = -1; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint16Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint16 value to packet!"); + + value = 0x10000; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint16Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint16 value to packet!"); + + tearDown(); + }); + + test("Can write a uint16 value", () => { + // Successful write of min value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0; + bytesWritten = OctreePacketData.appendUint16Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of intermediate value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0x1020; + bytesWritten = OctreePacketData.appendUint16Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("00002010000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of max value. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0xffff; + bytesWritten = OctreePacketData.appendUint16Value(data, 2, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("0000ffff000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX, true); + value = 0; + bytesWritten = OctreePacketData.appendUint16Value(data, 9, EntityPropertyList.PROP_PARENT_JOINT_INDEX, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_PARENT_JOINT_INDEX)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid Uint32 value", () => { + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_LAYER, true); + + value = "aabbcc"; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint32Value(data, 2, EntityPropertyList.PROP_RENDER_LAYER, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint32 value to packet!"); + + value = -1; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint32Value(data, 2, EntityPropertyList.PROP_RENDER_LAYER, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint32 value to packet!"); + + value = 0x100000000; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint32Value(data, 2, EntityPropertyList.PROP_RENDER_LAYER, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint32 value to packet!"); + + tearDown(); + }); + + test("Can write a uint32 value", () => { + // Successful write of min value. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_LAYER, true); + value = 0; + bytesWritten = OctreePacketData.appendUint32Value(data, 2, EntityPropertyList.PROP_RENDER_LAYER, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of intermediate value. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_LAYER, true); + value = 0x71020; + bytesWritten = OctreePacketData.appendUint32Value(data, 2, EntityPropertyList.PROP_RENDER_LAYER, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("000020100700000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of max value. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_LAYER, true); + value = 0xffffffff; + bytesWritten = OctreePacketData.appendUint32Value(data, 2, EntityPropertyList.PROP_RENDER_LAYER, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(4); + expect(buffer2hex(data.buffer)).toEqual("0000ffffffff000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_LAYER, true); + value = 1; + bytesWritten = OctreePacketData.appendUint32Value(data, 9, EntityPropertyList.PROP_RENDER_LAYER, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_RENDER_LAYER)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid Uint64 value", () => { + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_CREATED, true); + + value = "aabbcc"; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint64Value(data, 2, EntityPropertyList.PROP_CREATED, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint64 value to packet!"); + + value = -1n; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint64Value(data, 2, EntityPropertyList.PROP_CREATED, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint64 value to packet!"); + + value = 0x10000000000000000n; + errorMessage = ""; + bytesWritten = OctreePacketData.appendUint64Value(data, 2, EntityPropertyList.PROP_CREATED, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid uint64 value to packet!"); + + tearDown(); + }); + + test("Can write a uint64 value", () => { + // Successful write of min value. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_CREATED, true); + value = 0n; + bytesWritten = OctreePacketData.appendUint64Value(data, 2, EntityPropertyList.PROP_CREATED, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of intermediate value. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_CREATED, true); + value = 0x700001020n; + bytesWritten = OctreePacketData.appendUint64Value(data, 2, EntityPropertyList.PROP_CREATED, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("00002010000007000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of max value. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_CREATED, true); + value = 0xffffffffffffffffn; + bytesWritten = OctreePacketData.appendUint64Value(data, 2, EntityPropertyList.PROP_CREATED, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("0000ffffffffffffffff000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_CREATED, true); + value = 0n; + bytesWritten = OctreePacketData.appendUint64Value(data, 9, EntityPropertyList.PROP_CREATED, value, + UDT.LITTLE_ENDIAN, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_CREATED)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid UUID array", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendUuidArray(data, 2, EntityPropertyList.PROP_RENDER_WITH_ZONES, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid UUID array to packet!"); + + value = ["aabbcc"]; + bytesWritten = OctreePacketData.appendUuidArray(data, 2, EntityPropertyList.PROP_RENDER_WITH_ZONES, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid UUID array to packet!"); + + value = [new Uuid(), "aabbcc"]; + bytesWritten = OctreePacketData.appendUuidArray(data, 2, EntityPropertyList.PROP_RENDER_WITH_ZONES, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid UUID array to packet!"); + + tearDown(); + }); + + test("Can write a UUID array", () => { + // Successful write of single null value. + setUp(40); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES, true); + value = [new Uuid()]; + bytesWritten = OctreePacketData.appendUuidArray(data, 2, EntityPropertyList.PROP_RENDER_WITH_ZONES, value, context); + expect(bytesWritten).toBe(18); + expect(buffer2hex(data.buffer)) + .toEqual("00000100000000000000000000000000000000000000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of multiple non-null values. + setUp(40); + value = [new Uuid("a3eda01ec4de456dbf07858a26c5a648"), new Uuid("b4feb12fd5ef567ec018969b37d6b759")]; + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES, true); + bytesWritten = OctreePacketData.appendUuidArray(data, 2, EntityPropertyList.PROP_RENDER_WITH_ZONES, value, context); + expect(bytesWritten).toBe(34); + expect(buffer2hex(data.buffer)) + .toEqual("00000200a3eda01ec4de456dbf07858a26c5a648b4feb12fd5ef567ec018969b37d6b75900000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(40); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES, true); + value = [new Uuid("a3eda01ec4de456dbf07858a26c5a648"), new Uuid("b4feb12fd5ef567ec018969b37d6b759")]; + bytesWritten = OctreePacketData.appendUuidArray(data, 8, EntityPropertyList.PROP_RENDER_WITH_ZONES, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)) + .toEqual("00000000000000000000000000000000000000000000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_RENDER_WITH_ZONES)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid UUID value", () => { + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendUuidValue(data, 2, EntityPropertyList.PROP_LAST_EDITED_BY, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid UUID value to packet!"); + + tearDown(); + }); + + test("Can write a UUID value", () => { + // Successful write of null value. + setUp(24); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY, true); + value = new Uuid(); + bytesWritten = OctreePacketData.appendUuidValue(data, 2, EntityPropertyList.PROP_LAST_EDITED_BY, value, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of non-null value. + setUp(24); + value = new Uuid("a3eda01ec4de456dbf07858a26c5a648"); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY, true); + bytesWritten = OctreePacketData.appendUuidValue(data, 2, EntityPropertyList.PROP_LAST_EDITED_BY, value, context); + expect(bytesWritten).toBe(18); + expect(buffer2hex(data.buffer)).toEqual("00001000a3eda01ec4de456dbf07858a26c5a64800000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(10); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY, true); + value = new Uuid("a3eda01ec4de456dbf07858a26c5a648"); + bytesWritten = OctreePacketData.appendUuidValue(data, 2, EntityPropertyList.PROP_LAST_EDITED_BY, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_LAST_EDITED_BY)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + + tearDown(); + }); + + test("Error if try to write an invalid vec2 value", () => { + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendVec2Value(data, 2, EntityPropertyList.PROP_MATERIAL_MAPPING_POS, value, context); + expect(bytesWritten).toBe(0); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec2 value to packet!"); + + value = { x: -4, y: "" }; + bytesWritten = OctreePacketData.appendVec2Value(data, 2, EntityPropertyList.PROP_MATERIAL_MAPPING_POS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec2 value to packet!"); + + value = { x: -1.1 * MAX_FLOAT32, y: 1 }; + bytesWritten = OctreePacketData.appendVec2Value(data, 2, EntityPropertyList.PROP_MATERIAL_MAPPING_POS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec2 value to packet!"); + + value = { x: -4, y: 1.1 * MAX_FLOAT32 }; + bytesWritten = OctreePacketData.appendVec2Value(data, 2, EntityPropertyList.PROP_MATERIAL_MAPPING_POS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec2 value to packet!"); + + tearDown(); + }); + + test("Can write a vec2 value", () => { + // Successful write. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS, true); + value = { x: -10.5, y: 1.2 }; + bytesWritten = OctreePacketData.appendVec2Value(data, 2, EntityPropertyList.PROP_MATERIAL_MAPPING_POS, value, context); + expect(bytesWritten).toBe(8); + expect(buffer2hex(data.buffer)).toEqual("0000000028c19a99993f0000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(12); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS, true); + value = { x: -10.5, y: 1.2 }; + bytesWritten = OctreePacketData.appendVec2Value(data, 8, EntityPropertyList.PROP_MATERIAL_MAPPING_POS, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_MATERIAL_MAPPING_POS)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + + test("Error if try to write an invalid vec3 array", () => { + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 array to packet!"); + + value = { x: -4, y: -10.5, z: 1 }; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 array to packet!"); + + value = [{ x: -4, y: -10.5, z: "" }]; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 array to packet!"); + + value = [{ x: -1.1 * MAX_FLOAT32, y: -10.5, z: 1 }]; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 array to packet!"); + + value = [{ x: -4, y: -10.5, z: 1.1 * MAX_FLOAT32 }]; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 array to packet!"); + + tearDown(); + }); + + test("Can write a vec3 array", () => { + // Successful write of empty array. + setUp(30); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS, true); + value = []; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(2); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of array with one element. + setUp(30); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS, true); + value = [{ x: -4, y: -10.5, z: 1.2 }]; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(14); + expect(buffer2hex(data.buffer)).toEqual("00000100000080c0000028c19a99993f0000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Successful write of array with two elements. + setUp(30); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS, true); + value = [ + { x: -4, y: -10.5, z: 1.2 }, + { x: -4, y: -10.5, z: 1.2 } + ]; + bytesWritten = OctreePacketData.appendVec3Array(data, 2, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(26); + expect(buffer2hex(data.buffer)).toEqual("00000200000080c0000028c19a99993f000080c0000028c19a99993f0000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(30); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS, true); + value = [ + { x: -4, y: -10.5, z: 1.2 }, + { x: -4, y: -10.5, z: 1.2 } + ]; + bytesWritten = OctreePacketData.appendVec3Array(data, 5, EntityPropertyList.PROP_JOINT_TRANSLATIONS, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("000000000000000000000000000000000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_JOINT_TRANSLATIONS)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + + test("Error if try to write an invalid vec3 value", () => { + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_POSITION, true); + + value = "aabbcc"; + bytesWritten = OctreePacketData.appendVec3Value(data, 2, EntityPropertyList.PROP_POSITION, value, context); + expect(bytesWritten).toBe(0); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 value to packet!"); + + value = { x: -4, y: -10.5, z: "" }; + bytesWritten = OctreePacketData.appendVec3Value(data, 2, EntityPropertyList.PROP_POSITION, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 value to packet!"); + + value = { x: -1.1 * MAX_FLOAT32, y: -10.5, z: 1 }; + bytesWritten = OctreePacketData.appendVec3Value(data, 2, EntityPropertyList.PROP_POSITION, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 value to packet!"); + + value = { x: -4, y: -10.5, z: 1.1 * MAX_FLOAT32 }; + bytesWritten = OctreePacketData.appendVec3Value(data, 2, EntityPropertyList.PROP_POSITION, value, context); + expect(bytesWritten).toBe(0); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(errorMessage).toBe("[EntityServer] Cannot write invalid vec3 value to packet!"); + + tearDown(); + }); + + test("Can write a vec3 value", () => { + // Successful write. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_POSITION, true); + value = { x: -4, y: -10.5, z: 1.2 }; + bytesWritten = OctreePacketData.appendVec3Value(data, 2, EntityPropertyList.PROP_POSITION, value, context); + expect(bytesWritten).toBe(12); + expect(buffer2hex(data.buffer)).toEqual("0000000080c0000028c19a99993f0000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(context.propertyCount).toBe(1); + expect(context.appendState).toBe(AppendState.COMPLETED); + + // Unsuccessful write if insufficient space. + setUp(16); + context.propertiesToWrite.setHasProperty(EntityPropertyList.PROP_POSITION, true); + value = { x: -4, y: -10.5, z: 1.2 }; + bytesWritten = OctreePacketData.appendVec3Value(data, 8, EntityPropertyList.PROP_POSITION, value, context); + expect(bytesWritten).toBe(0); + expect(buffer2hex(data.buffer)).toEqual("00000000000000000000000000000000"); + expect(context.propertiesToWrite.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(true); + expect(context.propertiesWritten.getHasProperty(EntityPropertyList.PROP_POSITION)).toBe(false); + expect(context.propertyCount).toBe(0); + expect(context.appendState).toBe(AppendState.PARTIAL); + tearDown(); + }); + +}); diff --git a/tests/domain/shared/ByteCountCoded.unit.test.js b/tests/domain/shared/ByteCountCoded.unit.test.js index 9ce1e230..23de9110 100644 --- a/tests/domain/shared/ByteCountCoded.unit.test.js +++ b/tests/domain/shared/ByteCountCoded.unit.test.js @@ -10,6 +10,7 @@ // import ByteCountCoded from "../../../src/domain/shared/ByteCountCoded"; +import { buffer2hex } from "../../testUtils"; describe("ByteCountCoded - unit tests", () => { @@ -26,9 +27,8 @@ describe("ByteCountCoded - unit tests", () => { let data = new DataView(bufferArray.buffer); let codec = new ByteCountCoded(); let bytesConsumed = codec.decode(data, bufferArray.length); - expect(bytesConsumed).toBe(1); - expect(codec.data).toBe(4); + expect(codec.data).toBe(4n); // 2 bytes encoded. bufferHex = "8bfafffe3f"; @@ -38,9 +38,8 @@ describe("ByteCountCoded - unit tests", () => { data = new DataView(bufferArray.buffer); codec = new ByteCountCoded(); bytesConsumed = codec.decode(data, bufferArray.length); - expect(bytesConsumed).toBe(2); - expect(codec.data).toBe(6132); + expect(codec.data).toBe(6132n); // 3 bytes encoded. bufferHex = "d20e80ffff8fff"; @@ -50,9 +49,55 @@ describe("ByteCountCoded - unit tests", () => { data = new DataView(bufferArray.buffer); codec = new ByteCountCoded(); bytesConsumed = codec.decode(data, bufferArray.length); - expect(bytesConsumed).toBe(3); - expect(codec.data).toBe(11785); + expect(codec.data).toBe(11785n); + + // 0 encoding. + bufferHex = "00dc"; + bufferArray = new Uint8Array(bufferHex.match(/[\da-f]{2}/giu).map(function (hex) { + return parseInt(hex, 16); + })); + data = new DataView(bufferArray.buffer); + codec = new ByteCountCoded(); + bytesConsumed = codec.decode(data, bufferArray.length); + expect(bytesConsumed).toBe(1); + expect(codec.data).toBe(0n); + }); + + test("Can byte count encode data", () => { + const codec = new ByteCountCoded(); + + // 1 byte encoded. + let bufferHex = "1000"; + codec.data = 4n; + let bufferArray = new Uint8Array(2); + let bytesWritten = codec.encode(new DataView(bufferArray.buffer, 0)); + expect(bytesWritten).toBe(1); + expect(buffer2hex(bufferArray)).toBe(bufferHex); + + // 2 bytes encoded. + bufferHex = "8bfa0000"; + codec.data = 6132n; + bufferArray = new Uint8Array(4); + bytesWritten = codec.encode(new DataView(bufferArray.buffer, 0)); + expect(bytesWritten).toBe(2); + expect(buffer2hex(bufferArray)).toBe(bufferHex); + + // 3 bytes encoded. + bufferHex = "d20e80000000"; + codec.data = 11785n; + bufferArray = new Uint8Array(6); + bytesWritten = codec.encode(new DataView(bufferArray.buffer, 0)); + expect(bytesWritten).toBe(3); + expect(buffer2hex(bufferArray)).toBe(bufferHex); + + // 0 encoding. + bufferHex = "0000"; + codec.data = 0n; + bufferArray = new Uint8Array(2); + bytesWritten = codec.encode(new DataView(bufferArray.buffer, 0)); + expect(bytesWritten).toBe(1); + expect(buffer2hex(bufferArray)).toBe(bufferHex); }); }); diff --git a/tests/domain/shared/GLMHelpers.unit.test.js b/tests/domain/shared/GLMHelpers.unit.test.js index 27de7e5d..812310dc 100644 --- a/tests/domain/shared/GLMHelpers.unit.test.js +++ b/tests/domain/shared/GLMHelpers.unit.test.js @@ -30,6 +30,15 @@ describe("GLMHelpers - unit tests", () => { expect(quat.w).toBeCloseTo(1, 2); }); + test("Can write a quaternion into 8 bytes of packet data", () => { + const buffer = new ArrayBuffer(16); + const data = new DataView(buffer); + const quat = { x: -0.00913472, y: 0.104486, z: 0.0052514, w: 0.994471 }; + GLMHelpers.packOrientationQuatToBytes(data, 2, quat); + const bytes = buffer2hex(buffer); + expect(bytes).toBe("0000d47e5f8dab8049ff000000000000"); + }); + test("Can read a quaternion from 6 bytes of packet data", () => { // 30 deg yaw. diff --git a/tests/domain/shared/JSONExtensions.unit.test.js b/tests/domain/shared/JSONExtensions.unit.test.js new file mode 100644 index 00000000..f719b45f --- /dev/null +++ b/tests/domain/shared/JSONExtensions.unit.test.js @@ -0,0 +1,52 @@ +// +// JSONExtensions.unit.test.js +// +// Created by David Rowe on 3 Jul 2023. +// Copyright 2023 Vircadia contributors. +// Copyright 2023 DigiSomni LLC. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* + eslint-disable + @typescript-eslint/no-magic-numbers, + @typescript-eslint/no-unsafe-assignment, + @typescript-eslint/no-unsafe-member-access +*/ + +import { bigintReplacer, bigintReviver } from "../../../src/domain/shared/JSONExtensions"; +import Uuid from "../../../src/domain/shared/Uuid"; + + +describe("JSON extensions - unit tests", () => { + + test("Can stringify objects with bigint values", () => { + const value = 2n ** 127n + 3n; // Most-significant and 2 least-significant bits. + const object = { + value, + other: 123 + }; + const jsonString = JSON.stringify(object, bigintReplacer); + expect(jsonString).toBe("{\"value\":\"170141183460469231731687303715884105731n\",\"other\":123}"); + }); + + test("Can stringify objects with Uuid values", () => { + const value = new Uuid(2n ** 127n + 3n); // Most-significant and 2 least-significant bits. + const object = { + value, + other: 123 + }; + const jsonString = JSON.stringify(object, bigintReplacer); + expect(jsonString).toBe("{\"value\":\"170141183460469231731687303715884105731n\",\"other\":123}"); + }); + + test("Can parse objects with bigint values", () => { + const jsonString = "{\"value\":\"170141183460469231731687303715884105731n\",\"other\":123}"; + const object = JSON.parse(jsonString, bigintReviver); + expect(object.value).toBe(2n ** 127n + 3n); + expect(object.other).toBe(123); + }); + +}); diff --git a/tests/domain/shared/PropertyFlags.unit.test.js b/tests/domain/shared/PropertyFlags.unit.test.js index 6b2ac0ec..70187928 100644 --- a/tests/domain/shared/PropertyFlags.unit.test.js +++ b/tests/domain/shared/PropertyFlags.unit.test.js @@ -10,30 +10,44 @@ // import PropertyFlags from "../../../src/domain/shared/PropertyFlags"; +import { buffer2hex } from "../../testUtils"; describe("EntityData - unit tests", () => { /* eslint-disable @typescript-eslint/no-magic-numbers */ + test("Can create an empty property flags", () => { + const propertyFlags = new PropertyFlags(); + expect(propertyFlags.isEmpty()).toBe(true); + }); + test("Can set and get property flags", () => { const propertyFlags = new PropertyFlags(); // Set 1st bit. propertyFlags.setHasProperty(0, true); expect(propertyFlags.getHasProperty(0)).toBe(true); + expect(propertyFlags.isEmpty()).toBe(false); + expect(propertyFlags.length()).toBe(1); // Clear 1st bit. propertyFlags.setHasProperty(0, false); expect(propertyFlags.getHasProperty(0)).toBe(false); + expect(propertyFlags.isEmpty()).toBe(true); + expect(propertyFlags.length()).toBe(0); // Set 2nd bit. propertyFlags.setHasProperty(1, true); expect(propertyFlags.getHasProperty(1)).toBe(true); + expect(propertyFlags.isEmpty()).toBe(false); + expect(propertyFlags.length()).toBe(2); // Clear 2nd bit. propertyFlags.setHasProperty(1, false); expect(propertyFlags.getHasProperty(1)).toBe(false); + expect(propertyFlags.isEmpty()).toBe(true); + expect(propertyFlags.length()).toBe(0); // Set 1st, 2nd and 9th bits. propertyFlags.setHasProperty(0, true); @@ -42,6 +56,8 @@ describe("EntityData - unit tests", () => { expect(propertyFlags.getHasProperty(0)).toBe(true); expect(propertyFlags.getHasProperty(1)).toBe(true); expect(propertyFlags.getHasProperty(8)).toBe(true); + expect(propertyFlags.isEmpty()).toBe(false); + expect(propertyFlags.length()).toBe(9); }); test("Can decode property flags", () => { @@ -53,8 +69,9 @@ describe("EntityData - unit tests", () => { const propertyFlags = new PropertyFlags(); const bytesConsumed = propertyFlags.decode(data, 32, 0); - expect(bytesConsumed).toBe(16); + expect(propertyFlags.isEmpty()).toBe(false); + expect(propertyFlags.getEncodedLength()).toBe(16); expect(propertyFlags.getHasProperty(0)).toBe(false); expect(propertyFlags.getHasProperty(1)).toBe(false); @@ -168,5 +185,222 @@ describe("EntityData - unit tests", () => { expect(propertyFlags.getHasProperty(109)).toBe(true); expect(propertyFlags.getHasProperty(110)).toBe(true); expect(propertyFlags.getHasProperty(111)).toBe(false); + + expect(propertyFlags.length()).toBe(111); + }); + + test("Can encode property flags", () => { + const bufferHex = "fffe3fffcdfffffffffffff8381ffffe"; + + const propertyFlags = new PropertyFlags(); + propertyFlags.setHasProperty(2, true); + propertyFlags.setHasProperty(3, true); + propertyFlags.setHasProperty(4, true); + propertyFlags.setHasProperty(5, true); + propertyFlags.setHasProperty(6, true); + propertyFlags.setHasProperty(7, true); + propertyFlags.setHasProperty(8, true); + propertyFlags.setHasProperty(9, true); + propertyFlags.setHasProperty(10, true); + propertyFlags.setHasProperty(11, true); + propertyFlags.setHasProperty(12, true); + propertyFlags.setHasProperty(13, true); + propertyFlags.setHasProperty(14, true); + propertyFlags.setHasProperty(15, true); + propertyFlags.setHasProperty(16, true); + propertyFlags.setHasProperty(17, true); + propertyFlags.setHasProperty(20, true); + propertyFlags.setHasProperty(21, true); + propertyFlags.setHasProperty(23, true); + propertyFlags.setHasProperty(24, true); + propertyFlags.setHasProperty(25, true); + propertyFlags.setHasProperty(26, true); + propertyFlags.setHasProperty(27, true); + propertyFlags.setHasProperty(28, true); + propertyFlags.setHasProperty(29, true); + propertyFlags.setHasProperty(30, true); + propertyFlags.setHasProperty(31, true); + propertyFlags.setHasProperty(32, true); + propertyFlags.setHasProperty(33, true); + propertyFlags.setHasProperty(34, true); + propertyFlags.setHasProperty(35, true); + propertyFlags.setHasProperty(36, true); + propertyFlags.setHasProperty(37, true); + propertyFlags.setHasProperty(38, true); + propertyFlags.setHasProperty(39, true); + propertyFlags.setHasProperty(40, true); + propertyFlags.setHasProperty(41, true); + propertyFlags.setHasProperty(42, true); + propertyFlags.setHasProperty(43, true); + propertyFlags.setHasProperty(44, true); + propertyFlags.setHasProperty(45, true); + propertyFlags.setHasProperty(46, true); + propertyFlags.setHasProperty(47, true); + propertyFlags.setHasProperty(48, true); + propertyFlags.setHasProperty(49, true); + propertyFlags.setHasProperty(50, true); + propertyFlags.setHasProperty(51, true); + propertyFlags.setHasProperty(52, true); + propertyFlags.setHasProperty(53, true); + propertyFlags.setHasProperty(54, true); + propertyFlags.setHasProperty(55, true); + propertyFlags.setHasProperty(56, true); + propertyFlags.setHasProperty(57, true); + propertyFlags.setHasProperty(58, true); + propertyFlags.setHasProperty(59, true); + propertyFlags.setHasProperty(60, true); + propertyFlags.setHasProperty(61, true); + propertyFlags.setHasProperty(62, true); + propertyFlags.setHasProperty(63, true); + propertyFlags.setHasProperty(64, true); + propertyFlags.setHasProperty(65, true); + propertyFlags.setHasProperty(66, true); + propertyFlags.setHasProperty(67, true); + propertyFlags.setHasProperty(68, true); + propertyFlags.setHasProperty(69, true); + propertyFlags.setHasProperty(70, true); + propertyFlags.setHasProperty(71, true); + propertyFlags.setHasProperty(72, true); + propertyFlags.setHasProperty(73, true); + propertyFlags.setHasProperty(74, true); + propertyFlags.setHasProperty(75, true); + propertyFlags.setHasProperty(76, true); + propertyFlags.setHasProperty(82, true); + propertyFlags.setHasProperty(83, true); + propertyFlags.setHasProperty(84, true); + propertyFlags.setHasProperty(91, true); + propertyFlags.setHasProperty(92, true); + propertyFlags.setHasProperty(93, true); + propertyFlags.setHasProperty(94, true); + propertyFlags.setHasProperty(95, true); + propertyFlags.setHasProperty(96, true); + propertyFlags.setHasProperty(97, true); + propertyFlags.setHasProperty(98, true); + propertyFlags.setHasProperty(99, true); + propertyFlags.setHasProperty(100, true); + propertyFlags.setHasProperty(101, true); + propertyFlags.setHasProperty(102, true); + propertyFlags.setHasProperty(103, true); + propertyFlags.setHasProperty(104, true); + propertyFlags.setHasProperty(105, true); + propertyFlags.setHasProperty(106, true); + propertyFlags.setHasProperty(107, true); + propertyFlags.setHasProperty(108, true); + propertyFlags.setHasProperty(109, true); + propertyFlags.setHasProperty(110, true); + + expect(propertyFlags.length()).toBe(111); + + const bufferArray = new Uint8Array(16); + const data = new DataView(bufferArray.buffer); + + const bytesWritten = propertyFlags.encode(data, 0); + expect(bytesWritten).toBe(16); + expect(propertyFlags.getEncodedLength()).toBe(16); + + const bytes = buffer2hex(bufferArray); + expect(bytes).toBe(bufferHex); + }); + + test("Can construct from another PropertyFlags object", () => { + const bufferHex = "fffe3fffcdfffffffffffff8381ffffe"; + const bufferArray = new Uint8Array(bufferHex.match(/[\da-f]{2}/giu).map(function (hex) { + return parseInt(hex, 16); + })); + const data = new DataView(bufferArray.buffer); + const propertyFlags = new PropertyFlags(); + + const bytesConsumed = propertyFlags.decode(data, 16, 0); + expect(bytesConsumed).toBe(16); + expect(propertyFlags.length()).toBe(111); + const propertyFlagsCopy = new PropertyFlags(propertyFlags); + expect(propertyFlagsCopy.length()).toBe(111); + + const originalArray = new Uint8Array(16); + const originalData = new DataView(originalArray.buffer); + propertyFlags.encode(originalData, 0); + const originalBytes = buffer2hex(bufferArray); + expect(originalBytes).toBe(bufferHex); + + const copyArray = new Uint8Array(16); + const copyData = new DataView(copyArray.buffer); + propertyFlags.encode(copyData, 0); + const copyBytes = buffer2hex(bufferArray); + expect(copyBytes).toBe(bufferHex); + }); + + test("Can copy from another PropertyFlags object", () => { + const bufferHex = "fffe3fffcdfffffffffffff8381ffffe"; + const bufferArray = new Uint8Array(bufferHex.match(/[\da-f]{2}/giu).map(function (hex) { + return parseInt(hex, 16); + })); + const data = new DataView(bufferArray.buffer); + const propertyFlags = new PropertyFlags(); + + const bytesConsumed = propertyFlags.decode(data, 16, 0); + expect(bytesConsumed).toBe(16); + expect(propertyFlags.length()).toBe(111); + const propertyFlagsCopy = new PropertyFlags(); + propertyFlagsCopy.copy(propertyFlags); + expect(propertyFlagsCopy.length()).toBe(111); + + const originalArray = new Uint8Array(16); + const originalData = new DataView(originalArray.buffer); + propertyFlags.encode(originalData, 0); + const originalBytes = buffer2hex(bufferArray); + expect(originalBytes).toBe(bufferHex); + + const copyArray = new Uint8Array(16); + const copyData = new DataView(copyArray.buffer); + propertyFlags.encode(copyData, 0); + const copyBytes = buffer2hex(bufferArray); + expect(copyBytes).toBe(bufferHex); + }); + + test("Can \"or()\" two PropertyFlags objects", () => { + const propertyFlagsA = new PropertyFlags(); + propertyFlagsA.setHasProperty(0, true); + propertyFlagsA.setHasProperty(1, true); + propertyFlagsA.setHasProperty(2, false); + propertyFlagsA.setHasProperty(3, false); + propertyFlagsA.setHasProperty(8, true); + + const propertyFlagsB = new PropertyFlags(); + propertyFlagsB.setHasProperty(1, false); + propertyFlagsB.setHasProperty(2, true); + propertyFlagsB.setHasProperty(11, true); + + propertyFlagsA.or(propertyFlagsB); + + expect(propertyFlagsA.getHasProperty(0)).toBe(true); + expect(propertyFlagsA.getHasProperty(1)).toBe(true); + expect(propertyFlagsA.getHasProperty(2)).toBe(true); + expect(propertyFlagsA.getHasProperty(3)).toBe(false); + expect(propertyFlagsA.getHasProperty(8)).toBe(true); + expect(propertyFlagsA.getHasProperty(11)).toBe(true); }); + + test("Can output debug information", () => { + let debugMessage = ""; + const debug = jest.spyOn(console, "debug").mockImplementation((...message) => { + debugMessage = message.join(" "); + }); + + const propertyFlags = new PropertyFlags(); + propertyFlags.setHasProperty(0, true); + propertyFlags.setHasProperty(1, true); + propertyFlags.setHasProperty(8, true); + expect(propertyFlags.getHasProperty(0)).toBe(true); + expect(propertyFlags.getHasProperty(1)).toBe(true); + expect(propertyFlags.getHasProperty(8)).toBe(true); + + propertyFlags.debugDumpBits(); + expect(debugMessage).toBe("bits: 110000001"); + + propertyFlags.debugDumpBits("Test"); + expect(debugMessage).toBe("Test bits: 110000001"); + + debug.mockReset(); + }); + });