Implement GLTF overrides
This commit is contained in:
@@ -13,25 +13,65 @@ describe('LLGLTFMaterial', () =>
|
||||
assert.equal(mat.version, '1.1');
|
||||
assert.equal(mat.type, 'GLTF 2.0');
|
||||
assert.ok(mat.data);
|
||||
assert.equal(mat.data.asset.version, '2.0');
|
||||
assert.equal(mat.data.images.length, 4);
|
||||
assert.equal(mat.data.images[0].uri, '2c7e7332-3717-5f4e-8f22-6ee9da5275fb');
|
||||
assert.equal(mat.data.images[1].uri, '1078f5ec-1c56-e73f-8f8f-be36c40e5121');
|
||||
assert.equal(mat.data.images[2].uri, '230d2d2d-b092-9bf5-ba7e-b319566322a6');
|
||||
assert.equal(mat.data.images[3].uri, '230d2d2d-b092-9bf5-ba7e-b319566322a6');
|
||||
assert.equal(mat.data.materials.length, 1);
|
||||
assert.equal(mat.data.asset?.version, '2.0');
|
||||
assert.equal(mat.data.images?.length, 4);
|
||||
let image = mat.data?.images?.[0];
|
||||
if (image && 'uri' in image)
|
||||
{
|
||||
assert.equal(image.uri, '2c7e7332-3717-5f4e-8f22-6ee9da5275fb');
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Image missing');
|
||||
}
|
||||
image = mat.data?.images?.[1];
|
||||
if (image && 'uri' in image)
|
||||
{
|
||||
assert.equal(image.uri, '1078f5ec-1c56-e73f-8f8f-be36c40e5121');
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Image missing');
|
||||
}
|
||||
image = mat.data?.images?.[2];
|
||||
if (image && 'uri' in image)
|
||||
{
|
||||
assert.equal(image.uri, '230d2d2d-b092-9bf5-ba7e-b319566322a6');
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Image missing');
|
||||
}
|
||||
image = mat.data?.images?.[3];
|
||||
if (image && 'uri' in image)
|
||||
{
|
||||
assert.equal(image.uri, '230d2d2d-b092-9bf5-ba7e-b319566322a6');
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Image missing');
|
||||
}
|
||||
|
||||
const mat0 = mat.data.materials[0];
|
||||
assert.equal(mat0.normalTexture.index, 1);
|
||||
assert.equal(mat0.occlusionTexture.index, 3);
|
||||
assert.equal(mat0.pbrMetallicRoughness.baseColorTexture.index, 0);
|
||||
assert.equal(mat0.pbrMetallicRoughness.metallicRoughnessTexture.index, 2);
|
||||
assert.equal(mat.data.materials?.length, 1);
|
||||
|
||||
assert.equal(mat.data.textures.length, 4);
|
||||
assert.equal(mat.data.textures[0].source, 0);
|
||||
assert.equal(mat.data.textures[1].source, 1);
|
||||
assert.equal(mat.data.textures[2].source, 2);
|
||||
assert.equal(mat.data.textures[3].source, 3);
|
||||
const mat0 = mat.data.materials?.[0];
|
||||
if (mat0)
|
||||
{
|
||||
assert.equal(mat0.normalTexture?.index, 1);
|
||||
assert.equal(mat0.occlusionTexture?.index, 3);
|
||||
assert.equal(mat0.pbrMetallicRoughness?.baseColorTexture?.index, 0);
|
||||
assert.equal(mat0.pbrMetallicRoughness?.metallicRoughnessTexture?.index, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Material missing');
|
||||
}
|
||||
|
||||
assert.equal(mat.data.textures?.length, 4);
|
||||
assert.equal(mat.data.textures?.[0].source, 0);
|
||||
assert.equal(mat.data.textures?.[1].source, 1);
|
||||
assert.equal(mat.data.textures?.[2].source, 2);
|
||||
assert.equal(mat.data.textures?.[3].source, 3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,28 +1,196 @@
|
||||
export interface LLGLTFMaterialData
|
||||
export interface LLGLTFExtensionsAndExtras
|
||||
{
|
||||
asset: {
|
||||
version: string;
|
||||
};
|
||||
images: {
|
||||
uri: string;
|
||||
}[];
|
||||
materials: {
|
||||
normalTexture: {
|
||||
index: number
|
||||
},
|
||||
occlusionTexture: {
|
||||
index: number;
|
||||
},
|
||||
pbrMetallicRoughness: {
|
||||
baseColorTexture: {
|
||||
index: number
|
||||
},
|
||||
metallicRoughnessTexture: {
|
||||
index: number
|
||||
}
|
||||
}
|
||||
}[];
|
||||
textures: {
|
||||
source: number
|
||||
}[];
|
||||
extensions?: Record<string, unknown>
|
||||
extras?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface LLGLTFTexture
|
||||
{
|
||||
index: number,
|
||||
extensions?: Record<string, unknown>,
|
||||
extras?: Record<string, unknown>,
|
||||
texCoord?: number,
|
||||
}
|
||||
|
||||
export type LLGLTFTextureInfo = LLGLTFTexture & LLGLTFExtensionsAndExtras;
|
||||
|
||||
export interface LLGLTFMaterialDataPart
|
||||
{
|
||||
asset?: {
|
||||
version: string;
|
||||
generator?: string;
|
||||
minVersion?: string;
|
||||
copyright?: string;
|
||||
} & LLGLTFExtensionsAndExtras;
|
||||
extensionsUsed?: string[];
|
||||
extensionsRequired?: string[];
|
||||
buffers?: ({
|
||||
byteLength: number;
|
||||
uri?: string;
|
||||
type?: string;
|
||||
name?: string;
|
||||
} & LLGLTFExtensionsAndExtras)[],
|
||||
bufferViews?: ({
|
||||
buffer: number;
|
||||
byteOffset?: number;
|
||||
byteLength: number;
|
||||
byteStride?: number;
|
||||
target: number;
|
||||
name?: string;
|
||||
} & LLGLTFExtensionsAndExtras)[],
|
||||
accessors?: ({
|
||||
bufferView?: number;
|
||||
byteOffset?: number;
|
||||
normalized?: number;
|
||||
componentType: number;
|
||||
count: number;
|
||||
type: string;
|
||||
name?: string;
|
||||
minValues?: number[];
|
||||
maxValues?: number[];
|
||||
sparse?: {
|
||||
count: number;
|
||||
bufferView: number;
|
||||
indices: {
|
||||
bufferView: number;
|
||||
byteOffset?: number;
|
||||
componentType?: number;
|
||||
} & LLGLTFExtensionsAndExtras,
|
||||
values: {
|
||||
bufferView: number;
|
||||
byteOffset?: number;
|
||||
} & LLGLTFExtensionsAndExtras
|
||||
} & LLGLTFExtensionsAndExtras
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
meshes?: ({
|
||||
name?: string;
|
||||
primitives?: ({
|
||||
material?: number;
|
||||
mode?: number;
|
||||
indices?: number;
|
||||
targets?: Record<string, number>[];
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
weights?: number[];
|
||||
} & LLGLTFExtensionsAndExtras)[],
|
||||
nodes?: ({
|
||||
name?: string;
|
||||
skin?: number;
|
||||
camera?: number;
|
||||
mesh?: number;
|
||||
children?: number[];
|
||||
weights?: number[];
|
||||
emitter?: number;
|
||||
light?: number;
|
||||
} & (
|
||||
{
|
||||
rotation?: number[];
|
||||
scale?: number[];
|
||||
translaction?: number[]
|
||||
} | {
|
||||
matrix?: number[]
|
||||
}
|
||||
)) & LLGLTFExtensionsAndExtras[],
|
||||
scenes?: ({
|
||||
name?: string;
|
||||
nodes?: number[];
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
scene?: number;
|
||||
materials?: ({
|
||||
name?: string;
|
||||
emissiveFactor?: number[];
|
||||
alphaMode?: string;
|
||||
alphaCutoff?: number;
|
||||
doubleSided?: boolean;
|
||||
pbrMetallicRoughness?: {
|
||||
baseColorFactor: number[];
|
||||
baseColorTexture?: LLGLTFTextureInfo;
|
||||
metallicRoughnessTexture?: LLGLTFTextureInfo;
|
||||
metallicFactor: number;
|
||||
roughnessFactor: number;
|
||||
} & LLGLTFExtensionsAndExtras,
|
||||
normalTexture?: {
|
||||
index: number;
|
||||
texCoord?: number;
|
||||
scale?: number;
|
||||
} & LLGLTFExtensionsAndExtras,
|
||||
occlusionTexture?: {
|
||||
index: number;
|
||||
texCoord?: number;
|
||||
strength?: number;
|
||||
} & LLGLTFExtensionsAndExtras,
|
||||
emissiveTexture?: {
|
||||
extensions?: {
|
||||
KHR_texture_transform?: {
|
||||
offset: number[],
|
||||
rotation: number,
|
||||
scale: number[]
|
||||
}
|
||||
},
|
||||
index: number
|
||||
texCoord?: number;
|
||||
}
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
images?: (({
|
||||
bufferView: number;
|
||||
mimeType: string;
|
||||
width: number;
|
||||
height: number;
|
||||
} | {
|
||||
uri: string;
|
||||
}) & LLGLTFExtensionsAndExtras)[];
|
||||
textures?: ({
|
||||
sampler?: number;
|
||||
source?: number;
|
||||
name?: string;
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
animations?: ({
|
||||
name?: string;
|
||||
channels?: ({
|
||||
sampler: number;
|
||||
target?: {
|
||||
node?: number;
|
||||
path: string;
|
||||
|
||||
} & LLGLTFExtensionsAndExtras
|
||||
} & LLGLTFExtensionsAndExtras)[]
|
||||
samplers?: ({
|
||||
input: number;
|
||||
output: number;
|
||||
interpolation?: string;
|
||||
} & LLGLTFExtensionsAndExtras)[]
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
skins?: ({
|
||||
name?: string;
|
||||
joints?: number[];
|
||||
skeleton?: number;
|
||||
inverseBindMatrices: number;
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
samplers?: ({
|
||||
name?: string;
|
||||
minFilter?: number;
|
||||
magFilter: number;
|
||||
wrapS?: number;
|
||||
wrapT?: number;
|
||||
} & LLGLTFExtensionsAndExtras)[];
|
||||
cameras?: (({
|
||||
name?: string;
|
||||
type: 'orthographic';
|
||||
orthographic: {
|
||||
xmag: number;
|
||||
ymag: number;
|
||||
zfar: number;
|
||||
znear: number;
|
||||
} & LLGLTFExtensionsAndExtras
|
||||
} | {
|
||||
name?: string;
|
||||
type: 'perspective';
|
||||
perspective: {
|
||||
yfov: number;
|
||||
znear: number;
|
||||
aspectRatio?: number;
|
||||
zfar?: number;
|
||||
} & LLGLTFExtensionsAndExtras
|
||||
}) & LLGLTFExtensionsAndExtras)[];
|
||||
}
|
||||
|
||||
export type LLGLTFMaterialData = LLGLTFMaterialDataPart & LLGLTFExtensionsAndExtras;
|
||||
|
||||
19
lib/classes/LLGLTFMaterialOverride.ts
Normal file
19
lib/classes/LLGLTFMaterialOverride.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export interface LLGLTFTextureTransformOverride
|
||||
{
|
||||
offset: number[];
|
||||
scale: number[];
|
||||
rotation: number
|
||||
}
|
||||
|
||||
export class LLGLTFMaterialOverride
|
||||
{
|
||||
public textures?: string[];
|
||||
public baseColor?: number[];
|
||||
public emissiveColor?: number[];
|
||||
public metallicFactor?: number;
|
||||
public roughnessFactor?: number;
|
||||
public alphaMode?: number;
|
||||
public alphaCutoff?: number;
|
||||
public doubleSided?: boolean;
|
||||
public textureTransforms?: LLGLTFTextureTransformOverride[];
|
||||
}
|
||||
@@ -33,6 +33,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
super(circuit, agent, clientEvents, options);
|
||||
this.rtree = new RBush3D();
|
||||
this.fullStore = true;
|
||||
}
|
||||
|
||||
protected objectUpdate(objectUpdate: ObjectUpdateMessage): void
|
||||
@@ -43,17 +44,19 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
const parentID = objData.ParentID;
|
||||
let addToParentList = true;
|
||||
let newObject = false;
|
||||
if (this.objects[localID])
|
||||
let obj = this.objects.get(localID);
|
||||
if (obj)
|
||||
{
|
||||
if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID])
|
||||
const objsByParent = this.objectsByParent.get(parentID ?? 0);
|
||||
if (obj.ParentID !== parentID && objsByParent)
|
||||
{
|
||||
const ind = this.objectsByParent[parentID].indexOf(localID);
|
||||
const ind = objsByParent.indexOf(localID);
|
||||
if (ind !== -1)
|
||||
{
|
||||
this.objectsByParent[parentID].splice(ind, 1);
|
||||
objsByParent.splice(ind, 1);
|
||||
}
|
||||
}
|
||||
else if (this.objectsByParent[parentID])
|
||||
else if (objsByParent)
|
||||
{
|
||||
addToParentList = false;
|
||||
}
|
||||
@@ -61,12 +64,12 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
else
|
||||
{
|
||||
newObject = true;
|
||||
this.objects[localID] = new GameObject();
|
||||
this.objects[localID].region = this.agent.currentRegion;
|
||||
obj = new GameObject();
|
||||
obj.region = this.agent.currentRegion;
|
||||
this.objects.set(localID, obj);
|
||||
}
|
||||
this.objects[localID].deleted = false;
|
||||
obj.deleted = false;
|
||||
|
||||
const obj = this.objects[localID];
|
||||
obj.ID = objData.ID;
|
||||
obj.State = objData.State;
|
||||
obj.FullID = objData.FullID;
|
||||
@@ -100,6 +103,12 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
obj.ProfileEnd = Utils.unpackEndCut(objData.ProfileEnd);
|
||||
obj.ProfileHollow = Utils.unpackProfileHollow(objData.ProfileHollow);
|
||||
obj.TextureEntry = TextureEntry.from(objData.TextureEntry);
|
||||
const override = this.cachedMaterialOverrides.get(obj.ID);
|
||||
if (override)
|
||||
{
|
||||
obj.TextureEntry.gltfMaterialOverrides = override;
|
||||
this.cachedMaterialOverrides.delete(obj.ID);
|
||||
}
|
||||
obj.textureAnim = TextureAnim.from(objData.TextureAnim);
|
||||
|
||||
const pcodeData = objData.Data;
|
||||
@@ -131,7 +140,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.objects[localID].PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString())
|
||||
if (obj.PCode === PCode.Avatar && obj.FullID.toString() === this.agent.agentID.toString())
|
||||
{
|
||||
this.agent.localID = localID;
|
||||
|
||||
@@ -143,25 +152,23 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
if (parent !== this.agent.localID)
|
||||
{
|
||||
let foundAvatars = false;
|
||||
for (const objID of this.objectsByParent[parent])
|
||||
const objsByParent = this.objectsByParent.get(parent);
|
||||
if (objsByParent)
|
||||
{
|
||||
if (this.objects[objID])
|
||||
for (const objID of objsByParent)
|
||||
{
|
||||
const o = this.objects[objID];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
const ob = this.objects.get(objID);
|
||||
if (ob && ob.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.objects[parent])
|
||||
{
|
||||
const o = this.objects[parent];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
|
||||
const o = this.objects.get(parent);
|
||||
if (o && o.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
}
|
||||
if (!foundAvatars)
|
||||
{
|
||||
this.deleteObject(parent);
|
||||
@@ -170,23 +177,28 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
}
|
||||
}
|
||||
this.objects[localID].extraParams = ExtraParams.from(objData.ExtraParams);
|
||||
this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue));
|
||||
|
||||
this.objects[localID].IsAttachment = this.objects[localID].NameValue['AttachItemID'] !== undefined;
|
||||
if (obj.IsAttachment && obj.State !== undefined)
|
||||
{
|
||||
this.objects[localID].attachmentPoint = this.decodeAttachPoint(obj.State);
|
||||
}
|
||||
|
||||
this.objectsByUUID[objData.FullID.toString()] = localID;
|
||||
if (!this.objectsByParent[parentID])
|
||||
obj.extraParams = ExtraParams.from(objData.ExtraParams);
|
||||
obj.NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue));
|
||||
|
||||
obj.IsAttachment = obj.NameValue['AttachItemID'] !== undefined;
|
||||
if (obj.IsAttachment && obj.State !== undefined)
|
||||
{
|
||||
this.objectsByParent[parentID] = [];
|
||||
obj.attachmentPoint = this.decodeAttachPoint(obj.State);
|
||||
}
|
||||
|
||||
this.objectsByUUID.set(objData.FullID.toString(), localID);
|
||||
|
||||
let objByParent = this.objectsByParent.get(parentID);
|
||||
if (!objByParent)
|
||||
{
|
||||
objByParent = [];
|
||||
this.objectsByParent.set(parentID, objByParent);
|
||||
}
|
||||
if (addToParentList)
|
||||
{
|
||||
this.objectsByParent[parentID].push(localID);
|
||||
objByParent.push(localID);
|
||||
}
|
||||
|
||||
if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly && (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID))
|
||||
@@ -197,7 +209,8 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
else
|
||||
{
|
||||
this.insertIntoRtree(obj);
|
||||
if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects[objData.ParentID])
|
||||
const parentObj = this.objects.get(objData.ParentID ?? 0);
|
||||
if (objData.ParentID !== undefined && objData.ParentID !== 0 && !parentObj)
|
||||
{
|
||||
this.requestMissingObject(objData.ParentID).then(() =>
|
||||
{
|
||||
@@ -225,7 +238,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
rmo.ObjectData = [];
|
||||
for (const obj of objectUpdateCached.ObjectData)
|
||||
{
|
||||
if (!this.objects[obj.ID])
|
||||
if (!this.objects.has(obj.ID))
|
||||
{
|
||||
rmo.ObjectData.push({
|
||||
CacheMissType: 0,
|
||||
@@ -257,15 +270,16 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
pos += 4;
|
||||
const pcode = buf.readUInt8(pos++);
|
||||
let newObj = false;
|
||||
if (!this.objects[localID])
|
||||
let o = this.objects.get(localID);
|
||||
if (!o)
|
||||
{
|
||||
newObj = true;
|
||||
this.objects[localID] = new GameObject();
|
||||
this.objects[localID].region = this.agent.currentRegion;
|
||||
o = new GameObject();
|
||||
o.region = this.agent.currentRegion;
|
||||
this.objects.set(localID, o);
|
||||
}
|
||||
const o = this.objects[localID];
|
||||
o.ID = localID;
|
||||
this.objectsByUUID[fullID.toString()] = localID;
|
||||
this.objectsByUUID.set(fullID.toString(), localID);
|
||||
o.FullID = fullID;
|
||||
o.Flags = flags;
|
||||
o.PCode = pcode;
|
||||
@@ -301,26 +315,29 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
let add = true;
|
||||
if (!newObj && o.ParentID !== undefined)
|
||||
{
|
||||
if (newParentID !== o.ParentID)
|
||||
const obsByParent = this.objectsByParent.get(o.ParentID);
|
||||
if (newParentID !== o.ParentID && obsByParent)
|
||||
{
|
||||
const index = this.objectsByParent[o.ParentID].indexOf(localID);
|
||||
const index = obsByParent.indexOf(localID);
|
||||
if (index !== -1)
|
||||
{
|
||||
this.objectsByParent[o.ParentID].splice(index, 1);
|
||||
obsByParent.splice(index, 1);
|
||||
}
|
||||
}
|
||||
else if (this.objectsByParent[o.ParentID])
|
||||
else if (obsByParent)
|
||||
{
|
||||
add = false;
|
||||
}
|
||||
}
|
||||
if (add)
|
||||
{
|
||||
if (!this.objectsByParent[newParentID])
|
||||
let objsByNewParent = this.objectsByParent.get(newParentID);
|
||||
if (!objsByNewParent)
|
||||
{
|
||||
this.objectsByParent[newParentID] = [];
|
||||
objsByNewParent = [];
|
||||
this.objectsByParent.set(newParentID, objsByNewParent);
|
||||
}
|
||||
this.objectsByParent[newParentID].push(localID);
|
||||
objsByNewParent.push(localID);
|
||||
}
|
||||
|
||||
if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly && (this.agent.localID !== 0 && o.ParentID !== this.agent.localID))
|
||||
@@ -331,9 +348,12 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
else
|
||||
{
|
||||
if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects[o.ParentID])
|
||||
if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects.has(o.ParentID))
|
||||
{
|
||||
this.requestMissingObject(o.ParentID);
|
||||
this.requestMissingObject(o.ParentID).catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
if (compressedflags & CompressedFlags.Tree)
|
||||
{
|
||||
@@ -420,6 +440,12 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
const textureEntryLength = buf.readUInt32LE(pos);
|
||||
pos = pos + 4;
|
||||
o.TextureEntry = TextureEntry.from(buf.slice(pos, pos + textureEntryLength));
|
||||
const override = this.cachedMaterialOverrides.get(o.ID);
|
||||
if (override)
|
||||
{
|
||||
o.TextureEntry.gltfMaterialOverrides = override;
|
||||
this.cachedMaterialOverrides.delete(o.ID);
|
||||
}
|
||||
pos = pos + textureEntryLength;
|
||||
|
||||
if (compressedflags & CompressedFlags.TextureAnimation)
|
||||
@@ -432,7 +458,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0;
|
||||
if (o.IsAttachment && o.State !== undefined)
|
||||
{
|
||||
this.objects[localID].attachmentPoint = this.decodeAttachPoint(o.State);
|
||||
o.attachmentPoint = this.decodeAttachPoint(o.State);
|
||||
}
|
||||
|
||||
this.insertIntoRtree(o);
|
||||
@@ -456,37 +482,38 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
let pos = 0;
|
||||
const localID = objectData.Data.readUInt32LE(pos);
|
||||
pos = pos + 4;
|
||||
if (this.objects[localID])
|
||||
const o = this.objects.get(localID);
|
||||
if (o)
|
||||
{
|
||||
this.objects[localID].State = objectData.Data.readUInt8(pos++);
|
||||
o.State = objectData.Data.readUInt8(pos++);
|
||||
const avatar: boolean = (objectData.Data.readUInt8(pos++) !== 0);
|
||||
if (avatar)
|
||||
{
|
||||
this.objects[localID].CollisionPlane = new Vector4(objectData.Data, pos);
|
||||
o.CollisionPlane = new Vector4(objectData.Data, pos);
|
||||
pos += 16;
|
||||
}
|
||||
this.objects[localID].Position = new Vector3(objectData.Data, pos);
|
||||
o.Position = new Vector3(objectData.Data, pos);
|
||||
pos += 12;
|
||||
this.objects[localID].Velocity = new Vector3([
|
||||
o.Velocity = new Vector3([
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -128.0, 128.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -128.0, 128.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -128.0, 128.0)
|
||||
]);
|
||||
pos += 6;
|
||||
this.objects[localID].Acceleration = new Vector3([
|
||||
o.Acceleration = new Vector3([
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -64.0, 64.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -64.0, 64.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -64.0, 64.0)
|
||||
]);
|
||||
pos += 6;
|
||||
this.objects[localID].Rotation = new Quaternion([
|
||||
o.Rotation = new Quaternion([
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -1.0, 1.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -1.0, 1.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -1.0, 1.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 6), -1.0, 1.0)
|
||||
]);
|
||||
pos += 8;
|
||||
this.objects[localID].AngularVelocity = new Vector3([
|
||||
o.AngularVelocity = new Vector3([
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -64.0, 64.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -64.0, 64.0),
|
||||
Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -64.0, 64.0)
|
||||
@@ -496,11 +523,17 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
if (objectData.TextureEntry.length > 0)
|
||||
{
|
||||
// No idea why the first four bytes are skipped here.
|
||||
this.objects[localID].TextureEntry = TextureEntry.from(objectData.TextureEntry.slice(4));
|
||||
this.objects[localID].onTextureUpdate.next();
|
||||
o.TextureEntry = TextureEntry.from(objectData.TextureEntry.slice(4));
|
||||
const override = this.cachedMaterialOverrides.get(o.ID);
|
||||
if (override)
|
||||
{
|
||||
o.TextureEntry.gltfMaterialOverrides = override;
|
||||
this.cachedMaterialOverrides.delete(o.ID);
|
||||
}
|
||||
this.insertIntoRtree(this.objects[localID]);
|
||||
this.notifyTerseUpdate(this.objects[localID]);
|
||||
o.onTextureUpdate.next();
|
||||
}
|
||||
this.insertIntoRtree(o);
|
||||
this.notifyTerseUpdate(o);
|
||||
|
||||
}
|
||||
else
|
||||
|
||||
@@ -40,22 +40,54 @@ import Timer = NodeJS.Timer;
|
||||
import { GenericStreamingMessageMessage } from './messages/GenericStreamingMessage';
|
||||
import { LLSDNotationParser } from './llsd/LLSDNotationParser';
|
||||
import { LLSDMap } from './llsd/LLSDMap';
|
||||
import { LLGLTFMaterialOverride, LLGLTFTextureTransformOverride } from './LLGLTFMaterialOverride';
|
||||
import * as Long from 'long';
|
||||
|
||||
export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
protected circuit?: Circuit;
|
||||
protected agent: Agent;
|
||||
protected objects: { [key: number]: GameObject } = {};
|
||||
protected objectsByUUID: { [key: string]: number } = {};
|
||||
protected objectsByParent: { [key: number]: number[] } = {};
|
||||
protected objects = new Map<number, GameObject>();
|
||||
protected objectsByUUID = new Map<string, number>();
|
||||
protected objectsByParent = new Map<number, number[]>();
|
||||
protected clientEvents: ClientEvents;
|
||||
protected options: BotOptionFlags;
|
||||
protected requestedObjects: { [key: number]: boolean } = {};
|
||||
protected fullStore = false;
|
||||
protected requestedObjects = new Set<number>();
|
||||
protected deadObjects: number[] = [];
|
||||
protected persist = false;
|
||||
protected pendingObjectProperties: { [key: string]: any } = {};
|
||||
protected cachedMaterialOverrides = new Map<number, Map<number, LLGLTFMaterialOverride>>();
|
||||
protected pendingObjectProperties = new Map<string, {
|
||||
ObjectID: UUID;
|
||||
CreatorID: UUID;
|
||||
OwnerID: UUID;
|
||||
GroupID: UUID;
|
||||
CreationDate: Long;
|
||||
BaseMask: number;
|
||||
OwnerMask: number;
|
||||
GroupMask: number;
|
||||
EveryoneMask: number;
|
||||
NextOwnerMask: number;
|
||||
OwnershipCost: number;
|
||||
SaleType: number;
|
||||
SalePrice: number;
|
||||
AggregatePerms: number;
|
||||
AggregatePermTextures: number;
|
||||
AggregatePermTexturesOwner: number;
|
||||
Category: number;
|
||||
InventorySerial: number;
|
||||
ItemID: UUID;
|
||||
FolderID: UUID;
|
||||
FromTaskID: UUID;
|
||||
LastOwnerID: UUID;
|
||||
Name: Buffer;
|
||||
Description: Buffer;
|
||||
TouchName: Buffer;
|
||||
SitName: Buffer;
|
||||
TextureID: Buffer;
|
||||
}>;
|
||||
private physicsSubscription: Subscription;
|
||||
private selectedPrimsWithoutUpdate: { [key: number]: boolean } = {};
|
||||
private selectedPrimsWithoutUpdate = new Map<number, boolean>();
|
||||
private selectedChecker?: Timer;
|
||||
|
||||
rtree?: RBush3D;
|
||||
@@ -64,6 +96,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
agent.localID = 0;
|
||||
this.options = options;
|
||||
this.fullStore = false;
|
||||
this.clientEvents = clientEvents;
|
||||
this.circuit = circuit;
|
||||
this.agent = agent;
|
||||
@@ -81,6 +114,10 @@ export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
case Message.GenericStreamingMessage:
|
||||
{
|
||||
if (!this.fullStore)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const genMsg = packet.message as GenericStreamingMessageMessage;
|
||||
if (genMsg.MethodData.Method === 0x4175)
|
||||
{
|
||||
@@ -88,16 +125,141 @@ export class ObjectStoreLite implements IObjectStore
|
||||
const result = LLSDNotationParser.parse(genMsg.DataBlock.Data.toString('utf-8'));
|
||||
if (result instanceof LLSDMap)
|
||||
{
|
||||
const arr = result.get('te');
|
||||
if (Array.isArray(arr))
|
||||
{
|
||||
if (arr.length === 0)
|
||||
const localID = result.get('id');
|
||||
if (typeof localID !== 'number')
|
||||
{
|
||||
return;
|
||||
}
|
||||
const tes = result.get('te');
|
||||
const ods = result.get('od');
|
||||
|
||||
// TODO: figure out what to do with this..
|
||||
console.log(JSON.stringify(result, null, 4));
|
||||
const overrides = new Map<number, LLGLTFMaterialOverride>();
|
||||
|
||||
if (Array.isArray(tes) && Array.isArray(ods) && tes.length === ods.length)
|
||||
{
|
||||
for (let x = 0; x < tes.length; x++)
|
||||
{
|
||||
const te = tes[x];
|
||||
if (typeof te !== 'number')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const params = ods[x];
|
||||
if (!(params instanceof LLSDMap))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const textureIDs = params.get('tex');
|
||||
const baseColor = params.get('bc');
|
||||
const emissiveColor = params.get('ec');
|
||||
const metallicFactor = params.get('mf');
|
||||
const roughnessFactor = params.get('rf');
|
||||
const alphaMode = params.get('am');
|
||||
const alphaCutoff = params.get('ac');
|
||||
const doubleSided = params.get('ds');
|
||||
const textureTransforms = params.get('ti');
|
||||
|
||||
const override = new LLGLTFMaterialOverride();
|
||||
overrides.set(te, override);
|
||||
|
||||
if (textureIDs !== undefined && Array.isArray(textureIDs) && textureIDs.length === 4)
|
||||
{
|
||||
override.textures = [];
|
||||
for (const tex of textureIDs)
|
||||
{
|
||||
if (typeof tex === 'string')
|
||||
{
|
||||
override.textures.push(tex);
|
||||
}
|
||||
else if (tex instanceof UUID)
|
||||
{
|
||||
override.textures.push(tex.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isNumberArray(array: unknown[]): array is number[]
|
||||
{
|
||||
return array.every(element => typeof element === 'number');
|
||||
}
|
||||
|
||||
if (baseColor !== undefined && Array.isArray(baseColor) && baseColor.length === 4 && isNumberArray(baseColor))
|
||||
{
|
||||
override.baseColor = baseColor;
|
||||
}
|
||||
|
||||
if (emissiveColor !== undefined && Array.isArray(emissiveColor) && emissiveColor.length === 3 && isNumberArray(emissiveColor))
|
||||
{
|
||||
override.emissiveColor = emissiveColor;
|
||||
}
|
||||
|
||||
if (metallicFactor !== undefined && typeof metallicFactor === 'number')
|
||||
{
|
||||
override.metallicFactor = metallicFactor;
|
||||
}
|
||||
|
||||
if (roughnessFactor !== undefined && typeof roughnessFactor === 'number')
|
||||
{
|
||||
override.roughnessFactor = roughnessFactor;
|
||||
}
|
||||
|
||||
if (alphaMode !== undefined && typeof alphaMode === 'number')
|
||||
{
|
||||
override.alphaMode = alphaMode;
|
||||
}
|
||||
|
||||
if (alphaCutoff !== undefined && typeof alphaCutoff === 'number')
|
||||
{
|
||||
override.alphaCutoff = alphaCutoff;
|
||||
}
|
||||
|
||||
if (doubleSided !== undefined && typeof doubleSided === 'boolean')
|
||||
{
|
||||
override.doubleSided = doubleSided;
|
||||
}
|
||||
|
||||
function isLLGLTFTextureTransformOverride(objToCheck: unknown): objToCheck is LLGLTFTextureTransformOverride
|
||||
{
|
||||
const isArrayOfNumbers = (value: unknown): value is number[] =>
|
||||
{
|
||||
return Array.isArray(value) && value.every(item => typeof item === 'number');
|
||||
};
|
||||
|
||||
// Validate the object structure and types
|
||||
return (
|
||||
typeof objToCheck === 'object' &&
|
||||
objToCheck !== null &&
|
||||
'offset' in objToCheck && isArrayOfNumbers((objToCheck as LLGLTFTextureTransformOverride).offset) &&
|
||||
'scale' in objToCheck && isArrayOfNumbers((objToCheck as LLGLTFTextureTransformOverride).scale) &&
|
||||
'rotation' in objToCheck && typeof (objToCheck as LLGLTFTextureTransformOverride).rotation === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
if (textureTransforms !== undefined && Array.isArray(textureTransforms) && textureTransforms.length === 4)
|
||||
{
|
||||
override.textureTransforms = [];
|
||||
for (const transform of textureTransforms)
|
||||
{
|
||||
if (isLLGLTFTextureTransformOverride(transform))
|
||||
{
|
||||
override.textureTransforms.push(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const obj = this.objects.get(localID);
|
||||
const textureEntry = obj?.TextureEntry;
|
||||
if (textureEntry)
|
||||
{
|
||||
textureEntry.gltfMaterialOverrides = overrides;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.cachedMaterialOverrides.set(localID, overrides);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,15 +270,15 @@ export class ObjectStoreLite implements IObjectStore
|
||||
const objProp = packet.message as ObjectPropertiesMessage;
|
||||
for (const obj of objProp.ObjectData)
|
||||
{
|
||||
const obje = this.objectsByUUID[obj.ObjectID.toString()];
|
||||
if (obje !== undefined && this.objects[obje] !== undefined)
|
||||
const obje = this.objectsByUUID.get(obj.ObjectID.toString());
|
||||
const o = this.objects.get(obje ?? 0);
|
||||
if (obje !== undefined && o !== undefined)
|
||||
{
|
||||
const o = this.objects[obje];
|
||||
this.applyObjectProperties(o, obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.pendingObjectProperties[obj.ObjectID.toString()] = obj;
|
||||
this.pendingObjectProperties.set(obj.ObjectID.toString(), obj);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -156,13 +318,14 @@ export class ObjectStoreLite implements IObjectStore
|
||||
|
||||
this.physicsSubscription = this.clientEvents.onPhysicsDataEvent.subscribe((evt: ObjectPhysicsDataEvent) =>
|
||||
{
|
||||
if (this.objects[evt.localID])
|
||||
const obj = this.objects.get(evt.localID);
|
||||
if (obj)
|
||||
{
|
||||
this.objects[evt.localID].physicsShapeType = evt.physicsShapeType;
|
||||
this.objects[evt.localID].density = evt.density;
|
||||
this.objects[evt.localID].restitution = evt.restitution;
|
||||
this.objects[evt.localID].gravityMultiplier = evt.gravityMultiplier;
|
||||
this.objects[evt.localID].friction = evt.friction;
|
||||
obj.physicsShapeType = evt.physicsShapeType;
|
||||
obj.density = evt.density;
|
||||
obj.restitution = evt.restitution;
|
||||
obj.gravityMultiplier = evt.gravityMultiplier;
|
||||
obj.friction = evt.friction;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -224,10 +387,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
|
||||
private applyObjectProperties(o: GameObject, obj: any): void
|
||||
{
|
||||
if (this.selectedPrimsWithoutUpdate[o.ID])
|
||||
{
|
||||
delete this.selectedPrimsWithoutUpdate[o.ID];
|
||||
}
|
||||
this.selectedPrimsWithoutUpdate.delete(o.ID);
|
||||
// const n = Utils.BufferToStringSimple(obj.Name); // Currently unused
|
||||
o.creatorID = obj.CreatorID;
|
||||
o.creationDate = obj.CreationDate;
|
||||
@@ -278,7 +438,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
|
||||
protected async requestMissingObject(localID: number, attempt = 0): Promise<void>
|
||||
{
|
||||
if (this.requestedObjects[localID])
|
||||
if (this.requestedObjects.has(localID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -286,7 +446,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.requestedObjects[localID] = true;
|
||||
this.requestedObjects.add(localID);
|
||||
const rmo = new RequestMultipleObjectsMessage();
|
||||
rmo.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
@@ -324,11 +484,11 @@ export class ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
return FilterResponse.NoMatch;
|
||||
});
|
||||
delete this.requestedObjects[localID];
|
||||
this.requestedObjects.delete(localID);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
delete this.requestedObjects[localID];
|
||||
this.requestedObjects.delete(localID);
|
||||
if (attempt < 5)
|
||||
{
|
||||
await this.requestMissingObject(localID, ++attempt);
|
||||
@@ -371,17 +531,19 @@ export class ObjectStoreLite implements IObjectStore
|
||||
let addToParentList = true;
|
||||
let newObject = false;
|
||||
|
||||
if (this.objects[localID])
|
||||
let obj = this.objects.get(localID);
|
||||
if (obj)
|
||||
{
|
||||
if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID])
|
||||
const p = this.objectsByParent.get(parentID);
|
||||
if (obj.ParentID !== parentID && p !== undefined)
|
||||
{
|
||||
const ind = this.objectsByParent[parentID].indexOf(localID);
|
||||
const ind = p.indexOf(localID);
|
||||
if (ind !== -1)
|
||||
{
|
||||
this.objectsByParent[parentID].splice(ind, 1);
|
||||
p.splice(ind, 1);
|
||||
}
|
||||
}
|
||||
else if (this.objectsByParent[parentID])
|
||||
else if (p)
|
||||
{
|
||||
addToParentList = false;
|
||||
}
|
||||
@@ -389,27 +551,28 @@ export class ObjectStoreLite implements IObjectStore
|
||||
else
|
||||
{
|
||||
newObject = true;
|
||||
this.objects[localID] = new GameObject();
|
||||
this.objects[localID].region = this.agent.currentRegion;
|
||||
const newObj = new GameObject();
|
||||
newObj.region = this.agent.currentRegion;
|
||||
this.objects.set(localID, newObj);
|
||||
}
|
||||
|
||||
const obj = this.objects[localID];
|
||||
obj.deleted = false;
|
||||
obj.ID = objData.ID;
|
||||
obj.FullID = objData.FullID;
|
||||
obj.ParentID = objData.ParentID;
|
||||
obj.OwnerID = objData.OwnerID;
|
||||
obj.PCode = objData.PCode;
|
||||
obj = this.objects.get(localID);
|
||||
obj!.deleted = false;
|
||||
obj!.ID = objData.ID;
|
||||
obj!.FullID = objData.FullID;
|
||||
obj!.ParentID = objData.ParentID;
|
||||
obj!.OwnerID = objData.OwnerID;
|
||||
obj!.PCode = objData.PCode;
|
||||
|
||||
this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue));
|
||||
obj!.NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue));
|
||||
|
||||
this.objects[localID].IsAttachment = this.objects[localID].NameValue['AttachItemID'] !== undefined;
|
||||
if (obj.IsAttachment && obj.State !== undefined)
|
||||
obj!.IsAttachment = obj!.NameValue.AttachItemID !== undefined;
|
||||
if (obj!.IsAttachment && obj!.State !== undefined)
|
||||
{
|
||||
this.objects[localID].attachmentPoint = this.decodeAttachPoint(obj.State);
|
||||
obj!.attachmentPoint = this.decodeAttachPoint(obj!.State);
|
||||
}
|
||||
|
||||
if (objData.PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString())
|
||||
if (objData.PCode === PCode.Avatar && obj!.FullID.toString() === this.agent.agentID.toString())
|
||||
{
|
||||
this.agent.localID = localID;
|
||||
|
||||
@@ -421,21 +584,25 @@ export class ObjectStoreLite implements IObjectStore
|
||||
if (parent !== this.agent.localID)
|
||||
{
|
||||
let foundAvatars = false;
|
||||
for (const objID of this.objectsByParent[parent])
|
||||
const p = this.objectsByParent.get(parent);
|
||||
if (p !== undefined)
|
||||
{
|
||||
if (this.objects[objID])
|
||||
for (const objID of p)
|
||||
{
|
||||
const o = this.objects[objID];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
const childObj = this.objects.get(objID);
|
||||
if (childObj)
|
||||
{
|
||||
if (childObj.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.objects[parent])
|
||||
}
|
||||
const parentObj = this.objects.get(parent);
|
||||
if (parentObj)
|
||||
{
|
||||
const o = this.objects[parent];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
if (parentObj.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
@@ -449,19 +616,21 @@ export class ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
}
|
||||
|
||||
this.objectsByUUID[objData.FullID.toString()] = localID;
|
||||
if (!this.objectsByParent[parentID])
|
||||
this.objectsByUUID.set(objData.FullID.toString(), localID);
|
||||
let objByParent = this.objectsByParent.get(parentID);
|
||||
if (!objByParent)
|
||||
{
|
||||
this.objectsByParent[parentID] = [];
|
||||
objByParent = [];
|
||||
this.objectsByParent.set(parentID, objByParent);
|
||||
}
|
||||
if (addToParentList)
|
||||
{
|
||||
this.objectsByParent[parentID].push(localID);
|
||||
objByParent.push(localID);
|
||||
}
|
||||
|
||||
if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
if (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID)
|
||||
if (this.agent.localID !== 0 && obj!.ParentID !== this.agent.localID)
|
||||
{
|
||||
// Drop object
|
||||
this.deleteObject(localID);
|
||||
@@ -469,9 +638,9 @@ export class ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
}
|
||||
|
||||
this.notifyObjectUpdate(newObject, obj);
|
||||
this.notifyObjectUpdate(newObject, obj!);
|
||||
|
||||
if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects[objData.ParentID])
|
||||
if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects.get(objData.ParentID))
|
||||
{
|
||||
this.requestMissingObject(objData.ParentID);
|
||||
}
|
||||
@@ -480,7 +649,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
|
||||
protected notifyTerseUpdate(obj: GameObject): void
|
||||
{
|
||||
if (this.objects[obj.ID])
|
||||
if (this.objects.get(obj.ID))
|
||||
{
|
||||
if (obj.PCode === PCode.Avatar)
|
||||
{
|
||||
@@ -531,15 +700,16 @@ export class ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj.ParentID === 0 || (obj.ParentID !== undefined && this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar))
|
||||
const parentObj = this.objects.get(obj.ParentID ?? 0);
|
||||
if (obj.ParentID === 0 || (obj.ParentID !== undefined && parentObj !== undefined && parentObj.PCode === PCode.Avatar))
|
||||
{
|
||||
if (newObject)
|
||||
{
|
||||
if (obj.IsAttachment && obj.ParentID !== undefined)
|
||||
{
|
||||
if (this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar)
|
||||
if (parentObj !== undefined && parentObj.PCode === PCode.Avatar)
|
||||
{
|
||||
const avatar = this.agent.currentRegion.agents[this.objects[obj.ParentID].FullID.toString()];
|
||||
const avatar = this.agent.currentRegion.agents[parentObj.FullID.toString()];
|
||||
|
||||
let invItemID = UUID.zero();
|
||||
if (obj.NameValue['AttachItemID'])
|
||||
@@ -581,9 +751,9 @@ export class ObjectStoreLite implements IObjectStore
|
||||
obj.createdSelected = newObj.createSelected;
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
// noinspection JSBitwiseOperatorUsage
|
||||
if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties[obj.FullID.toString()])
|
||||
if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties.get(obj.FullID.toString()))
|
||||
{
|
||||
this.selectedPrimsWithoutUpdate[obj.ID] = true;
|
||||
this.selectedPrimsWithoutUpdate.set(obj.ID, true);
|
||||
}
|
||||
this.clientEvents.onNewObjectEvent.next(newObj);
|
||||
}
|
||||
@@ -595,10 +765,11 @@ export class ObjectStoreLite implements IObjectStore
|
||||
updObj.object = obj;
|
||||
this.clientEvents.onObjectUpdatedEvent.next(updObj);
|
||||
}
|
||||
if (this.pendingObjectProperties[obj.FullID.toString()])
|
||||
const pendingProp = this.pendingObjectProperties.get(obj.FullID.toString());
|
||||
if (pendingProp)
|
||||
{
|
||||
this.applyObjectProperties(obj, this.pendingObjectProperties[obj.FullID.toString()]);
|
||||
delete this.pendingObjectProperties[obj.FullID.toString()];
|
||||
this.applyObjectProperties(obj, pendingProp);
|
||||
this.pendingObjectProperties.delete(obj.FullID.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -637,18 +808,18 @@ export class ObjectStoreLite implements IObjectStore
|
||||
const localID = buf.readUInt32LE(pos);
|
||||
pos += 4;
|
||||
const pcode = buf.readUInt8(pos++);
|
||||
let newObj = false;
|
||||
if (!this.objects[localID])
|
||||
const newObj = false;
|
||||
let o = this.objects.get(localID);
|
||||
if (!o)
|
||||
{
|
||||
newObj = true;
|
||||
this.objects[localID] = new GameObject();
|
||||
this.objects[localID].region = this.agent.currentRegion;
|
||||
o = new GameObject();
|
||||
o.region = this.agent.currentRegion;
|
||||
this.objects.set(localID, o);
|
||||
}
|
||||
const o = this.objects[localID];
|
||||
o.deleted = false;
|
||||
o.ID = localID;
|
||||
o.PCode = pcode;
|
||||
this.objectsByUUID[fullID.toString()] = localID;
|
||||
this.objectsByUUID.set(fullID.toString(), localID);
|
||||
o.FullID = fullID;
|
||||
|
||||
|
||||
@@ -675,26 +846,29 @@ export class ObjectStoreLite implements IObjectStore
|
||||
let add = true;
|
||||
if (!newObj && o.ParentID !== undefined)
|
||||
{
|
||||
if (newParentID !== o.ParentID)
|
||||
const p = this.objectsByParent.get(o.ParentID);
|
||||
if (newParentID !== o.ParentID && p)
|
||||
{
|
||||
const index = this.objectsByParent[o.ParentID].indexOf(localID);
|
||||
if (index !== -1)
|
||||
const ind = p.indexOf(localID);
|
||||
if (ind !== -1)
|
||||
{
|
||||
this.objectsByParent[o.ParentID].splice(index, 1);
|
||||
p.splice(ind, 1);
|
||||
}
|
||||
}
|
||||
else if (this.objectsByParent[o.ParentID])
|
||||
else if (p)
|
||||
{
|
||||
add = false;
|
||||
}
|
||||
}
|
||||
if (add)
|
||||
{
|
||||
if (!this.objectsByParent[newParentID])
|
||||
let objByParent = this.objectsByParent.get(newParentID);
|
||||
if (!objByParent)
|
||||
{
|
||||
this.objectsByParent[newParentID] = [];
|
||||
objByParent = [];
|
||||
this.objectsByParent.set(newParentID, objByParent);
|
||||
}
|
||||
this.objectsByParent[newParentID].push(localID);
|
||||
objByParent.push(localID);
|
||||
}
|
||||
if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
@@ -705,9 +879,12 @@ export class ObjectStoreLite implements IObjectStore
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects[o.ParentID])
|
||||
if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects.has(o.ParentID))
|
||||
{
|
||||
this.requestMissingObject(o.ParentID);
|
||||
this.requestMissingObject(o.ParentID).catch((e) =>
|
||||
{
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
if (compressedflags & CompressedFlags.Tree)
|
||||
{
|
||||
@@ -789,7 +966,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
for (const obj of killObj.ObjectData)
|
||||
{
|
||||
const objectID = obj.ID;
|
||||
if (this.objects[objectID])
|
||||
if (this.objects.has(objectID))
|
||||
{
|
||||
this.deleteObject(objectID);
|
||||
}
|
||||
@@ -811,10 +988,10 @@ export class ObjectStoreLite implements IObjectStore
|
||||
|
||||
deleteObject(objectID: number): void
|
||||
{
|
||||
if (this.objects[objectID])
|
||||
const obj = this.objects.get(objectID);
|
||||
if (obj)
|
||||
{
|
||||
const objectUUID = this.objects[objectID].FullID;
|
||||
const obj = this.objects[objectID];
|
||||
const objectUUID = obj.FullID;
|
||||
obj.deleted = true;
|
||||
|
||||
if (this.persist)
|
||||
@@ -825,9 +1002,10 @@ export class ObjectStoreLite implements IObjectStore
|
||||
|
||||
if (obj.IsAttachment && obj.ParentID !== undefined)
|
||||
{
|
||||
if (this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar)
|
||||
const parent = this.objects.get(obj.ParentID);
|
||||
if (parent !== undefined && parent.PCode === PCode.Avatar)
|
||||
{
|
||||
this.agent.currentRegion.agents[this.objects[obj.ParentID].FullID.toString()]?.removeAttachment(obj);
|
||||
this.agent.currentRegion.agents[parent.FullID.toString()]?.removeAttachment(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,31 +1015,31 @@ export class ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
|
||||
// First, kill all children (not the people kind)
|
||||
if (this.objectsByParent[objectID])
|
||||
const objsByParent = this.objectsByParent.get(objectID);
|
||||
if (objsByParent)
|
||||
{
|
||||
for (const childObjID of this.objectsByParent[objectID])
|
||||
for (const childObjID of objsByParent)
|
||||
{
|
||||
this.deleteObject(childObjID);
|
||||
}
|
||||
}
|
||||
delete this.objectsByParent[objectID];
|
||||
this.objectsByParent.delete(objectID);
|
||||
|
||||
// Now delete this object
|
||||
const uuid = obj.FullID.toString();
|
||||
|
||||
if (this.objectsByUUID[uuid])
|
||||
{
|
||||
delete this.objectsByUUID[uuid];
|
||||
}
|
||||
this.objectsByUUID.delete(uuid);
|
||||
|
||||
if (obj.ParentID !== undefined)
|
||||
{
|
||||
const parentID = obj.ParentID;
|
||||
if (this.objectsByParent[parentID])
|
||||
const objsByParentParent = this.objectsByParent.get(parentID);
|
||||
if (objsByParentParent)
|
||||
{
|
||||
const ind = this.objectsByParent[parentID].indexOf(objectID);
|
||||
const ind = objsByParentParent.indexOf(objectID);
|
||||
if (ind !== -1)
|
||||
{
|
||||
this.objectsByParent[parentID].splice(ind, 1);
|
||||
objsByParentParent.splice(ind, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -869,12 +1047,13 @@ export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
this.rtree.remove(obj.rtreeEntry);
|
||||
}
|
||||
delete this.objects[objectID];
|
||||
this.objects.delete(objectID);
|
||||
this.cachedMaterialOverrides.delete(objectID);
|
||||
}
|
||||
}
|
||||
getObjectsByParent(parentID: number): GameObject[]
|
||||
{
|
||||
const list = this.objectsByParent[parentID];
|
||||
const list = this.objectsByParent.get(parentID);
|
||||
if (list === undefined)
|
||||
{
|
||||
return [];
|
||||
@@ -882,9 +1061,10 @@ export class ObjectStoreLite implements IObjectStore
|
||||
const result: GameObject[] = [];
|
||||
for (const localID of list)
|
||||
{
|
||||
if (this.objects[localID])
|
||||
const obj = this.objects.get(localID);
|
||||
if (obj)
|
||||
{
|
||||
result.push(this.objects[localID]);
|
||||
result.push(obj);
|
||||
}
|
||||
}
|
||||
result.sort((a: GameObject, b: GameObject) =>
|
||||
@@ -933,25 +1113,26 @@ export class ObjectStoreLite implements IObjectStore
|
||||
delete this.selectedChecker;
|
||||
}
|
||||
this.physicsSubscription.unsubscribe();
|
||||
this.objects = {};
|
||||
this.objects.clear();
|
||||
if (this.rtree)
|
||||
{
|
||||
this.rtree.clear();
|
||||
}
|
||||
this.objectsByUUID = {};
|
||||
this.objectsByParent = {};
|
||||
this.objectsByUUID.clear();
|
||||
this.objectsByParent.clear()
|
||||
delete this.circuit;
|
||||
}
|
||||
|
||||
protected findParent(go: GameObject): GameObject
|
||||
{
|
||||
if (go.ParentID !== undefined && go.ParentID !== 0 && this.objects[go.ParentID])
|
||||
const parentObj = this.objects.get(go.ParentID ?? 0);
|
||||
if (go.ParentID !== undefined && go.ParentID !== 0 && parentObj)
|
||||
{
|
||||
return this.findParent(this.objects[go.ParentID]);
|
||||
return this.findParent(parentObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (go.ParentID !== undefined && go.ParentID !== 0 && !this.objects[go.ParentID])
|
||||
if (go.ParentID !== undefined && go.ParentID !== 0 && !parentObj)
|
||||
{
|
||||
this.requestMissingObject(go.ParentID).catch((e: unknown) =>
|
||||
{
|
||||
@@ -988,10 +1169,10 @@ export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
const results = [];
|
||||
const found: { [key: string]: GameObject } = {};
|
||||
for (const k of Object.keys(this.objects))
|
||||
for (const localID of this.objects.keys())
|
||||
{
|
||||
const go = this.objects[parseInt(k, 10)];
|
||||
if (go.PCode !== PCode.Avatar && (go.IsAttachment === undefined || !go.IsAttachment))
|
||||
const go = this.objects.get(localID);
|
||||
if (go && go.PCode !== PCode.Avatar && (go.IsAttachment === undefined || !go.IsAttachment))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1091,21 +1272,23 @@ export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
fullID = fullID.toString();
|
||||
}
|
||||
if (!this.objectsByUUID[fullID])
|
||||
const localID = this.objectsByUUID.get(fullID);
|
||||
const go = this.objects.get(localID ?? 0);
|
||||
if (localID === undefined || go === undefined)
|
||||
{
|
||||
throw new Error('No object found with that UUID');
|
||||
}
|
||||
const localID: number = this.objectsByUUID[fullID];
|
||||
return this.objects[localID];
|
||||
return go;
|
||||
}
|
||||
|
||||
getObjectByLocalID(localID: number): GameObject
|
||||
{
|
||||
if (!this.objects[localID])
|
||||
const go = this.objects.get(localID);
|
||||
if (!go)
|
||||
{
|
||||
throw new Error('No object found with that UUID');
|
||||
}
|
||||
return this.objects[localID];
|
||||
return go;
|
||||
}
|
||||
|
||||
insertIntoRtree(obj: GameObject): void
|
||||
|
||||
@@ -2,12 +2,14 @@ import { TextureEntryFace } from './TextureEntryFace';
|
||||
import { UUID } from './UUID';
|
||||
import { Color4 } from './Color4';
|
||||
import { Utils } from './Utils';
|
||||
import { LLGLTFMaterialOverride } from './LLGLTFMaterialOverride';
|
||||
|
||||
export class TextureEntry
|
||||
{
|
||||
static MAX_UINT32 = 4294967295;
|
||||
defaultTexture: TextureEntryFace | null;
|
||||
faces: TextureEntryFace[] = [];
|
||||
public defaultTexture: TextureEntryFace | null;
|
||||
public faces: TextureEntryFace[] = [];
|
||||
public gltfMaterialOverrides = new Map<number, LLGLTFMaterialOverride>()
|
||||
|
||||
static readFaceBitfield(buf: Buffer, pos: number): {
|
||||
result: boolean,
|
||||
|
||||
@@ -196,125 +196,6 @@ export class LLSDNotationParser
|
||||
{
|
||||
return generator.next().value;
|
||||
}
|
||||
const data = this.parseValueToken(getToken);
|
||||
do
|
||||
{
|
||||
const t = getToken();
|
||||
if (t === undefined)
|
||||
{
|
||||
return data;
|
||||
return this.parseValueToken(getToken);
|
||||
}
|
||||
if (t.type !== LLSDTokenType.WHITESPACE)
|
||||
{
|
||||
throw new Error('Unexpected token at end of document: ' + t.value);
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
/*
|
||||
private static readObject(cont: LLSDParserContainer): LLSDType
|
||||
{
|
||||
let tokenType: LLSDTokenType | null = null;
|
||||
const stack: LLSDObject[] = [];
|
||||
while (cont.pos < cont.str.length)
|
||||
{
|
||||
const subString = cont.str.slice(cont.pos);
|
||||
let value: string | null = null;
|
||||
for (const { regex, type } of this.tokenSpecs)
|
||||
{
|
||||
const tokenMatch = subString.match(regex);
|
||||
if (tokenMatch)
|
||||
{
|
||||
value = tokenMatch[0];
|
||||
tokenType = type;
|
||||
if (tokenMatch.length > 1)
|
||||
{
|
||||
value = tokenMatch[tokenMatch.length - 1];
|
||||
}
|
||||
cont.pos += tokenMatch[0].length; // Move past this token
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tokenType === null)
|
||||
{
|
||||
tokenType = LLSDTokenType.UNKNOWN;
|
||||
value = cont.str[cont.pos++];
|
||||
}
|
||||
if (stack.length > 0)
|
||||
{
|
||||
if (stack[stack.length - 1].acceptToken(tokenType, value))
|
||||
{
|
||||
// stack object completed
|
||||
}
|
||||
}
|
||||
switch (tokenType)
|
||||
{
|
||||
case LLSDTokenType.WHITESPACE:
|
||||
{
|
||||
continue;
|
||||
}
|
||||
case LLSDTokenType.UNKNOWN:
|
||||
{
|
||||
throw new Error('Unexpected token: ' + value);
|
||||
}
|
||||
case LLSDTokenType.NULL:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case LLSDTokenType.MAP_START:
|
||||
{
|
||||
|
||||
break;
|
||||
}
|
||||
case LLSDTokenType.MAP_END:
|
||||
break;
|
||||
case LLSDTokenType.COLON:
|
||||
break;
|
||||
case LLSDTokenType.COMMA:
|
||||
break;
|
||||
case LLSDTokenType.ARRAY_START:
|
||||
break;
|
||||
case LLSDTokenType.ARRAY_END:
|
||||
break;
|
||||
case LLSDTokenType.BOOLEAN:
|
||||
break;
|
||||
case LLSDTokenType.INTEGER:
|
||||
break;
|
||||
case LLSDTokenType.REAL:
|
||||
break;
|
||||
case LLSDTokenType.UUID:
|
||||
break;
|
||||
case LLSDTokenType.STRING_FIXED:
|
||||
break;
|
||||
case LLSDTokenType.STRING_DYNAMIC_START:
|
||||
break;
|
||||
case LLSDTokenType.URI:
|
||||
break;
|
||||
case LLSDTokenType.DATE:
|
||||
break;
|
||||
case LLSDTokenType.BINARY_STATIC:
|
||||
break;
|
||||
case LLSDTokenType.BINARY_DYNAMIC_START:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static parse(input: string): LLSDType
|
||||
{
|
||||
const cont: LLSDParserContainer = {
|
||||
str: input,
|
||||
pos: 0
|
||||
};
|
||||
|
||||
const token = this.readObject(cont);
|
||||
if (cont.pos < input.length)
|
||||
{
|
||||
throw new Error('Only one root object expected');
|
||||
}
|
||||
return token;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
157
lib/classes/public/GameObject.spec.ts
Normal file
157
lib/classes/public/GameObject.spec.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { GameObject } from './GameObject';
|
||||
import { TextureEntry } from '../TextureEntry';
|
||||
import { LLGLTFMaterialOverride } from '../LLGLTFMaterialOverride';
|
||||
import { TextureEntryFace } from '../TextureEntryFace';
|
||||
import { UUID } from '../UUID';
|
||||
import { Color4 } from '../Color4';
|
||||
import * as assert from 'assert';
|
||||
|
||||
describe('GameObject', () =>
|
||||
{
|
||||
it('Can serialize and unserialize GLTF override data to xml', async() =>
|
||||
{
|
||||
const go = new GameObject();
|
||||
const te = new TextureEntry();
|
||||
te.defaultTexture = new TextureEntryFace(null);
|
||||
te.defaultTexture.textureID = new UUID('3f268e01-7368-7002-e5fb-cd7af0a3931c');
|
||||
te.defaultTexture.rgba = new Color4(1.0, 1.0, 1.0, 1.0);
|
||||
te.defaultTexture.glow = 1.0;
|
||||
te.defaultTexture.rotation = 0.5;
|
||||
te.defaultTexture.media = 0;
|
||||
te.defaultTexture.mappingType = 0;
|
||||
te.defaultTexture.materialID = new UUID('00000000-0000-0000-0000-000000000000');
|
||||
go.TextureEntry = te;
|
||||
te.gltfMaterialOverrides = new Map<number, LLGLTFMaterialOverride>();
|
||||
const override = new LLGLTFMaterialOverride();
|
||||
te.gltfMaterialOverrides.set(0, override)
|
||||
override.doubleSided = true;
|
||||
override.alphaCutoff = 0.5;
|
||||
override.alphaMode = 2;
|
||||
override.roughnessFactor = 0.2;
|
||||
override.metallicFactor = 0.6;
|
||||
override.textures = [
|
||||
'c9eca1db-8f2f-4d53-930e-77e6f06d9f37',
|
||||
'ec3cfa77-bc40-4fc3-9e81-ce9d51e86b77',
|
||||
'1e079cce-eeca-4e05-9a4f-e58d8398ecf1',
|
||||
'03573ae5-4c1a-44f7-9cd3-8b49028f9e48'
|
||||
];
|
||||
override.emissiveColor = [0.2, 0.5, 0.6];
|
||||
override.baseColor = [0.1, 0.2, 0.3, 0.4];
|
||||
override.textureTransforms = [
|
||||
{
|
||||
offset: [0.1, 0.2],
|
||||
scale: [0.5, 0.5],
|
||||
rotation: 1
|
||||
},
|
||||
{
|
||||
offset: [0.1, 0.2],
|
||||
scale: [0.5, 0.5],
|
||||
rotation: 2
|
||||
},
|
||||
{
|
||||
offset: [0.1, 0.2],
|
||||
scale: [0.5, 0.5],
|
||||
rotation: 3
|
||||
},
|
||||
{
|
||||
offset: [0.1, 0.2],
|
||||
scale: [0.5, 0.5],
|
||||
rotation: 4
|
||||
}
|
||||
];
|
||||
te.gltfMaterialOverrides.set(1, override)
|
||||
|
||||
const xml = await go.exportXML();
|
||||
|
||||
const obj = await GameObject.fromXML(xml);
|
||||
assert.notEqual(obj, undefined);
|
||||
assert.notEqual(obj.TextureEntry, undefined);
|
||||
assert.notEqual(obj.TextureEntry?.gltfMaterialOverrides, undefined);
|
||||
const overrides = obj.TextureEntry?.gltfMaterialOverrides;
|
||||
if (overrides)
|
||||
{
|
||||
const entry = overrides.get(0);
|
||||
if (entry === undefined)
|
||||
{
|
||||
assert(false, 'Failed to get material override');
|
||||
}
|
||||
else
|
||||
{
|
||||
assert.equal(entry.doubleSided, true);
|
||||
assert.equal(entry.alphaCutoff, 0.5);
|
||||
assert.equal(entry.alphaMode, 2);
|
||||
assert.equal(entry.roughnessFactor, 0.2);
|
||||
assert.equal(entry.metallicFactor, 0.6);
|
||||
if (Array.isArray(entry.textures))
|
||||
{
|
||||
assert.equal(entry.textures.length, 4);
|
||||
assert.equal(entry.textures[0], 'c9eca1db-8f2f-4d53-930e-77e6f06d9f37');
|
||||
assert.equal(entry.textures[1], 'ec3cfa77-bc40-4fc3-9e81-ce9d51e86b77');
|
||||
assert.equal(entry.textures[2], '1e079cce-eeca-4e05-9a4f-e58d8398ecf1');
|
||||
assert.equal(entry.textures[3], '03573ae5-4c1a-44f7-9cd3-8b49028f9e48');
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Textures is not an array');
|
||||
}
|
||||
if (Array.isArray(entry.emissiveColor))
|
||||
{
|
||||
assert.equal(entry.emissiveColor.length, 3);
|
||||
assert.equal(entry.emissiveColor[0], 0.2);
|
||||
assert.equal(entry.emissiveColor[1], 0.5);
|
||||
assert.equal(entry.emissiveColor[2], 0.6);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'EmissiveColor is not an array');
|
||||
}
|
||||
if (Array.isArray(entry.baseColor))
|
||||
{
|
||||
assert.equal(entry.baseColor.length, 4);
|
||||
assert.equal(entry.baseColor[0], 0.1);
|
||||
assert.equal(entry.baseColor[1], 0.2);
|
||||
assert.equal(entry.baseColor[2], 0.3);
|
||||
assert.equal(entry.baseColor[3], 0.4);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'BaseColor is not an array');
|
||||
}
|
||||
if (Array.isArray(entry.textureTransforms))
|
||||
{
|
||||
assert.equal(entry.textureTransforms.length, 4);
|
||||
for (let x = 0; x < 4; x++)
|
||||
{
|
||||
const transform = entry.textureTransforms[x];
|
||||
if (Array.isArray(transform.scale))
|
||||
{
|
||||
assert.equal(transform.scale.length, 2);
|
||||
assert.equal(transform.scale[0], 0.5);
|
||||
assert.equal(transform.scale[1], 0.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Scale is not an array');
|
||||
}
|
||||
if (Array.isArray(transform.offset))
|
||||
{
|
||||
assert.equal(transform.offset.length, 2);
|
||||
assert.equal(transform.offset[0], 0.1);
|
||||
assert.equal(transform.offset[1], 0.2);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'Offset is not an array');
|
||||
}
|
||||
assert.equal(transform.rotation, x + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, 'BaseColor is not an array');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ import { Subject } from 'rxjs';
|
||||
import { RezScriptMessage } from '../messages/RezScript';
|
||||
import { PermissionMask } from '../../enums/PermissionMask';
|
||||
import { AssetType } from '../../enums/AssetType';
|
||||
import { LLGLTFMaterialOverride, LLGLTFTextureTransformOverride } from '../LLGLTFMaterialOverride';
|
||||
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
@@ -380,6 +381,125 @@ export class GameObject implements IGameObjectData
|
||||
const buf = Buffer.from(prop, 'base64');
|
||||
go.TextureEntry = TextureEntry.from(buf);
|
||||
}
|
||||
if (go.TextureEntry && shape['GLTFMaterialOverrides'] && Array.isArray(shape['GLTFMaterialOverrides']) && shape['GLTFMaterialOverrides'].length > 0)
|
||||
{
|
||||
const te = go.TextureEntry;
|
||||
te.gltfMaterialOverrides = new Map<number, LLGLTFMaterialOverride>();
|
||||
const children = shape['GLTFMaterialOverrides'][0];
|
||||
const childObj = children['GLTFMaterialOverride'];
|
||||
if (childObj)
|
||||
{
|
||||
for (const entry of childObj)
|
||||
{
|
||||
const override = new LLGLTFMaterialOverride();
|
||||
let textureEntry = 0;
|
||||
if ((prop = Utils.getFromXMLJS(entry, 'TextureEntry')) !== undefined)
|
||||
{
|
||||
textureEntry = prop;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((prop = Utils.getFromXMLJS(entry, 'DoubleSided')) !== undefined)
|
||||
{
|
||||
override.doubleSided = prop;
|
||||
}
|
||||
if ((prop = Utils.getFromXMLJS(entry, 'AlphaCutoff')) !== undefined)
|
||||
{
|
||||
override.alphaCutoff = parseFloat(prop);
|
||||
}
|
||||
if ((prop = Utils.getFromXMLJS(entry, 'RoughnessFactor')) !== undefined)
|
||||
{
|
||||
override.roughnessFactor = parseFloat(prop);
|
||||
}
|
||||
if ((prop = Utils.getFromXMLJS(entry, 'MetallicFactor')) !== undefined)
|
||||
{
|
||||
override.metallicFactor = parseFloat(prop);
|
||||
}
|
||||
if ((prop = Utils.getFromXMLJS(entry, 'AlphaMode')) !== undefined)
|
||||
{
|
||||
override.alphaMode = prop;
|
||||
}
|
||||
if (entry['Textures'] && Array.isArray(entry['Textures']) && entry['Textures'].length > 0)
|
||||
{
|
||||
override.textures = [];
|
||||
const childArr = entry['Textures'][0];
|
||||
for (const tex of childArr['UUID'])
|
||||
{
|
||||
override.textures.push(tex);
|
||||
}
|
||||
}
|
||||
if (entry['EmissiveColor'] && Array.isArray(entry['EmissiveColor']) && entry['EmissiveColor'].length > 0)
|
||||
{
|
||||
const childArr = entry['EmissiveColor'][0];
|
||||
const red = parseFloat(childArr['R'][0]);
|
||||
const green = parseFloat(childArr['G'][0]);
|
||||
const blue = parseFloat(childArr['B'][0]);
|
||||
override.emissiveColor = [
|
||||
red,
|
||||
green,
|
||||
blue
|
||||
];
|
||||
}
|
||||
if (entry['BaseColor'] && Array.isArray(entry['BaseColor']) && entry['BaseColor'].length > 0)
|
||||
{
|
||||
const childArr = entry['BaseColor'][0];
|
||||
const red = parseFloat(childArr['R'][0]);
|
||||
const green = parseFloat(childArr['G'][0]);
|
||||
const blue = parseFloat(childArr['B'][0]);
|
||||
const alpha = parseFloat(childArr['A'][0]);
|
||||
override.baseColor = [
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
alpha
|
||||
];
|
||||
}
|
||||
if (entry['TextureTransforms'] && Array.isArray(entry['TextureTransforms']) && entry['TextureTransforms'].length > 0)
|
||||
{
|
||||
const childArr = entry['TextureTransforms'][0];
|
||||
override.textureTransforms = [];
|
||||
for (const tex of childArr['Transform'])
|
||||
{
|
||||
const t: LLGLTFTextureTransformOverride = {
|
||||
offset: [],
|
||||
scale: [],
|
||||
rotation: 0,
|
||||
};
|
||||
|
||||
if ((prop = Utils.getFromXMLJS(tex, 'Rotation')) !== undefined)
|
||||
{
|
||||
t.rotation = parseFloat(prop);
|
||||
}
|
||||
if (tex['Offsets'] && Array.isArray(tex['Offsets']) && tex['Offsets'].length > 0)
|
||||
{
|
||||
const offsetArr = tex['Offsets'][0];
|
||||
const xOffset = parseFloat(offsetArr['X'][0]);
|
||||
const yOffset = parseFloat(offsetArr['Y'][0]);
|
||||
t.offset = [
|
||||
xOffset,
|
||||
yOffset
|
||||
];
|
||||
}
|
||||
if (tex['Scale'] && Array.isArray(tex['Scale']) && tex['Scale'].length > 0)
|
||||
{
|
||||
const offsetArr = tex['Scale'][0];
|
||||
const xOffset = parseFloat(offsetArr['X'][0]);
|
||||
const yOffset = parseFloat(offsetArr['Y'][0]);
|
||||
t.scale = [
|
||||
xOffset,
|
||||
yOffset
|
||||
];
|
||||
}
|
||||
|
||||
override.textureTransforms.push(t);
|
||||
}
|
||||
}
|
||||
te.gltfMaterialOverrides.set(textureEntry, override);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((prop = Utils.getFromXMLJS(shape, 'PathBegin')) !== undefined)
|
||||
{
|
||||
go.PathBegin = Utils.unpackBeginCut(prop);
|
||||
@@ -1484,7 +1604,7 @@ export class GameObject implements IGameObjectData
|
||||
|
||||
private async getXML(xml: XMLNode, rootPrim: GameObject, linkNum: number, rootNode?: string): Promise<void>
|
||||
{
|
||||
if (this.resolvedAt === undefined || this.resolvedAt === 0 || this.resolvedInventory === false)
|
||||
if ((this.resolvedAt === undefined || this.resolvedAt === 0 || !this.resolvedInventory) && this.region?.resolver)
|
||||
{
|
||||
await this.region.resolver.resolveObjects([this], true, false, false);
|
||||
}
|
||||
@@ -1502,7 +1622,10 @@ export class GameObject implements IGameObjectData
|
||||
sceneObjectPart.ele('LocalId', this.ID);
|
||||
sceneObjectPart.ele('Name', this.name);
|
||||
sceneObjectPart.ele('Material', this.Material);
|
||||
if (this.region)
|
||||
{
|
||||
sceneObjectPart.ele('RegionHandle', this.region.regionHandle.toString());
|
||||
}
|
||||
Vector3.getXML(sceneObjectPart.ele('GroupPosition'), rootPrim.Position);
|
||||
if (rootPrim === this)
|
||||
{
|
||||
@@ -1535,6 +1658,80 @@ export class GameObject implements IGameObjectData
|
||||
if (this.TextureEntry)
|
||||
{
|
||||
shape.ele('TextureEntry', this.TextureEntry.toBase64());
|
||||
|
||||
if (this.TextureEntry.gltfMaterialOverrides)
|
||||
{
|
||||
const overrides = shape.ele('GLTFMaterialOverrides');
|
||||
for (const te of this.TextureEntry.gltfMaterialOverrides.keys())
|
||||
{
|
||||
const entry = overrides.ele('GLTFMaterialOverride');
|
||||
entry.ele('TextureEntry', te);
|
||||
const override = this.TextureEntry.gltfMaterialOverrides.get(te);
|
||||
if (override)
|
||||
{
|
||||
if (override.doubleSided !== undefined)
|
||||
{
|
||||
entry.ele('DoubleSided', override.doubleSided);
|
||||
}
|
||||
if (override.alphaCutoff !== undefined)
|
||||
{
|
||||
entry.ele('AlphaCutoff', override.alphaCutoff);
|
||||
}
|
||||
if (override.roughnessFactor !== undefined)
|
||||
{
|
||||
entry.ele('RoughnessFactor', override.roughnessFactor);
|
||||
}
|
||||
if (override.metallicFactor !== undefined)
|
||||
{
|
||||
entry.ele('MetallicFactor', override.metallicFactor);
|
||||
}
|
||||
if (override.alphaMode !== undefined)
|
||||
{
|
||||
entry.ele('AlphaMode', override.alphaMode);
|
||||
}
|
||||
if (override.textures !== undefined)
|
||||
{
|
||||
const texs = entry.ele('Textures');
|
||||
for (const tex of override.textures)
|
||||
{
|
||||
texs.ele('UUID', tex);
|
||||
}
|
||||
}
|
||||
if (override.emissiveColor !== undefined && override.emissiveColor.length === 3)
|
||||
{
|
||||
const emissive = entry.ele('EmissiveColor');
|
||||
emissive.ele('R', override.emissiveColor[0]);
|
||||
emissive.ele('G', override.emissiveColor[1]);
|
||||
emissive.ele('B', override.emissiveColor[2]);
|
||||
}
|
||||
if (override.baseColor !== undefined && override.baseColor.length === 4)
|
||||
{
|
||||
const base = entry.ele('BaseColor');
|
||||
base.ele('R', override.baseColor[0]);
|
||||
base.ele('G', override.baseColor[1]);
|
||||
base.ele('B', override.baseColor[2]);
|
||||
base.ele('A', override.baseColor[3]);
|
||||
}
|
||||
if (override.textureTransforms && override.textureTransforms.length === 4)
|
||||
{
|
||||
const transforms = entry.ele('TextureTransforms');
|
||||
for (const trans of override.textureTransforms)
|
||||
{
|
||||
const tfm = transforms.ele('Transform');
|
||||
tfm.ele('Rotation', trans.rotation);
|
||||
|
||||
const offsets = tfm.ele('Offsets');
|
||||
offsets.ele('X', trans.offset[0]);
|
||||
offsets.ele('Y', trans.offset[1]);
|
||||
|
||||
const scale = tfm.ele('Scale');
|
||||
scale.ele('X', trans.scale[0]);
|
||||
scale.ele('Y', trans.scale[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.extraParams)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@caspertech/node-metaverse",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.6",
|
||||
"description": "A node.js interface for Second Life.",
|
||||
"main": "dist/lib/index.js",
|
||||
"types": "dist/lib/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user