diff --git a/lib/classes/Caps.ts b/lib/classes/Caps.ts index fee7af5..236c1d9 100644 --- a/lib/classes/Caps.ts +++ b/lib/classes/Caps.ts @@ -60,6 +60,7 @@ export class Caps req.push('FetchInventoryDescendents2'); req.push('IncrementCOFVersion'); req.push('InterestList'); + req.push('InventoryThumbnailUpload'); req.push('GetDisplayNames'); req.push('GetExperiences'); req.push('AgentExperiences'); @@ -74,9 +75,12 @@ export class Caps req.push('IsExperienceContributor'); req.push('RegionExperiences'); req.push('ExperienceQuery'); + req.push('GetMesh'); + req.push('GetMesh2'); req.push('GetMetadata'); req.push('GetObjectCost'); req.push('GetObjectPhysicsData'); + req.push('GetTexture'); req.push('GroupAPIv1'); req.push('GroupMemberData'); req.push('GroupProposalBallot'); @@ -86,6 +90,7 @@ export class Caps req.push('MapLayer'); req.push('MapLayerGod'); req.push('MeshUploadFlag'); + req.push('ModifyMaterialParams'); req.push('NavMeshGenerationStatus'); req.push('NewFileAgentInventory'); req.push('ObjectAnimation'); @@ -128,6 +133,8 @@ export class Caps req.push('UpdateSettingsAgentInventory'); req.push('UpdateSettingsTaskInventory'); req.push('UploadAgentProfileImage'); + req.push('UpdateMaterialAgentInventory'); + req.push('UpdateMaterialTaskInventory'); req.push('UploadBakedTexture'); req.push('UserInfo'); req.push('ViewerAsset'); diff --git a/lib/classes/Circuit.ts b/lib/classes/Circuit.ts index 6de486b..d352070 100644 --- a/lib/classes/Circuit.ts +++ b/lib/classes/Circuit.ts @@ -117,7 +117,6 @@ export class Circuit }; pos.position += packetLength; } - console.log('Sent packet ' + packetID + ', ' + packetLength + ' bytes'); this.sendMessage(sendXfer, PacketFlags.Reliable); if (final) { @@ -148,7 +147,6 @@ export class Circuit { if (pos.position > -1) { - console.log('Packet confirmed, sending next. Position: ' + pos.position); packetID++; this.sendXferPacket(xferID, packetID, data, pos); } @@ -160,7 +158,6 @@ export class Circuit const msg = packet.message as AbortXferMessage; if (msg.XferID.ID.equals(xferID)) { - console.log('Transfer aborted'); subs.unsubscribe(); reject(new Error('Transfer aborted')); } diff --git a/lib/classes/LLGLTFMaterialData.ts b/lib/classes/LLGLTFMaterialData.ts index 843bb2c..39e547e 100644 --- a/lib/classes/LLGLTFMaterialData.ts +++ b/lib/classes/LLGLTFMaterialData.ts @@ -14,6 +14,43 @@ export interface LLGLTFTexture export type LLGLTFTextureInfo = LLGLTFTexture & LLGLTFExtensionsAndExtras; +export interface LLGLTFMaterialEntry +{ + 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; + } +} + export interface LLGLTFMaterialDataPart { asset?: { @@ -95,41 +132,7 @@ export interface LLGLTFMaterialDataPart 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)[]; + materials?: (LLGLTFMaterialEntry & LLGLTFExtensionsAndExtras)[]; images?: (({ bufferView: number; mimeType: string; diff --git a/lib/classes/LLGLTFMaterialOverride.spec.ts b/lib/classes/LLGLTFMaterialOverride.spec.ts new file mode 100644 index 0000000..0ba786c --- /dev/null +++ b/lib/classes/LLGLTFMaterialOverride.spec.ts @@ -0,0 +1,239 @@ +import { LLGLTFMaterialOverride } from './LLGLTFMaterialOverride'; +import * as assert from 'assert'; + + +const m = new LLGLTFMaterialOverride(); +m.textures = [ + 'a6edc906-2f9f-5fb2-a373-efac406f0ef2', + '2f70a4f7-4ece-48d2-8963-32192608067d', + '45a45cc0-463c-49dd-9133-5202399a16d4', + '39bf5b2b-0619-4892-872c-024e2f601684' +]; +m.doubleSided = true; +m.emissiveFactor = [ + 0.1, + 0.69, + 0.420 +]; +m.alphaCutoff = 0.42; +m.alphaMode = 1; +m.roughnessFactor = 0.23; +m.metallicFactor = 0.91; +m.baseColor = [ + 0.1, + 0.2, + 0.3, + 0.4 +]; +m.textureTransforms = [ + { + rotation: 0.43, + scale: [ + 0.2, + 0.4 + ], + offset: [ + 1.0, + 0.5 + ] + }, + { + rotation: 0.52, + scale: [ + 0.9, + 0.1 + ], + offset: [ + 0.8, + 0.3 + ] + }, + { + rotation: 0.43, + scale: [ + 0.1, + 0.9 + ], + offset: [ + 1.3, + 0.6 + ] + }, + { + rotation: 0.43, + scale: [ + 0.0, + 0.11 + ], + offset: [ + 0.5, + 0.4 + ] + } +]; + +const m3 = new LLGLTFMaterialOverride(); +m3.textures = [ + null, + null, + '45a45cc0-463c-49dd-9133-5202399a16d4', + '39bf5b2b-0619-4892-872c-024e2f601684' +]; +m3.doubleSided = false; +m3.emissiveFactor = [ + 0.1, + 0.69, + 0.420 +]; +m3.alphaCutoff = 0.42; +m3.alphaMode = 0; +m3.roughnessFactor = 0.23; +m3.metallicFactor = 0.91; +m3.baseColor = [ + 0.1, + 0.2, + 0.3, + 0.4 +]; +m3.textureTransforms = [ + { + rotation: 0.43, + scale: [ + 0.2, + 0.4 + ], + offset: [ + 1.0, + 0.5 + ] + }, + null, + { + rotation: 0.43, + scale: [ + 0.1, + 0.9 + ], + offset: [ + 1.3, + 0.6 + ] + }, + { + rotation: 0.43, + scale: [ + 0.0, + 0.11 + ], + offset: [ + 0.5, + 0.4 + ] + } +]; + +describe('LLGLTFMaterialOverride', () => +{ + it('outputs valid JSON', () => + { + assert.equal(m.getFullMaterialJSON(), '{"asset":{"version":"2.0"},"images":[{"uri":"a6edc906-2f9f-5fb2-a373-efac406f0ef2"},{"uri":"45a45cc0-463c-49dd-9133-5202399a16d4"},{"uri":"39bf5b2b-0619-4892-872c-024e2f601684"},{"uri":"2f70a4f7-4ece-48d2-8963-32192608067d"}],"textures":[{"source":0,"extensions":{"KHR_texture_transform":{"offset":[1,0.5],"scale":[0.2,0.4],"rotation":0.43}}},{"source":1,"extensions":{"KHR_texture_transform":{"offset":[1.3,0.6],"scale":[0.1,0.9],"rotation":0.43}}},{"source":2,"extensions":{"KHR_texture_transform":{"offset":[0.5,0.4],"scale":[0,0.11],"rotation":0.43}}},{"source":3,"extensions":{"KHR_texture_transform":{"offset":[0.8,0.3],"scale":[0.9,0.1],"rotation":0.52}}}],"materials":[{"occlusionTexture":{"index":1},"pbrMetallicRoughness":{"baseColorFactor":[0.1,0.2,0.3,0.4],"metallicFactor":0.91,"roughnessFactor":0.23,"baseColorTexture":{"index":0},"metallicRoughnessTexture":{"index":1}},"alphaMode":"BLEND","alphaCutoff":0.42,"emissiveFactor":[0.1,0.69,0.42],"doubleSided":true,"emissiveTexture":{"index":2},"normalTexture":{"index":3}}]}'); + }) + + it('parses json JSON correctly', () => + { + let json = m.getFullMaterialJSON(); + const m2 = LLGLTFMaterialOverride.fromFullMaterialJSON(json); + assert.equal(m2.roughnessFactor, m.roughnessFactor); + assert.equal(m2.doubleSided, m.doubleSided); + assert.equal(m2.alphaCutoff, m.alphaCutoff); + assert.equal(m2.alphaMode, m.alphaMode); + assert.equal(m2.metallicFactor, m.metallicFactor); + assert.equal(m2.baseColor?.length, m.baseColor?.length); + assert.equal(m2.baseColor?.[0], m.baseColor?.[0]); + assert.equal(m2.baseColor?.[1], m.baseColor?.[1]); + assert.equal(m2.baseColor?.[2], m.baseColor?.[2]); + assert.equal(m2.baseColor?.[3], m.baseColor?.[3]); + assert.equal(m2.emissiveFactor?.length, m.emissiveFactor?.length); + assert.equal(m2.emissiveFactor?.[0], m.emissiveFactor?.[0]); + assert.equal(m2.emissiveFactor?.[1], m.emissiveFactor?.[1]); + assert.equal(m2.emissiveFactor?.[2], m.emissiveFactor?.[2]); + assert.equal(m2.textures?.length, m.textures?.length); + assert.equal(m2.textures?.[0], m.textures?.[0]); + assert.equal(m2.textures?.[1], m.textures?.[1]); + assert.equal(m2.textures?.[2], m.textures?.[2]); + assert.equal(m2.textures?.[3], m.textures?.[3]); + assert.equal(m2.textureTransforms?.length, m.textureTransforms?.length); + assert.equal(m2.textureTransforms?.[0]?.offset?.[0], m.textureTransforms?.[0]?.offset?.[0]); + assert.equal(m2.textureTransforms?.[0]?.offset?.[1], m.textureTransforms?.[0]?.offset?.[1]); + assert.equal(m2.textureTransforms?.[0]?.scale?.[0], m.textureTransforms?.[0]?.scale?.[0]); + assert.equal(m2.textureTransforms?.[0]?.scale?.[1], m.textureTransforms?.[0]?.scale?.[1]); + assert.equal(m2.textureTransforms?.[0]?.rotation, m.textureTransforms?.[0]?.rotation); + + assert.equal(m2.textureTransforms?.[1]?.offset?.[0], m.textureTransforms?.[1]?.offset?.[0]); + assert.equal(m2.textureTransforms?.[1]?.offset?.[1], m.textureTransforms?.[1]?.offset?.[1]); + assert.equal(m2.textureTransforms?.[1]?.scale?.[0], m.textureTransforms?.[1]?.scale?.[0]); + assert.equal(m2.textureTransforms?.[1]?.scale?.[1], m.textureTransforms?.[1]?.scale?.[1]); + assert.equal(m2.textureTransforms?.[1]?.rotation, m.textureTransforms?.[1]?.rotation); + + + assert.equal(m2.textureTransforms?.[2]?.offset?.[0], m.textureTransforms?.[2]?.offset?.[0]); + assert.equal(m2.textureTransforms?.[2]?.offset?.[1], m.textureTransforms?.[2]?.offset?.[1]); + assert.equal(m2.textureTransforms?.[2]?.scale?.[0], m.textureTransforms?.[2]?.scale?.[0]); + assert.equal(m2.textureTransforms?.[2]?.scale?.[1], m.textureTransforms?.[2]?.scale?.[1]); + assert.equal(m2.textureTransforms?.[2]?.rotation, m.textureTransforms?.[2]?.rotation); + + assert.equal(m2.textureTransforms?.[3]?.offset?.[0], m.textureTransforms?.[3]?.offset?.[0]); + assert.equal(m2.textureTransforms?.[3]?.offset?.[1], m.textureTransforms?.[3]?.offset?.[1]); + assert.equal(m2.textureTransforms?.[3]?.scale?.[0], m.textureTransforms?.[3]?.scale?.[0]); + assert.equal(m2.textureTransforms?.[3]?.scale?.[1], m.textureTransforms?.[3]?.scale?.[1]); + assert.equal(m2.textureTransforms?.[3]?.rotation, m.textureTransforms?.[3]?.rotation); + + json = m3.getFullMaterialJSON(); + const m4 = LLGLTFMaterialOverride.fromFullMaterialJSON(json); + assert.equal(m4.roughnessFactor, m3.roughnessFactor); + assert.equal(m4.doubleSided, m3.doubleSided); + assert.equal(m4.alphaCutoff, m3.alphaCutoff); + assert.equal(m4.alphaMode, m3.alphaMode); + assert.equal(m4.metallicFactor, m3.metallicFactor); + assert.equal(m4.baseColor?.length, m3.baseColor?.length); + assert.equal(m4.baseColor?.[0], m3.baseColor?.[0]); + assert.equal(m4.baseColor?.[1], m3.baseColor?.[1]); + assert.equal(m4.baseColor?.[2], m3.baseColor?.[2]); + assert.equal(m4.baseColor?.[3], m3.baseColor?.[3]); + assert.equal(m4.emissiveFactor?.length, m3.emissiveFactor?.length); + assert.equal(m4.emissiveFactor?.[0], m3.emissiveFactor?.[0]); + assert.equal(m4.emissiveFactor?.[1], m3.emissiveFactor?.[1]); + assert.equal(m4.emissiveFactor?.[2], m3.emissiveFactor?.[2]); + assert.equal(m4.textures?.length, m3.textures?.length); + assert.equal(m4.textures?.[0], m3.textures?.[0]); + assert.equal(m4.textures?.[1], m3.textures?.[1]); + assert.equal(m4.textures?.[2], m3.textures?.[2]); + assert.equal(m4.textures?.[3], m3.textures?.[3]); + assert.equal(m4.textureTransforms?.length, m3.textureTransforms?.length); + assert.equal(m4.textureTransforms?.[0]?.offset?.[0], m3.textureTransforms?.[0]?.offset?.[0]); + assert.equal(m4.textureTransforms?.[0]?.offset?.[1], m3.textureTransforms?.[0]?.offset?.[1]); + assert.equal(m4.textureTransforms?.[0]?.scale?.[0], m3.textureTransforms?.[0]?.scale?.[0]); + assert.equal(m4.textureTransforms?.[0]?.scale?.[1], m3.textureTransforms?.[0]?.scale?.[1]); + assert.equal(m4.textureTransforms?.[0]?.rotation, m3.textureTransforms?.[0]?.rotation); + + assert.equal(m4.textureTransforms?.[1]?.offset?.[0], m3.textureTransforms?.[1]?.offset?.[0]); + assert.equal(m4.textureTransforms?.[1]?.offset?.[1], m3.textureTransforms?.[1]?.offset?.[1]); + assert.equal(m4.textureTransforms?.[1]?.scale?.[0], m3.textureTransforms?.[1]?.scale?.[0]); + assert.equal(m4.textureTransforms?.[1]?.scale?.[1], m3.textureTransforms?.[1]?.scale?.[1]); + assert.equal(m4.textureTransforms?.[1]?.rotation, m3.textureTransforms?.[1]?.rotation); + + + assert.equal(m4.textureTransforms?.[2]?.offset?.[0], m3.textureTransforms?.[2]?.offset?.[0]); + assert.equal(m4.textureTransforms?.[2]?.offset?.[1], m3.textureTransforms?.[2]?.offset?.[1]); + assert.equal(m4.textureTransforms?.[2]?.scale?.[0], m3.textureTransforms?.[2]?.scale?.[0]); + assert.equal(m4.textureTransforms?.[2]?.scale?.[1], m3.textureTransforms?.[2]?.scale?.[1]); + assert.equal(m4.textureTransforms?.[2]?.rotation, m3.textureTransforms?.[2]?.rotation); + + assert.equal(m4.textureTransforms?.[3]?.offset?.[0], m3.textureTransforms?.[3]?.offset?.[0]); + assert.equal(m4.textureTransforms?.[3]?.offset?.[1], m3.textureTransforms?.[3]?.offset?.[1]); + assert.equal(m4.textureTransforms?.[3]?.scale?.[0], m3.textureTransforms?.[3]?.scale?.[0]); + assert.equal(m4.textureTransforms?.[3]?.scale?.[1], m3.textureTransforms?.[3]?.scale?.[1]); + assert.equal(m4.textureTransforms?.[3]?.rotation, m3.textureTransforms?.[3]?.rotation); + }); +}); + diff --git a/lib/classes/LLGLTFMaterialOverride.ts b/lib/classes/LLGLTFMaterialOverride.ts index a010714..8f0fbf9 100644 --- a/lib/classes/LLGLTFMaterialOverride.ts +++ b/lib/classes/LLGLTFMaterialOverride.ts @@ -1,19 +1,397 @@ +import { + LLGLTFExtensionsAndExtras, + LLGLTFMaterialData, + LLGLTFMaterialEntry, + LLGLTFTextureInfo, +} from './LLGLTFMaterialData'; +import { UUID } from './UUID'; + export interface LLGLTFTextureTransformOverride { - offset: number[]; - scale: number[]; - rotation: number + offset?: number[]; + scale?: number[]; + rotation?: number } export class LLGLTFMaterialOverride { - public textures?: string[]; + public textures?: (string | null)[]; public baseColor?: number[]; - public emissiveColor?: number[]; + public emissiveFactor?: number[]; public metallicFactor?: number; public roughnessFactor?: number; public alphaMode?: number; public alphaCutoff?: number; public doubleSided?: boolean; - public textureTransforms?: LLGLTFTextureTransformOverride[]; + public textureTransforms?: (LLGLTFTextureTransformOverride | null)[]; + + public getFullMaterialJSON(): string + { + const obj: LLGLTFMaterialData = {}; + obj.asset = { + version: '2.0', + }; + + let texIndex = 0; + + const material: LLGLTFMaterialEntry & LLGLTFExtensionsAndExtras = {}; + + const addTexture = (texNum: number): number | undefined => + { + let idx: number | undefined = undefined; + if ((this.textures && this.textures.length > texNum && this.textures[texNum] !== null) || (this.textureTransforms && this.textureTransforms.length > texNum && this.textureTransforms[texNum] !== null)) + { + idx = texIndex++; + if (idx === 0) + { + obj.images = []; + obj.textures = []; + } + + const texture = this.textures?.[texNum]; + if (texture) + { + obj.images!.push({ + uri: texture + }); + } + else + { + obj.images!.push({ + uri: UUID.zero().toString() + }); + } + + const tex: { + source: number + } & LLGLTFExtensionsAndExtras = { + source: idx + }; + + if (this.textureTransforms && this.textureTransforms.length > texNum && this.textureTransforms[texNum] !== null) + { + const trans = this.textureTransforms[texNum]; + tex.extensions = { + KHR_texture_transform: { + offset: trans?.offset ?? undefined, + scale: trans?.scale ?? undefined, + rotation: trans?.rotation ?? undefined + } + }; + } + + if (obj.textures === undefined) + { + obj.textures = [ + tex + ]; + } + else + { + obj.textures.push(tex); + } + } + return idx; + } + + if ( + this.baseColor !== undefined + || (this.textures !== undefined + && ((this.textures.length > 0 + && this.textures[0] !== undefined + ) + || (this.textures.length > 2 + && this.textures[2] !== undefined + ))) + || this.metallicFactor !== undefined + || this.roughnessFactor !== undefined + ) + { + const pbrMetallicRoughness: { + baseColorFactor?: number[]; + baseColorTexture?: LLGLTFTextureInfo; + metallicRoughnessTexture?: LLGLTFTextureInfo; + metallicFactor?: number; + roughnessFactor?: number; + } & LLGLTFExtensionsAndExtras = { + baseColorFactor: this.baseColor, + metallicFactor: this.metallicFactor, + roughnessFactor: this.roughnessFactor + }; + + + let texIdx2 = addTexture(0); + if (texIdx2 !== undefined) + { + pbrMetallicRoughness.baseColorTexture = { + index: texIdx2 + }; + } + + texIdx2 = addTexture(2); + if (texIdx2 !== undefined) + { + pbrMetallicRoughness.metallicRoughnessTexture = { + index: texIdx2 + }; + + material.occlusionTexture = { + index: texIdx2 + }; + } + + material.pbrMetallicRoughness = pbrMetallicRoughness; + } + + if (this.alphaMode !== undefined) + { + if (this.alphaMode === 0) // OPAQUE + { + material.extras = { + override_alpha_mode: true + }; + } + else if (this.alphaMode === 1) + { + material.alphaMode = 'BLEND'; + } + else if (this.alphaMode === 2) + { + material.alphaMode = 'MASK'; + } + } + + if (this.alphaCutoff !== undefined) + { + material.alphaCutoff = this.alphaCutoff; + } + + if (this.emissiveFactor !== undefined) + { + material.emissiveFactor = this.emissiveFactor; + } + + if (this.doubleSided) + { + material.doubleSided = this.doubleSided; + } + else if (this.doubleSided === false) + { + if (!material.extras) + { + material.extras = {}; + } + material.extras.override_double_sided = true; + } + + // Emissive Texture + let texIdx = addTexture(3); + if (texIdx !== undefined) + { + material.emissiveTexture = { + index: texIdx + }; + } + + // Normal Map Texture + texIdx = addTexture(1); + if (texIdx !== undefined) + { + material.normalTexture = { + index: texIdx + }; + } + + if (Object.keys(material).length > 0) + { + obj.materials = [ + material + ]; + } + + return JSON.stringify(obj); + } + + public setTexture(idx: number, uuid: string): void + { + if (!this.textures) + { + this.textures = []; + } + for (let x = this.textures.length; x < idx + 1; x++) + { + this.textures.push(null); + } + this.textures[idx] = uuid; + } + + public setTransform(idx: number, trans: LLGLTFTextureTransformOverride): void + { + if (!this.textureTransforms) + { + this.textureTransforms = []; + } + for (let x = this.textureTransforms.length; x < idx + 1; x++) + { + this.textureTransforms.push(null); + } + this.textureTransforms[idx] = trans; + } + + public static fromFullMaterialJSON(json: string): LLGLTFMaterialOverride + { + const obj = JSON.parse(json) as LLGLTFMaterialData; + + const over = new LLGLTFMaterialOverride(); + + if (!obj.materials?.length) + { + return over; + } + const mat = obj.materials[0]; + + const getTexture = (idx: number): { uuid: string | null, transform: LLGLTFTextureTransformOverride | null } => + { + const found: { + uuid: string | null, + transform: LLGLTFTextureTransformOverride | null + } = { + uuid: null, + transform: null + }; + + if (obj.textures && Array.isArray(obj.textures) && obj.textures.length > idx) + { + const source = obj.textures[idx].source; + if (source !== undefined && obj.images && Array.isArray(obj.images) && obj.images.length > source) + { + const img = obj.images[source]; + if ('uri' in img) + { + found.uuid = img.uri ?? null; + if (found.uuid === UUID.zero().toString()) + { + found.uuid = null; + } + } + } + const transform = obj.textures[idx].extensions?.KHR_texture_transform as { + offset?: number[], + scale?: number[], + rotation?: number + }; + if (transform) + { + found.transform = transform ?? null; + } + } + + return found; + }; + + if (mat.pbrMetallicRoughness) + { + const pbr = mat.pbrMetallicRoughness; + if (pbr.metallicFactor !== undefined) + { + over.metallicFactor = pbr.metallicFactor; + } + if (pbr.roughnessFactor !== undefined) + { + over.roughnessFactor = pbr.roughnessFactor; + } + if (pbr.baseColorFactor !== undefined && Array.isArray(pbr.baseColorFactor) && pbr.baseColorFactor.length === 4) + { + over.baseColor = pbr.baseColorFactor; + } + if (pbr.baseColorTexture?.index !== undefined) + { + const tex = getTexture(pbr.baseColorTexture.index); + if (tex && tex.uuid) + { + over.setTexture(0, tex.uuid); + } + if (tex && tex.transform) + { + over.setTransform(0, tex.transform); + } + } + if (pbr.metallicRoughnessTexture?.index !== undefined) + { + const tex = getTexture(pbr.metallicRoughnessTexture.index); + if (tex && tex.uuid) + { + over.setTexture(2, tex.uuid); + } + if (tex && tex.transform) + { + over.setTransform(2, tex.transform); + } + } + } + + if (mat.alphaMode) + { + switch (mat.alphaMode) + { + case 'BLEND': + over.alphaMode = 1; + break; + case 'MASK': + over.alphaMode = 2; + break; + } + } + else if (mat.extras && mat.extras.override_alpha_mode) + { + over.alphaMode = 0; + } + + if (mat.alphaCutoff !== undefined) + { + over.alphaCutoff = mat.alphaCutoff; + } + + if (mat.emissiveFactor !== undefined) + { + over.emissiveFactor = mat.emissiveFactor; + } + + if (mat.doubleSided === true) + { + over.doubleSided = true; + } + else if (mat.extras && mat.extras.override_double_sided) + { + over.doubleSided = false; + } + + if (mat.normalTexture?.index !== undefined) + { + const tex = getTexture(mat.normalTexture?.index); + if (tex && tex.uuid) + { + over.setTexture(1, tex.uuid); + } + if (tex && tex.transform) + { + over.setTransform(1, tex.transform); + } + } + + if (mat.emissiveTexture?.index !== undefined) + { + const tex = getTexture(mat.emissiveTexture?.index); + if (tex && tex.uuid) + { + over.setTexture(3, tex.uuid); + } + if (tex && tex.transform) + { + over.setTransform(3, tex.transform); + } + } + + return over; + } } diff --git a/lib/classes/ObjectStoreLite.ts b/lib/classes/ObjectStoreLite.ts index 292f8b2..1aa2a87 100644 --- a/lib/classes/ObjectStoreLite.ts +++ b/lib/classes/ObjectStoreLite.ts @@ -192,7 +192,7 @@ export class ObjectStoreLite implements IObjectStore if (emissiveColor !== undefined && Array.isArray(emissiveColor) && emissiveColor.length === 3 && isNumberArray(emissiveColor)) { - override.emissiveColor = emissiveColor; + override.emissiveFactor = emissiveColor; } if (metallicFactor !== undefined && typeof metallicFactor === 'number') @@ -242,9 +242,17 @@ export class ObjectStoreLite implements IObjectStore override.textureTransforms = []; for (const transform of textureTransforms) { - if (isLLGLTFTextureTransformOverride(transform)) + if (transform instanceof LLSDMap) { - override.textureTransforms.push(transform); + const tObj = { + offset: transform.get('o'), + scale: transform.get('s'), + rotation: transform.get('r') + } + if (isLLGLTFTextureTransformOverride(tObj)) + { + override.textureTransforms.push(tObj); + } } } } diff --git a/lib/classes/public/GameObject.spec.ts b/lib/classes/public/GameObject.spec.ts index d1fede3..13afd1c 100644 --- a/lib/classes/public/GameObject.spec.ts +++ b/lib/classes/public/GameObject.spec.ts @@ -35,7 +35,7 @@ describe('GameObject', () => '1e079cce-eeca-4e05-9a4f-e58d8398ecf1', '03573ae5-4c1a-44f7-9cd3-8b49028f9e48' ]; - override.emissiveColor = [0.2, 0.5, 0.6]; + override.emissiveFactor = [0.2, 0.5, 0.6]; override.baseColor = [0.1, 0.2, 0.3, 0.4]; override.textureTransforms = [ { @@ -94,12 +94,12 @@ describe('GameObject', () => { assert(false, 'Textures is not an array'); } - if (Array.isArray(entry.emissiveColor)) + if (Array.isArray(entry.emissiveFactor)) { - 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); + assert.equal(entry.emissiveFactor.length, 3); + assert.equal(entry.emissiveFactor[0], 0.2); + assert.equal(entry.emissiveFactor[1], 0.5); + assert.equal(entry.emissiveFactor[2], 0.6); } else { @@ -123,7 +123,7 @@ describe('GameObject', () => for (let x = 0; x < 4; x++) { const transform = entry.textureTransforms[x]; - if (Array.isArray(transform.scale)) + if (transform?.scale && Array.isArray(transform.scale)) { assert.equal(transform.scale.length, 2); assert.equal(transform.scale[0], 0.5); @@ -133,7 +133,7 @@ describe('GameObject', () => { assert(false, 'Scale is not an array'); } - if (Array.isArray(transform.offset)) + if (transform?.offset && Array.isArray(transform.offset)) { assert.equal(transform.offset.length, 2); assert.equal(transform.offset[0], 0.1); diff --git a/lib/classes/public/GameObject.ts b/lib/classes/public/GameObject.ts index 874af17..ecfba36 100644 --- a/lib/classes/public/GameObject.ts +++ b/lib/classes/public/GameObject.ts @@ -55,7 +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 { LLGLTFMaterialOverride } from '../LLGLTFMaterialOverride'; import * as uuid from 'uuid'; @@ -381,123 +381,19 @@ 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) + if (go.TextureEntry && shape['MatOvrd'] && Array.isArray(shape['MatOvrd']) && shape['MatOvrd'].length > 0) { - const te = go.TextureEntry; - te.gltfMaterialOverrides = new Map(); - const children = shape['GLTFMaterialOverrides'][0]; - const childObj = children['GLTFMaterialOverride']; - if (childObj) + const tex = Buffer.from(shape['MatOvrd'][0], 'base64'); + let pos = 0; + const entryCount = tex.readUInt8(pos++); + for (let x = 0; x < entryCount; x++) { - 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); - } + const te_index = tex.readUInt8(pos++); + const len = tex.readUInt16LE(pos++); + pos++; + const json = tex.slice(pos, pos + len).toString('utf-8'); + pos = pos + len; + go.TextureEntry.gltfMaterialOverrides.set(te_index, LLGLTFMaterialOverride.fromFullMaterialJSON(json)); } } if ((prop = Utils.getFromXMLJS(shape, 'PathBegin')) !== undefined) @@ -1661,75 +1557,33 @@ export class GameObject implements IGameObjectData if (this.TextureEntry.gltfMaterialOverrides) { - const overrides = shape.ele('GLTFMaterialOverrides'); - for (const te of this.TextureEntry.gltfMaterialOverrides.keys()) + const overrideKeys = Array.from(this.TextureEntry.gltfMaterialOverrides.keys()); + const numEntries = overrideKeys.length; + + if (numEntries > 0) { - const entry = overrides.ele('GLTFMaterialOverride'); - entry.ele('TextureEntry', te); - const override = this.TextureEntry.gltfMaterialOverrides.get(te); - if (override) + const buf: Buffer[] = []; + + const num = Buffer.allocUnsafe(1); + num.writeUInt8(numEntries, 0); + buf.push(num) + for (const overrideKey of overrideKeys) { - if (override.doubleSided !== undefined) + const override = this.TextureEntry.gltfMaterialOverrides.get(overrideKey); + if (override === undefined) { - entry.ele('DoubleSided', override.doubleSided); + continue; } - 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 header = Buffer.allocUnsafe(3); + header.writeUInt8(overrideKey, 0); - const scale = tfm.ele('Scale'); - scale.ele('X', trans.scale[0]); - scale.ele('Y', trans.scale[1]); - } - } + const json = override.getFullMaterialJSON(); + header.writeUInt16LE(json.length, 1); + buf.push(header); + buf.push(Buffer.from(json, 'utf-8')); } + shape.ele('MatOvrd', Buffer.concat(buf).toString('base64')); } } } diff --git a/package.json b/package.json index 1b97720..89898c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.6.6", + "version": "0.6.7", "description": "A node.js interface for Second Life.", "main": "dist/lib/index.js", "types": "dist/lib/index.d.ts",