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 @@
lifetime
+ * property value set.
+ * @property {boolean} canGetAndSetPrivateUserData - Whether the user has permissions to get and set entities'
+ * privateUserData properties in the domain.
+ * @property {SignalprivateUserData 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.
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 ZoneambientLight 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
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 Modelanimation 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 Mapbloom 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 Zonebloom 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
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 + * TheHostType namespace enumerates how an entity is hosted and sent to others for display.
+ * C++: HostType
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
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 thedidntFitProperties 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}.
- * | Name | Value | Description |
|---|---|---|
| PROP_PAGED_PROPERTY | 0 | Paged property flag. |
| PROP_CUSTOM_PROPERTIES_INCLUDED | 1 | Custom properties included flag. |
| PROP_SIMULATION_OWNER | 2 | Simulation owner flag. |
| PROP_PARENT_ID | 3 | Parent id flag. |
| PROP_PARENT_JOINT_INDEX | 4 | Parent joint index flag. |
| PROP_VISIBLE | 5 | Visible flag. |
| PROP_NAME | 6 | Name flag. |
| PROP_LOCKED | 7 | Locked flag. |
| PROP_USER_DATA | 8 | User data flag. |
| PROP_PRIVATE_USER_DATA | 9 | Private user data flag. |
| PROP_HREF | 10 | Href flag. |
| PROP_DESCRIPTION | 11 | Description flag. |
| PROP_POSITION | 12 | Position flag. |
| PROP_DIMENSIONS | 13 | Dimensions flag. |
| PROP_ROTATION | 14 | Rotation flag. |
| PROP_REGISTRATION_POINT | 15 | Registration point flag. |
| PROP_CREATED | 16 | Created flag. |
| PROP_LAST_EDITED_BY | 17 | Last edited by flag. |
| PROP_ENTITY_HOST_TYPE | 18 | Entity host type flag. |
| PROP_OWNING_AVATAR_ID | 19 | Owning avatar id flag. |
| PROP_QUERY_AA_CUBE | 20 | Query aa cube flag. |
| PROP_CAN_CAST_SHADOW | 21 | Can cast shadow flag. |
| PROP_VISIBLE_IN_SECONDARY_CAMERA | 22 | Visible in secondary camera flag. |
| PROP_RENDER_LAYER | 23 | Render layer flag. |
| PROP_PRIMITIVE_MODE | 24 | Primitive mode flag. |
| PROP_IGNORE_PICK_INTERSECTION | 25 | Ignore pick intersection flag. |
| PROP_RENDER_WITH_ZONES | 26 | Render with zones flag. |
| PROP_BILLBOARD_MODE | 27 | Billboard mode flag. |
| PROP_GRAB_GRABBABLE | 28 | Grab grabbable flag. |
| PROP_GRAB_KINEMATIC | 29 | Grab kinematic flag. |
| PROP_GRAB_FOLLOWS_CONTROLLER | 30 | Grab follows controller flag. |
| PROP_GRAB_TRIGGERABLE | 31 | Grab triggerable flag. |
| PROP_GRAB_EQUIPPABLE | 32 | Grab equippable flag. |
| PROP_GRAB_DELEGATE_TO_PARENT | 33 | Grab delegate to parent flag. |
| PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET | 34 | Grab left equippable position offset flag. | - *
| PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET | 35 | Grab left equippable rotation offset flag. | - *
| PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET | 36 | Grab right equippable position offset flag. - * |
| PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET | 37 | Grab right equippable rotation offset flag. - * |
| PROP_GRAB_EQUIPPABLE_INDICATOR_URL | 38 | Grab equippable indicator url flag. |
| PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE | 39 | Grab equippable indicator scale flag. |
| PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET | 40 | Grab equippable indicator offset flag. |
| PROP_DENSITY | 41 | Density flag. |
| PROP_VELOCITY | 42 | Velocity flag. |
| PROP_ANGULAR_VELOCITY | 43 | Angular velocity flag. |
| PROP_GRAVITY | 44 | Gravity flag. |
| PROP_ACCELERATION | 45 | Acceleration flag. |
| PROP_DAMPING | 46 | Damping flag. |
| PROP_ANGULAR_DAMPING | 47 | Angular damping flag. |
| PROP_RESTITUTION | 48 | Restitution flag. |
| PROP_FRICTION | 49 | Friction flag. |
| PROP_LIFETIME | 50 | Lifetime flag. |
| PROP_COLLISIONLESS | 51 | Collisionless flag. |
| PROP_COLLISION_MASK | 52 | Collision mask flag. |
| PROP_DYNAMIC | 53 | Dynamic flag. |
| PROP_COLLISION_SOUND_URL | 54 | Collision sound url flag. |
| PROP_ACTION_DATA | 55 | Action data flag. |
| PROP_CLONEABLE | 56 | Cloneable flag. |
| PROP_CLONE_LIFETIME | 57 | Clone lifetime flag. |
| PROP_CLONE_LIMIT | 58 | Clone limit flag. |
| PROP_CLONE_DYNAMIC | 59 | Clone dynamic flag. |
| PROP_CLONE_AVATAR_ENTITY | 60 | Clone avatar entity flag. |
| PROP_CLONE_ORIGIN_ID | 61 | Clone origin id flag. |
| PROP_SCRIPT | 62 | Script flag. |
| PROP_SCRIPT_TIMESTAMP | 63 | Script timestamp flag. |
| PROP_SERVER_SCRIPTS | 64 | Server scripts flag. |
| PROP_ITEM_NAME | 65 | Item name flag. |
| PROP_ITEM_DESCRIPTION | 66 | Item description flag. |
| PROP_ITEM_CATEGORIES | 67 | Item categories flag. |
| PROP_ITEM_ARTIST | 68 | Item artist flag. |
| PROP_ITEM_LICENSE | 69 | Item license flag. |
| PROP_LIMITED_RUN | 70 | Limited run flag. |
| PROP_MARKETPLACE_ID | 71 | Marketplace id flag. |
| PROP_EDITION_NUMBER | 72 | Edition number flag. |
| PROP_ENTITY_INSTANCE_NUMBER | 73 | Entity instance number flag. |
| PROP_CERTIFICATE_ID | 74 | Certificate id flag. |
| PROP_CERTIFICATE_TYPE | 75 | Certificate type flag. |
| PROP_STATIC_CERTIFICATE_VERSION | 76 | Static certificate version flag. |
| PROP_LOCAL_POSITION | 77 | Local position flag. |
| PROP_LOCAL_ROTATION | 78 | Local rotation flag. |
| PROP_LOCAL_VELOCITY | 79 | Local velocity flag. |
| PROP_LOCAL_ANGULAR_VELOCITY | 80 | Local angular velocity flag. |
| PROP_LOCAL_DIMENSIONS | 81 | Local dimensions flag. |
| PROP_SHAPE_TYPE | 82 | Shape type flag. |
| PROP_COMPOUND_SHAPE_URL | 83 | Compound shape url flag. |
| PROP_COLOR | 84 | Color flag. |
| PROP_ALPHA | 85 | Alpha flag. |
| PROP_PULSE_MIN | 86 | Pulse min flag. |
| PROP_PULSE_MAX | 87 | Pulse max flag. |
| PROP_PULSE_PERIOD | 88 | Pulse period flag. |
| PROP_PULSE_COLOR_MODE | 89 | Pulse color mode flag. |
| PROP_PULSE_ALPHA_MODE | 90 | Pulse alpha mode flag. |
| PROP_TEXTURES | 91 | Textures flag. |
| PROP_DERIVED_0 | 92 | Derived 0 flag. |
| PROP_DERIVED_1 | 93 | Derived 1 flag. |
| PROP_DERIVED_2 | 94 | Derived 2 flag. |
| PROP_DERIVED_3 | 95 | Derived 3 flag. |
| PROP_DERIVED_4 | 96 | Derived 4 flag. |
| PROP_DERIVED_5 | 97 | Derived 5 flag. |
| PROP_DERIVED_6 | 98 | Derived 6 flag. |
| PROP_DERIVED_7 | 99 | Derived 7 flag. |
| PROP_DERIVED_8 | 100 | Derived 8 flag. |
| PROP_DERIVED_9 | 101 | Derived 9 flag. |
| PROP_DERIVED_10 | 102 | Derived 10 flag. |
| PROP_DERIVED_11 | 103 | Derived 11 flag. |
| PROP_DERIVED_12 | 104 | Derived 12 flag. |
| PROP_DERIVED_13 | 105 | Derived 13 flag. |
| PROP_DERIVED_14 | 106 | Derived 14 flag. |
| PROP_DERIVED_15 | 107 | Derived 15 flag. |
| PROP_DERIVED_16 | 108 | Derived 16 flag. |
| PROP_DERIVED_17 | 109 | Derived 17 flag. |
| PROP_DERIVED_18 | 110 | Derived 18 flag. |
| PROP_DERIVED_19 | 111 | Derived 19 flag. |
| PROP_DERIVED_20 | 112 | Derived 20 flag. |
| PROP_DERIVED_21 | 113 | Derived 21 flag. |
| PROP_DERIVED_22 | 114 | Derived 22 flag. |
| PROP_DERIVED_23 | 115 | Derived 23 flag. |
| PROP_DERIVED_24 | 116 | Derived 24 flag. |
| PROP_DERIVED_25 | 117 | Derived 25 flag. |
| PROP_DERIVED_26 | 118 | Derived 26 flag. |
| PROP_DERIVED_27 | 119 | Derived 27 flag. |
| PROP_DERIVED_28 | 120 | Derived 28 flag. |
| PROP_DERIVED_29 | 121 | Derived 29 flag. |
| PROP_DERIVED_30 | 122 | Derived 30 flag. |
| PROP_DERIVED_31 | 123 | Derived 31 flag. |
| PROP_DERIVED_32 | 124 | Derived 32 flag. |
| PROP_DERIVED_33 | 125 | Derived 33 flag. |
| PROP_DERIVED_34 | 126 | Derived 34 flag. |
| PROP_AFTER_LAST_ITEM | 127 | After 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. | - *
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
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
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 entitygrab 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 entitygrab 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 Maphaze 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 Zonehaze 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 MapkeyLight 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 ZonekeyLight 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 Recordtrue 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 Recordskybox 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 Zoneskybox 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.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.
- * @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();
+ });
+
});