- Mesh upload support
- LLMesh asset decoding and encoding (inc. LLPhysicsConvex, LLSkin, LLSubMesh) - Query inventory folder by type - onSelectedObject event - fetchInventoryItem command - Fix packing/unpacking of object shape - Time sync with SimulatorViewerTimeMessage - Changed several classes to a .from style rather than setting up in the constructor (exception friendly) - Whole bunch of other improvements - Object building
This commit is contained in:
@@ -299,101 +299,7 @@ async function connect()
|
||||
new nmv.Vector3([-1.0, 0, 0]),
|
||||
new nmv.Vector3([0.0, 1.0, 0]));
|
||||
|
||||
let foundObjects = {};
|
||||
|
||||
const getAllChildren = function(obj)
|
||||
{
|
||||
let children = obj.children;
|
||||
let newChildren = [obj]
|
||||
;
|
||||
for (const child of children)
|
||||
{
|
||||
newChildren = newChildren.concat(getAllChildren(child));
|
||||
}
|
||||
return children.concat(newChildren);
|
||||
};
|
||||
|
||||
const scan = setInterval(async () => {
|
||||
const count = bot.clientCommands.region.countObjects();
|
||||
if (count > lastObjects)
|
||||
{
|
||||
console.log(count + ' objects found.');
|
||||
lastObjects = count;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (height > 4096)
|
||||
{
|
||||
if (Object.keys(foundObjects).length > lastObjects)
|
||||
{
|
||||
const index = {};
|
||||
console.log('Indexing found objects');
|
||||
for(const o of foundObjects)
|
||||
{
|
||||
index[o.FullID.toString()] = true;
|
||||
}
|
||||
console.log('Searching for missing objects');
|
||||
for(const k of Object.keys(foundObjects))
|
||||
{
|
||||
if (index[k] === undefined)
|
||||
{
|
||||
console.log('Object missing: ' + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('Finished scanning region! ' + lastObjects + ' objects found. Resolving all objects..');
|
||||
clearInterval(scan);
|
||||
// Query all objects in the region
|
||||
var tmr = new Date().getTime();
|
||||
const objs = await bot.clientCommands.region.getAllObjects(true);
|
||||
var tmr2 = new Date().getTime();
|
||||
|
||||
let totalObjects = 0;
|
||||
let totalLandImpact = 0;
|
||||
|
||||
for (const obj of objs)
|
||||
{
|
||||
totalObjects += (1 + obj.totalChildren);
|
||||
if (obj.landImpact)
|
||||
{
|
||||
totalLandImpact += obj.landImpact;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Found ' + objs.length + ' linksets with ' + totalObjects + ' objects in ' + (tmr2 - tmr) + 'ms. Land impact: ' + totalLandImpact);
|
||||
|
||||
let searchResults = await bot.clientCommands.region.findObjectsByName('FINDME-*');
|
||||
console.log('Found ' + searchResults.length + ' objects containing the string FINDME-*');
|
||||
for (const obj of searchResults)
|
||||
{
|
||||
console.log('Object: ' + obj.name + ', ' + obj.FullID.toString() + ', position: ' + obj.Position.toString() + ', land impact: ' + obj.landImpact)
|
||||
}
|
||||
|
||||
searchResults = await bot.clientCommands.region.findObjectsByName('rezcubes');
|
||||
console.log('Found ' + searchResults.length + ' objects containing the string rezcubes');
|
||||
for (const obj of searchResults)
|
||||
{
|
||||
console.log('Object: ' + obj.name + ', ' + obj.FullID.toString() + ', position: ' + obj.Position.toString() + ', land impact: ' + obj.landImpact)
|
||||
for (const k of Object.keys(obj.NameValue))
|
||||
{
|
||||
console.log(k + ': ' + obj.NameValue[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('Moving to ' + height);
|
||||
height += 128;
|
||||
bot.clientCommands.agent.setCamera(
|
||||
new nmv.Vector3([128, 128, height]),
|
||||
new nmv.Vector3([128, 128, 0]),
|
||||
5000,
|
||||
new nmv.Vector3([-1.0, 0, 0]),
|
||||
new nmv.Vector3([0.0, 1.0, 0]));
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
|
||||
|
||||
//await bot.clientCommands.friends.grantFriendRights('d1cd5b71-6209-4595-9bf0-771bf689ce00', nmv.RightsFlags.CanModifyObjects | nmv.RightsFlags.CanSeeOnline | nmv.RightsFlags.CanSeeOnMap );
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {AttachmentPoint} from '../enums/AttachmentPoint';
|
||||
import {Utils} from './Utils';
|
||||
import {ClientEvents} from './ClientEvents';
|
||||
import Timer = NodeJS.Timer;
|
||||
import {ControlFlags, GroupChatSessionAgentListEvent, AgentFlags, PacketFlags, AssetType} from '..';
|
||||
import {ControlFlags, GroupChatSessionAgentListEvent, AgentFlags, PacketFlags, AssetType, FolderType} from '..';
|
||||
import {GameObject} from './public/GameObject';
|
||||
|
||||
export class Agent
|
||||
@@ -245,7 +245,7 @@ export class Agent
|
||||
Object.keys(this.inventory.main.skeleton).forEach((uuid) =>
|
||||
{
|
||||
const folder = this.inventory.main.skeleton[uuid];
|
||||
if (folder.typeDefault === AssetType.CurrentOutfitFolder)
|
||||
if (folder.typeDefault === FolderType.CurrentOutfit)
|
||||
{
|
||||
const folderID = folder.folderID;
|
||||
|
||||
|
||||
@@ -322,51 +322,48 @@ export class Caps
|
||||
});
|
||||
}
|
||||
|
||||
capsRequestXML(capability: string, data: any, debug = false): Promise<any>
|
||||
capsPerformXMLRequest(url: string, data: any): Promise<any>
|
||||
{
|
||||
return new Promise<any>(async (resolve, reject) =>
|
||||
{
|
||||
const xml = LLSD.LLSD.formatXML(data);
|
||||
this.request(url, xml, 'application/llsd+xml').then((body: string) =>
|
||||
{
|
||||
let result: any = null;
|
||||
try
|
||||
{
|
||||
result = LLSD.LLSD.parseXML(body);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.error('Error parsing LLSD');
|
||||
console.error(body);
|
||||
reject(err);
|
||||
}
|
||||
resolve(result);
|
||||
}).catch((err) =>
|
||||
{
|
||||
console.error(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async capsRequestXML(capability: string, data: any, debug = false): Promise<any>
|
||||
{
|
||||
if (debug)
|
||||
{
|
||||
console.log(data);
|
||||
}
|
||||
return new Promise<any>(async (resolve, reject) =>
|
||||
|
||||
const t = new Date().getTime();
|
||||
if (this.capRateLimitTimers[capability] && (this.capRateLimitTimers[capability] + Caps.CAP_INVOCATION_INTERVAL_MS) > t)
|
||||
{
|
||||
const t = new Date().getTime();
|
||||
if (this.capRateLimitTimers[capability] && (this.capRateLimitTimers[capability] + Caps.CAP_INVOCATION_INTERVAL_MS) > t)
|
||||
{
|
||||
await this.waitForCapTimeout(capability);
|
||||
}
|
||||
this.capRateLimitTimers[capability] = t;
|
||||
this.getCapability(capability).then((url) =>
|
||||
{
|
||||
const xml = LLSD.LLSD.formatXML(data);
|
||||
if (debug)
|
||||
{
|
||||
console.log(xml);
|
||||
}
|
||||
this.request(url, xml, 'application/llsd+xml').then((body: string) =>
|
||||
{
|
||||
let result: any = null;
|
||||
try
|
||||
{
|
||||
result = LLSD.LLSD.parseXML(body);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.error('Error parsing LLSD');
|
||||
console.error(body);
|
||||
reject(err);
|
||||
}
|
||||
resolve(result);
|
||||
}).catch((err) =>
|
||||
{
|
||||
console.error(err);
|
||||
reject(err);
|
||||
});
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
await this.waitForCapTimeout(capability);
|
||||
}
|
||||
this.capRateLimitTimers[capability] = t;
|
||||
const url = await this.getCapability(capability);
|
||||
return await this.capsPerformXMLRequest(url, data);
|
||||
}
|
||||
|
||||
shutdown()
|
||||
|
||||
@@ -23,6 +23,7 @@ import {Subject} from 'rxjs/internal/Subject';
|
||||
import {NewObjectEvent} from '../events/NewObjectEvent';
|
||||
import {ObjectUpdatedEvent} from '../events/ObjectUpdatedEvent';
|
||||
import {ObjectKilledEvent} from '../events/ObjectKilledEvent';
|
||||
import {SelectedObjectEvent} from '../events/SelectedObjectEvent';
|
||||
|
||||
|
||||
export class ClientEvents
|
||||
@@ -50,4 +51,5 @@ export class ClientEvents
|
||||
onNewObjectEvent: Subject<NewObjectEvent> = new Subject<NewObjectEvent>();
|
||||
onObjectUpdatedEvent: Subject<ObjectUpdatedEvent> = new Subject<ObjectUpdatedEvent>();
|
||||
onObjectKilledEvent: Subject<ObjectKilledEvent> = new Subject<ObjectKilledEvent>();
|
||||
onSelectedObjectEvent: Subject<SelectedObjectEvent> = new Subject<SelectedObjectEvent>();
|
||||
}
|
||||
|
||||
@@ -139,11 +139,31 @@ export class Color4
|
||||
return 0;
|
||||
}
|
||||
|
||||
writeToBuffer(buf: Buffer, pos: number)
|
||||
writeToBuffer(buf: Buffer, pos: number, inverted: boolean = false)
|
||||
{
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getRed(), 0, 1.0), pos++);
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getGreen(), 0, 1.0), pos++);
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getBlue(), 0, 1.0), pos++);
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getAlpha(), 0, 1.0), pos);
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getRed(), 0, 1.0), pos);
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getGreen(), 0, 1.0), pos + 1);
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getBlue(), 0, 1.0), pos + 2);
|
||||
buf.writeUInt8(Utils.FloatToByte(this.getAlpha(), 0, 1.0), pos + 3);
|
||||
|
||||
if (inverted)
|
||||
{
|
||||
buf[pos] = (255 - buf[pos]);
|
||||
buf[pos + 1] = (255 - buf[pos + 1]);
|
||||
buf[pos + 2] = (255 - buf[pos + 2]);
|
||||
buf[pos + 3] = (255 - buf[pos + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
getBuffer(inverted: boolean = false): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(4);
|
||||
this.writeToBuffer(buf, 0, inverted);
|
||||
return buf;
|
||||
}
|
||||
|
||||
equals(other: Color4): boolean
|
||||
{
|
||||
return (this.red === other.red && this.green === other.green && this.blue === other.blue && this.alpha === other.alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import {UUID} from './UUID';
|
||||
import {ClientEvents} from './ClientEvents';
|
||||
import {InventoryFolder} from './InventoryFolder';
|
||||
import {Agent} from './Agent';
|
||||
import {AssetType} from '..';
|
||||
import {AssetType, FolderType} from '..';
|
||||
import * as LLSD from '@caspertech/llsd';
|
||||
import {InventoryItem} from './InventoryItem';
|
||||
|
||||
export class Inventory
|
||||
{
|
||||
@@ -19,6 +21,9 @@ export class Inventory
|
||||
} = {
|
||||
skeleton: {}
|
||||
};
|
||||
|
||||
itemsByID: {[key: string]: InventoryItem} = {};
|
||||
|
||||
private clientEvents: ClientEvents;
|
||||
private agent: Agent;
|
||||
|
||||
@@ -59,25 +64,66 @@ export class Inventory
|
||||
return new InventoryFolder(this.main, this.agent);
|
||||
}
|
||||
}
|
||||
findFolderForType(type: AssetType): UUID
|
||||
findFolderForType(type: FolderType): UUID
|
||||
{
|
||||
if (this.main.root === undefined)
|
||||
const root = this.main.skeleton;
|
||||
for (const key of Object.keys(root))
|
||||
{
|
||||
return UUID.zero();
|
||||
}
|
||||
if (type === AssetType.Folder)
|
||||
{
|
||||
return this.main.root;
|
||||
}
|
||||
let found = UUID.zero();
|
||||
Object.keys(this.main.skeleton).forEach((fUUID) =>
|
||||
{
|
||||
const folder = this.main.skeleton[fUUID];
|
||||
if (folder.typeDefault === type)
|
||||
const f = root[key];
|
||||
if (f.typeDefault === type)
|
||||
{
|
||||
found = folder.folderID;
|
||||
return f.folderID;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
}
|
||||
return this.getRootFolderMain().folderID;
|
||||
}
|
||||
async fetchInventoryItem(item: UUID): Promise<InventoryItem | null>
|
||||
{
|
||||
const params = {
|
||||
'agent_id': new LLSD.UUID(this.agent.agentID),
|
||||
'items': [
|
||||
{
|
||||
'item_id': new LLSD.UUID(item),
|
||||
'owner_id': new LLSD.UUID(this.agent.agentID)
|
||||
}
|
||||
]
|
||||
};
|
||||
const response = await this.agent.currentRegion.caps.capsRequestXML('FetchInventory2', params);
|
||||
for (const receivedItem of response['items'])
|
||||
{
|
||||
const invItem = new InventoryItem();
|
||||
invItem.assetID = new UUID(receivedItem['asset_id'].toString());
|
||||
invItem.inventoryType = parseInt(receivedItem['inv_type'], 10);
|
||||
invItem.type = parseInt(receivedItem['type'], 10);
|
||||
invItem.itemID = item;
|
||||
invItem.permissions = {
|
||||
baseMask: parseInt(receivedItem['permissions']['base_mask'], 10),
|
||||
nextOwnerMask: parseInt(receivedItem['permissions']['next_owner_mask'], 10),
|
||||
groupMask: parseInt(receivedItem['permissions']['group_mask'], 10),
|
||||
lastOwner: new UUID(receivedItem['permissions']['last_owner_id'].toString()),
|
||||
owner: new UUID(receivedItem['permissions']['owner_id'].toString()),
|
||||
creator: new UUID(receivedItem['permissions']['creator_id'].toString()),
|
||||
group: new UUID(receivedItem['permissions']['group_id'].toString()),
|
||||
ownerMask: parseInt(receivedItem['permissions']['owner_mask'], 10),
|
||||
everyoneMask: parseInt(receivedItem['permissions']['everyone_mask'], 10),
|
||||
};
|
||||
invItem.flags = parseInt(receivedItem['flags'], 10);
|
||||
invItem.description = receivedItem['desc'];
|
||||
invItem.name = receivedItem['name'];
|
||||
invItem.created = new Date(receivedItem['created_at'] * 1000);
|
||||
invItem.parentID = new UUID(receivedItem['parent_id'].toString());
|
||||
invItem.saleType = parseInt(receivedItem['sale_info']['sale_type'], 10);
|
||||
invItem.salePrice = parseInt(receivedItem['sale_info']['sale_price'], 10);
|
||||
if (this.main.skeleton[invItem.parentID.toString()])
|
||||
{
|
||||
await this.main.skeleton[invItem.parentID.toString()].addItem(invItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('FolderID of ' + invItem.parentID.toString() + ' not found!');
|
||||
}
|
||||
return invItem;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import * as path from 'path';
|
||||
import * as LLSD from '@caspertech/llsd';
|
||||
import {InventorySortOrder} from '../enums/InventorySortOrder';
|
||||
import {Agent} from './Agent';
|
||||
import {AssetType} from '..';
|
||||
import {FolderType} from '..';
|
||||
import {Inventory} from './Inventory';
|
||||
|
||||
export class InventoryFolder
|
||||
{
|
||||
typeDefault: AssetType;
|
||||
typeDefault: FolderType;
|
||||
version: number;
|
||||
name: string;
|
||||
folderID: UUID;
|
||||
@@ -111,7 +112,7 @@ export class InventoryFolder
|
||||
item.permissions.owner = new UUID(item.permissions.owner.mUUID);
|
||||
item.permissions.creator = new UUID(item.permissions.creator.mUUID);
|
||||
item.permissions.group = new UUID(item.permissions.group.mUUID);
|
||||
this.items.push(item);
|
||||
this.addItem(item);
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
@@ -134,6 +135,37 @@ export class InventoryFolder
|
||||
});
|
||||
}
|
||||
|
||||
async removeItem(itemID: UUID, save: boolean = false)
|
||||
{
|
||||
if (this.agent.inventory.itemsByID[itemID.toString()])
|
||||
{
|
||||
delete this.agent.inventory.itemsByID[itemID.toString()];
|
||||
this.items = this.items.filter((item) =>
|
||||
{
|
||||
console.log(item.itemID + ' vs ' + JSON.stringify(itemID));
|
||||
return !item.itemID.equals(itemID);
|
||||
})
|
||||
}
|
||||
if (save)
|
||||
{
|
||||
await this.saveCache();
|
||||
}
|
||||
}
|
||||
|
||||
async addItem(item: InventoryItem, save: boolean = false)
|
||||
{
|
||||
if (this.agent.inventory.itemsByID[item.itemID.toString()])
|
||||
{
|
||||
await this.removeItem(item.itemID, false);
|
||||
}
|
||||
this.items.push(item);
|
||||
this.agent.inventory.itemsByID[item.itemID.toString()] = item;
|
||||
if (save)
|
||||
{
|
||||
await this.saveCache();
|
||||
}
|
||||
}
|
||||
|
||||
populate()
|
||||
{
|
||||
return new Promise((resolve, reject) =>
|
||||
@@ -186,7 +218,7 @@ export class InventoryFolder
|
||||
creator: new UUID(item['permissions']['creator_id'].toString()),
|
||||
group: new UUID(item['permissions']['group_id'].toString())
|
||||
};
|
||||
this.items.push(invItem);
|
||||
this.addItem(invItem);
|
||||
});
|
||||
this.saveCache().then(() =>
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ import {AssetType, InventoryItemFlags} from '..';
|
||||
|
||||
export class InventoryItem
|
||||
{
|
||||
assetID: UUID = UUID.zero();;
|
||||
assetID: UUID = UUID.zero();
|
||||
inventoryType: InventoryType;
|
||||
name: string;
|
||||
salePrice: number;
|
||||
@@ -39,4 +39,30 @@ export class InventoryItem
|
||||
group: UUID.zero(),
|
||||
groupOwned: false
|
||||
};
|
||||
}
|
||||
|
||||
getCRC(): number
|
||||
{
|
||||
let crc = 0;
|
||||
crc += this.assetID.CRC();
|
||||
crc += this.parentID.CRC();
|
||||
crc += this.itemID.CRC();
|
||||
crc += this.permissions.creator.CRC();
|
||||
crc += this.permissions.owner.CRC();
|
||||
crc += this.permissions.group.CRC();
|
||||
crc += this.permissions.ownerMask;
|
||||
crc += this.permissions.nextOwnerMask;
|
||||
crc += this.permissions.everyoneMask;
|
||||
crc += this.permissions.groupMask;
|
||||
crc += this.flags;
|
||||
crc += this.inventoryType;
|
||||
crc += this.type;
|
||||
crc += Math.round(this.created.getTime() / 1000);
|
||||
crc += this.salePrice;
|
||||
crc += this.saleType * 0x07073096;
|
||||
while (crc > 4294967295)
|
||||
{
|
||||
crc -= 4294967295;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,23 +80,23 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
obj.Flags = objData.UpdateFlags;
|
||||
obj.PathCurve = objData.PathCurve;
|
||||
obj.ProfileCurve = objData.ProfileCurve;
|
||||
obj.PathBegin = objData.PathBegin;
|
||||
obj.PathEnd = objData.PathEnd;
|
||||
obj.PathScaleX = objData.PathScaleX;
|
||||
obj.PathScaleY = objData.PathScaleY;
|
||||
obj.PathShearX = objData.PathShearX;
|
||||
obj.PathShearY = objData.PathShearY;
|
||||
obj.PathTwist = objData.PathTwist;
|
||||
obj.PathTwistBegin = objData.PathTwistBegin;
|
||||
obj.PathRadiusOffset = objData.PathRadiusOffset;
|
||||
obj.PathTaperX = objData.PathTaperX;
|
||||
obj.PathTaperY = objData.PathTaperY;
|
||||
obj.PathRevolutions = objData.PathRevolutions;
|
||||
obj.PathSkew = objData.PathSkew;
|
||||
obj.ProfileBegin = objData.ProfileBegin;
|
||||
obj.ProfileEnd = objData.ProfileEnd;
|
||||
obj.ProfileHollow = objData.ProfileHollow;
|
||||
obj.TextureEntry = new TextureEntry(objData.TextureEntry);
|
||||
obj.PathBegin = Utils.unpackBeginCut(objData.PathBegin);
|
||||
obj.PathEnd = Utils.unpackEndCut(objData.PathEnd);
|
||||
obj.PathScaleX = Utils.unpackPathScale(objData.PathScaleX);
|
||||
obj.PathScaleY = Utils.unpackPathScale(objData.PathScaleY);
|
||||
obj.PathShearX = Utils.unpackPathShear(objData.PathShearX);
|
||||
obj.PathShearY = Utils.unpackPathShear(objData.PathShearY);
|
||||
obj.PathTwist = Utils.unpackPathTwist(objData.PathTwist);
|
||||
obj.PathTwistBegin = Utils.unpackPathTwist(objData.PathTwistBegin);
|
||||
obj.PathRadiusOffset = Utils.unpackPathTwist(objData.PathRadiusOffset);
|
||||
obj.PathTaperX = Utils.unpackPathTaper(objData.PathTaperX);
|
||||
obj.PathTaperY = Utils.unpackPathTaper(objData.PathTaperY);
|
||||
obj.PathRevolutions = Utils.unpackPathRevolutions(objData.PathRevolutions);
|
||||
obj.PathSkew = Utils.unpackPathTwist(objData.PathSkew);
|
||||
obj.ProfileBegin = Utils.unpackBeginCut(objData.ProfileBegin);
|
||||
obj.ProfileEnd = Utils.unpackEndCut(objData.ProfileEnd);
|
||||
obj.ProfileHollow = Utils.unpackProfileHollow(objData.ProfileHollow);
|
||||
obj.TextureEntry = TextureEntry.from(objData.TextureEntry);
|
||||
obj.textureAnim = TextureAnim.from(objData.TextureAnim);
|
||||
|
||||
const pcodeData = objData.Data;
|
||||
@@ -378,31 +378,31 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore
|
||||
pos += result.readLength;
|
||||
}
|
||||
o.PathCurve = buf.readUInt8(pos++);
|
||||
o.PathBegin = buf.readUInt16LE(pos);
|
||||
o.PathBegin = Utils.unpackBeginCut(buf.readUInt16LE(pos));
|
||||
pos = pos + 2;
|
||||
o.PathEnd = buf.readUInt16LE(pos);
|
||||
o.PathEnd = Utils.unpackEndCut(buf.readUInt16LE(pos));
|
||||
pos = pos + 2;
|
||||
o.PathScaleX = buf.readUInt8(pos++);
|
||||
o.PathScaleY = buf.readUInt8(pos++);
|
||||
o.PathShearX = buf.readUInt8(pos++);
|
||||
o.PathShearY = buf.readUInt8(pos++);
|
||||
o.PathTwist = buf.readUInt8(pos++);
|
||||
o.PathTwistBegin = buf.readUInt8(pos++);
|
||||
o.PathRadiusOffset = buf.readUInt8(pos++);
|
||||
o.PathTaperX = buf.readUInt8(pos++);
|
||||
o.PathTaperY = buf.readUInt8(pos++);
|
||||
o.PathRevolutions = buf.readUInt8(pos++);
|
||||
o.PathSkew = buf.readUInt8(pos++);
|
||||
o.PathScaleX = Utils.unpackPathScale(buf.readUInt8(pos++));
|
||||
o.PathScaleY = Utils.unpackPathScale(buf.readUInt8(pos++));
|
||||
o.PathShearX = Utils.unpackPathShear(buf.readUInt8(pos++));
|
||||
o.PathShearY = Utils.unpackPathShear(buf.readUInt8(pos++));
|
||||
o.PathTwist = Utils.unpackPathTwist(buf.readUInt8(pos++));
|
||||
o.PathTwistBegin = Utils.unpackPathTwist(buf.readUInt8(pos++));
|
||||
o.PathRadiusOffset = Utils.unpackPathTwist(buf.readUInt8(pos++));
|
||||
o.PathTaperX = Utils.unpackPathTaper(buf.readUInt8(pos++));
|
||||
o.PathTaperY = Utils.unpackPathTaper(buf.readUInt8(pos++));
|
||||
o.PathRevolutions = Utils.unpackPathRevolutions(buf.readUInt8(pos++));
|
||||
o.PathSkew = Utils.unpackPathTwist(buf.readUInt8(pos++));
|
||||
o.ProfileCurve = buf.readUInt8(pos++);
|
||||
o.ProfileBegin = buf.readUInt16LE(pos);
|
||||
o.ProfileBegin = Utils.unpackBeginCut(buf.readUInt16LE(pos));
|
||||
pos = pos + 2;
|
||||
o.ProfileEnd = buf.readUInt16LE(pos);
|
||||
o.ProfileEnd = Utils.unpackEndCut(buf.readUInt16LE(pos));
|
||||
pos = pos + 2;
|
||||
o.ProfileHollow = buf.readUInt16LE(pos);
|
||||
o.ProfileHollow = Utils.unpackProfileHollow(buf.readUInt16LE(pos));
|
||||
pos = pos + 2;
|
||||
const textureEntryLength = buf.readUInt32LE(pos);
|
||||
pos = pos + 4;
|
||||
o.TextureEntry = new TextureEntry(buf.slice(pos, pos + textureEntryLength));
|
||||
o.TextureEntry = TextureEntry.from(buf.slice(pos, pos + textureEntryLength));
|
||||
pos = pos + textureEntryLength;
|
||||
|
||||
if (compressedflags & CompressedFlags.TextureAnimation)
|
||||
@@ -476,7 +476,7 @@ 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 = new TextureEntry(objectData.TextureEntry.slice(4));
|
||||
this.objects[localID].TextureEntry = TextureEntry.from(objectData.TextureEntry.slice(4));
|
||||
}
|
||||
this.insertIntoRtree(this.objects[localID]);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ObjectUpdatedEvent,
|
||||
PacketFlags,
|
||||
PCode,
|
||||
PrimFlags,
|
||||
Vector3
|
||||
} from '..';
|
||||
import {GameObject} from './public/GameObject';
|
||||
@@ -32,6 +33,8 @@ import {ObjectDeselectMessage} from './messages/ObjectDeselect';
|
||||
import {Quaternion} from './Quaternion';
|
||||
import {Subscription} from 'rxjs/internal/Subscription';
|
||||
import {ExtraParams} from './public/ExtraParams';
|
||||
import {ObjectPropertiesMessage} from './messages/ObjectProperties';
|
||||
import {SelectedObjectEvent} from '../events/SelectedObjectEvent';
|
||||
|
||||
export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
@@ -45,7 +48,9 @@ export class ObjectStoreLite implements IObjectStore
|
||||
protected requestedObjects: {[key: number]: boolean} = {};
|
||||
protected deadObjects: number[] = [];
|
||||
protected persist = false;
|
||||
protected pendingObjectProperties: {[key: string]: any} = {};
|
||||
private physicsSubscription: Subscription;
|
||||
private selectedPrimsWithoutUpdate: {[key: number]: boolean} = {};
|
||||
|
||||
rtree?: RBush3D;
|
||||
|
||||
@@ -61,11 +66,28 @@ export class ObjectStoreLite implements IObjectStore
|
||||
Message.ObjectUpdateCached,
|
||||
Message.ObjectUpdateCompressed,
|
||||
Message.ImprovedTerseObjectUpdate,
|
||||
Message.ObjectProperties,
|
||||
Message.KillObject
|
||||
], async (packet: Packet) =>
|
||||
{
|
||||
switch (packet.message.id)
|
||||
{
|
||||
case Message.ObjectProperties:
|
||||
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 o = this.objects[obje];
|
||||
this.applyObjectProperties(o, obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.pendingObjectProperties[obj.ObjectID.toString()] = obj;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Message.ObjectUpdate:
|
||||
const objectUpdate = packet.message as ObjectUpdateMessage;
|
||||
this.objectUpdate(objectUpdate);
|
||||
@@ -102,6 +124,92 @@ export class ObjectStoreLite implements IObjectStore
|
||||
this.objects[evt.localID].friction = evt.friction;
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(() =>
|
||||
{
|
||||
let selectObjects = [];
|
||||
for (const key of Object.keys(this.selectedPrimsWithoutUpdate))
|
||||
{
|
||||
selectObjects.push(key);
|
||||
}
|
||||
function shuffle(a: any)
|
||||
{
|
||||
let j, x, i;
|
||||
for (i = a.length - 1; i > 0; i--)
|
||||
{
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
x = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
selectObjects = shuffle(selectObjects);
|
||||
if (selectObjects.length > 10)
|
||||
{
|
||||
selectObjects = selectObjects.slice(0, 20);
|
||||
}
|
||||
if (selectObjects.length > 0)
|
||||
{
|
||||
const selectObject = new ObjectSelectMessage();
|
||||
selectObject.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID
|
||||
};
|
||||
selectObject.ObjectData = [];
|
||||
for (const id of selectObjects)
|
||||
{
|
||||
selectObject.ObjectData.push({
|
||||
ObjectLocalID: id
|
||||
});
|
||||
}
|
||||
this.circuit.sendMessage(selectObject, PacketFlags.Reliable);
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
private applyObjectProperties(o: GameObject, obj: any)
|
||||
{
|
||||
if (this.selectedPrimsWithoutUpdate[o.ID])
|
||||
{
|
||||
delete this.selectedPrimsWithoutUpdate[o.ID];
|
||||
}
|
||||
o.creatorID = obj.CreatorID;
|
||||
o.creationDate = obj.CreationDate;
|
||||
o.baseMask = obj.BaseMask;
|
||||
o.ownerMask = obj.OwnerMask;
|
||||
o.groupMask = obj.GroupMask;
|
||||
o.everyoneMask = obj.EveryoneMask;
|
||||
o.nextOwnerMask = obj.NextOwnerMask;
|
||||
o.ownershipCost = obj.OwnershipCost;
|
||||
o.saleType = obj.SaleType;
|
||||
o.salePrice = obj.SalePrice;
|
||||
o.aggregatePerms = obj.AggregatePerms;
|
||||
o.aggregatePermTextures = obj.AggregatePermTextures;
|
||||
o.aggregatePermTexturesOwner = obj.AggregatePermTexturesOwner;
|
||||
o.category = obj.Category;
|
||||
o.inventorySerial = obj.InventorySerial;
|
||||
o.itemID = obj.ItemID;
|
||||
o.folderID = obj.FolderID;
|
||||
o.fromTaskID = obj.FromTaskID;
|
||||
o.groupID = obj.GroupID;
|
||||
o.lastOwnerID = obj.LastOwnerID;
|
||||
o.name = Utils.BufferToStringSimple(obj.Name);
|
||||
o.description = Utils.BufferToStringSimple(obj.Description);
|
||||
o.touchName = Utils.BufferToStringSimple(obj.TouchName);
|
||||
o.sitName = Utils.BufferToStringSimple(obj.SitName);
|
||||
o.textureID = Utils.BufferToStringSimple(obj.TextureID);
|
||||
o.resolvedAt = new Date().getTime() / 1000;
|
||||
|
||||
if (o.Flags !== undefined)
|
||||
{
|
||||
if (o.Flags & PrimFlags.CreateSelected)
|
||||
{
|
||||
const evt = new SelectedObjectEvent();
|
||||
evt.object = o;
|
||||
this.clientEvents.onSelectedObjectEvent.next(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async requestMissingObject(localID: number, attempt = 0)
|
||||
@@ -302,6 +410,10 @@ export class ObjectStoreLite implements IObjectStore
|
||||
newObj.localID = obj.ID;
|
||||
newObj.objectID = obj.FullID;
|
||||
newObj.object = obj;
|
||||
if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties[obj.FullID.toString()])
|
||||
{
|
||||
this.selectedPrimsWithoutUpdate[obj.ID] = true;
|
||||
}
|
||||
this.clientEvents.onNewObjectEvent.next(newObj);
|
||||
}
|
||||
else
|
||||
@@ -312,6 +424,11 @@ export class ObjectStoreLite implements IObjectStore
|
||||
updObj.object = obj;
|
||||
this.clientEvents.onObjectUpdatedEvent.next(updObj);
|
||||
}
|
||||
if (this.pendingObjectProperties[obj.FullID.toString()])
|
||||
{
|
||||
this.applyObjectProperties(obj, this.pendingObjectProperties[obj.FullID.toString()]);
|
||||
delete this.pendingObjectProperties[obj.FullID.toString()];
|
||||
}
|
||||
}
|
||||
|
||||
protected objectUpdateCached(objectUpdateCached: ObjectUpdateCachedMessage)
|
||||
|
||||
@@ -64,9 +64,17 @@ export class Quaternion extends quat
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(buf?: Buffer | number[] | Quaternion, pos?: number)
|
||||
constructor(buf?: Buffer | number[] | Quaternion | quat, pos?: number)
|
||||
{
|
||||
if (buf instanceof Quaternion)
|
||||
if (buf instanceof quat)
|
||||
{
|
||||
super();
|
||||
this.x = buf.x;
|
||||
this.y = buf.y;
|
||||
this.z = buf.z;
|
||||
this.w = buf.z;
|
||||
}
|
||||
else if (buf instanceof Quaternion)
|
||||
{
|
||||
super();
|
||||
this.x = buf.x;
|
||||
@@ -106,4 +114,32 @@ export class Quaternion extends quat
|
||||
{
|
||||
return '<' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + '>';
|
||||
}
|
||||
getBuffer(): Buffer
|
||||
{
|
||||
const j = Buffer.allocUnsafe(12);
|
||||
this.writeToBuffer(j, 0);
|
||||
return j;
|
||||
}
|
||||
compareApprox(rot: Quaternion): boolean
|
||||
{
|
||||
return this.angleBetween(rot) < 0.0001 || rot.equals(this, 0.0001);
|
||||
}
|
||||
angleBetween(b: Quaternion): number
|
||||
{
|
||||
const a = this;
|
||||
const aa = (a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w);
|
||||
const bb = (b.x * b.x + b.y * b.y + b.z * b.z + b.w * b.w);
|
||||
const aa_bb = aa * bb;
|
||||
if (aa_bb === 0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
const ab = (a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
|
||||
const quotient = (ab * ab) / aa_bb;
|
||||
if (quotient >= 1.0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
return Math.acos(2 * quotient - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {SkyPreset} from './public/interfaces/SkyPreset';
|
||||
import {Vector4} from './Vector4';
|
||||
import {WaterPreset} from './public/interfaces/WaterPreset';
|
||||
import {ClientCommands} from './ClientCommands';
|
||||
import {SimulatorViewerTimeMessageMessage} from './messages/SimulatorViewerTimeMessage';
|
||||
|
||||
export class Region
|
||||
{
|
||||
@@ -119,6 +120,8 @@ export class Region
|
||||
|
||||
environment: RegionEnvironment;
|
||||
|
||||
timeOffset = 0;
|
||||
|
||||
static IDCTColumn16(linein: number[], lineout: number[], column: number)
|
||||
{
|
||||
let total: number;
|
||||
@@ -421,12 +424,14 @@ export class Region
|
||||
});
|
||||
|
||||
this.messageSubscription = this.circuit.subscribeToMessages([
|
||||
Message.LayerData
|
||||
Message.LayerData,
|
||||
Message.SimulatorViewerTimeMessage
|
||||
], (packet: Packet) =>
|
||||
{
|
||||
switch (packet.message.id)
|
||||
{
|
||||
case Message.LayerData:
|
||||
{
|
||||
const layerData: LayerDataMessage = packet.message as LayerDataMessage;
|
||||
const type: LayerType = layerData.LayerID.Type;
|
||||
|
||||
@@ -576,6 +581,14 @@ export class Region
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Message.SimulatorViewerTimeMessage:
|
||||
{
|
||||
const msg = packet.message as SimulatorViewerTimeMessageMessage;
|
||||
const timeStamp = msg.TimeInfo.UsecSinceStart.toNumber() / 1000000;
|
||||
this.timeOffset = (new Date().getTime() / 1000) - timeStamp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import {Utils} from './Utils';
|
||||
|
||||
export class TextureEntry
|
||||
{
|
||||
static MAX_UINT32 = 4294967295;
|
||||
defaultTexture: TextureEntryFace | null;
|
||||
faces: TextureEntryFace[] = [];
|
||||
binary: Buffer;
|
||||
|
||||
static readFaceBitfield(buf: Buffer, pos: number): {
|
||||
result: boolean,
|
||||
@@ -27,9 +27,19 @@ export class TextureEntry
|
||||
return result;
|
||||
}
|
||||
let b = 0;
|
||||
let outputValue = false;
|
||||
let str = '0x';
|
||||
do
|
||||
{
|
||||
b = buf.readUInt8(result.pos);
|
||||
if (b === 0x81)
|
||||
{
|
||||
outputValue = true;
|
||||
}
|
||||
if (outputValue)
|
||||
{
|
||||
str += b.toString(16);
|
||||
}
|
||||
result.faceBits = (result.faceBits << 7) | (b & 0x7F);
|
||||
result.bitfieldSize += 7;
|
||||
result.pos++;
|
||||
@@ -39,22 +49,51 @@ export class TextureEntry
|
||||
return result;
|
||||
}
|
||||
|
||||
constructor(buf: Buffer)
|
||||
static getFaceBitfieldBuffer(bitfield: number): Buffer
|
||||
{
|
||||
this.binary = buf;
|
||||
let byteLength = 0;
|
||||
let tmpBitfield = bitfield;
|
||||
while (tmpBitfield !== 0)
|
||||
{
|
||||
tmpBitfield >>= 7;
|
||||
byteLength++;
|
||||
}
|
||||
|
||||
|
||||
if (byteLength === 0)
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(1);
|
||||
buf[0] = 0;
|
||||
return buf;
|
||||
}
|
||||
const bytes = Buffer.allocUnsafe(byteLength);
|
||||
for (let i = 0; i < byteLength; i++)
|
||||
{
|
||||
bytes[i] = ((bitfield >> (7 * (byteLength - i - 1))) & 0x7F);
|
||||
if (i < byteLength - 1)
|
||||
{
|
||||
bytes[i] |= 0x80;
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static from(buf: Buffer)
|
||||
{
|
||||
const te = new TextureEntry();
|
||||
if (buf.length < 16)
|
||||
{
|
||||
this.defaultTexture = null;
|
||||
te.defaultTexture = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.defaultTexture = new TextureEntryFace(null);
|
||||
te.defaultTexture = new TextureEntryFace(null);
|
||||
const pos = 0;
|
||||
let i = pos;
|
||||
|
||||
// Texture
|
||||
{
|
||||
this.defaultTexture.textureID = new UUID(buf, i);
|
||||
te.defaultTexture.textureID = new UUID(buf, i);
|
||||
i += 16;
|
||||
|
||||
let done = false;
|
||||
@@ -71,8 +110,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].textureID = uuid;
|
||||
te.createFace(face);
|
||||
te.faces[face].textureID = uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +120,7 @@ export class TextureEntry
|
||||
|
||||
// Colour
|
||||
{
|
||||
this.defaultTexture.rgba = new Color4(buf, i, true);
|
||||
te.defaultTexture.rgba = new Color4(buf, i, true);
|
||||
i += 4;
|
||||
|
||||
let done = false;
|
||||
@@ -98,8 +137,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].rgba = tmpColor;
|
||||
te.createFace(face);
|
||||
te.faces[face].rgba = tmpColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +147,7 @@ export class TextureEntry
|
||||
|
||||
// RepeatU
|
||||
{
|
||||
this.defaultTexture.repeatU = buf.readFloatLE(i);
|
||||
te.defaultTexture.repeatU = buf.readFloatLE(i);
|
||||
i += 4;
|
||||
|
||||
let done = false;
|
||||
@@ -125,8 +164,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].repeatU = tmpFloat;
|
||||
te.createFace(face);
|
||||
te.faces[face].repeatU = tmpFloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +174,7 @@ export class TextureEntry
|
||||
|
||||
// RepeatV
|
||||
{
|
||||
this.defaultTexture.repeatV = buf.readFloatLE(i);
|
||||
te.defaultTexture.repeatV = buf.readFloatLE(i);
|
||||
i += 4;
|
||||
|
||||
let done = false;
|
||||
@@ -152,8 +191,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].repeatV = tmpFloat;
|
||||
te.createFace(face);
|
||||
te.faces[face].repeatV = tmpFloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +201,7 @@ export class TextureEntry
|
||||
|
||||
// OffsetU
|
||||
{
|
||||
this.defaultTexture.offsetU = Utils.ReadOffsetFloat(buf, i);
|
||||
te.defaultTexture.offsetU = Utils.ReadOffsetFloat(buf, i);
|
||||
i += 2;
|
||||
|
||||
let done = false;
|
||||
@@ -179,8 +218,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].offsetU = tmpFloat;
|
||||
te.createFace(face);
|
||||
te.faces[face].offsetU = tmpFloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +228,7 @@ export class TextureEntry
|
||||
|
||||
// OffsetV
|
||||
{
|
||||
this.defaultTexture.offsetV = Utils.ReadOffsetFloat(buf, i);
|
||||
te.defaultTexture.offsetV = Utils.ReadOffsetFloat(buf, i);
|
||||
i += 2;
|
||||
|
||||
let done = false;
|
||||
@@ -206,8 +245,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].offsetV = tmpFloat;
|
||||
te.createFace(face);
|
||||
te.faces[face].offsetV = tmpFloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,7 +255,7 @@ export class TextureEntry
|
||||
|
||||
// Rotation
|
||||
{
|
||||
this.defaultTexture.rotation = Utils.ReadRotationFloat(buf, i);
|
||||
te.defaultTexture.rotation = Utils.ReadRotationFloat(buf, i);
|
||||
i += 2;
|
||||
|
||||
let done = false;
|
||||
@@ -233,8 +272,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].rotation = tmpFloat;
|
||||
te.createFace(face);
|
||||
te.faces[face].rotation = tmpFloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +282,7 @@ export class TextureEntry
|
||||
|
||||
// Material
|
||||
{
|
||||
this.defaultTexture.material = buf[i++];
|
||||
te.defaultTexture.material = buf[i++];
|
||||
|
||||
let done = false;
|
||||
while (!done)
|
||||
@@ -258,8 +297,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].material = tmpByte;
|
||||
te.createFace(face);
|
||||
te.faces[face].material = tmpByte;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,7 +307,7 @@ export class TextureEntry
|
||||
|
||||
// Media
|
||||
{
|
||||
this.defaultTexture.media = buf[i++];
|
||||
te.defaultTexture.media = buf[i++];
|
||||
|
||||
let done = false;
|
||||
while (i - pos < buf.length && !done)
|
||||
@@ -283,8 +322,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].media = tmpByte;
|
||||
te.createFace(face);
|
||||
te.faces[face].media = tmpByte;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,7 +332,7 @@ export class TextureEntry
|
||||
|
||||
// Glow
|
||||
{
|
||||
this.defaultTexture.glow = Utils.ReadGlowFloat(buf, i++);
|
||||
te.defaultTexture.glow = Utils.ReadGlowFloat(buf, i++);
|
||||
|
||||
let done = false;
|
||||
while (!done)
|
||||
@@ -308,8 +347,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].glow = tmpFloat;
|
||||
te.createFace(face);
|
||||
te.faces[face].glow = tmpFloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,7 +360,7 @@ export class TextureEntry
|
||||
const len = i - pos + 16;
|
||||
if (i - pos + 16 <= buf.length)
|
||||
{
|
||||
this.defaultTexture.materialID = new UUID(buf, i);
|
||||
te.defaultTexture.materialID = new UUID(buf, i);
|
||||
i += 16;
|
||||
|
||||
let done = false;
|
||||
@@ -338,8 +377,8 @@ export class TextureEntry
|
||||
{
|
||||
if ((result.faceBits & bit) !== 0)
|
||||
{
|
||||
this.createFace(face);
|
||||
this.faces[face].materialID = uuid;
|
||||
te.createFace(face);
|
||||
te.faces[face].materialID = uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,6 +386,11 @@ export class TextureEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
return te;
|
||||
}
|
||||
|
||||
constructor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -361,4 +405,235 @@ export class TextureEntry
|
||||
this.faces.push(new TextureEntryFace(this.defaultTexture));
|
||||
}
|
||||
}
|
||||
|
||||
toBuffer(): Buffer
|
||||
{
|
||||
if (this.defaultTexture === null)
|
||||
{
|
||||
return Buffer.allocUnsafe(0);
|
||||
}
|
||||
const textures: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const rgbas: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const repeatus: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const repeatvs: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const offsetus: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const offsetvs: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const rotations: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const materials: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const medias: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const glows: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
const materialIDs: number[] = Utils.fillArray<number>(TextureEntry.MAX_UINT32, this.faces.length);
|
||||
|
||||
for (let i = 0; i < this.faces.length; i++)
|
||||
{
|
||||
if (this.faces[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.faces[i].textureID.equals(this.defaultTexture.textureID))
|
||||
{
|
||||
if (textures[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
textures[i] = 0;
|
||||
}
|
||||
textures[i] |= (1 << i);
|
||||
}
|
||||
if (!this.faces[i].rgba.equals(this.defaultTexture.rgba))
|
||||
{
|
||||
if (rgbas[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
rgbas[i] = 0;
|
||||
}
|
||||
rgbas[i] |= (1 << i);
|
||||
}
|
||||
if (this.faces[i].repeatU !== this.defaultTexture.repeatU)
|
||||
{
|
||||
if (repeatus[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
repeatus[i] = 0;
|
||||
}
|
||||
repeatus[i] |= (1 << i);
|
||||
}
|
||||
if (this.faces[i].repeatV !== this.defaultTexture.repeatV)
|
||||
{
|
||||
if (repeatvs[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
repeatvs[i] = 0;
|
||||
}
|
||||
repeatvs[i] |= (1 << i);
|
||||
}
|
||||
if (Utils.TEOffsetShort(this.faces[i].offsetU) !== Utils.TEOffsetShort(this.defaultTexture.offsetU))
|
||||
{
|
||||
if (offsetus[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
offsetus[i] = 0;
|
||||
}
|
||||
offsetus[i] |= (1 << i);
|
||||
}
|
||||
if (Utils.TEOffsetShort(this.faces[i].offsetV) !== Utils.TEOffsetShort(this.defaultTexture.offsetV))
|
||||
{
|
||||
if (offsetvs[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
offsetvs[i] = 0;
|
||||
}
|
||||
offsetvs[i] |= (1 << i);
|
||||
}
|
||||
if (Utils.TERotationShort(this.faces[i].rotation) !== Utils.TERotationShort(this.defaultTexture.rotation))
|
||||
{
|
||||
if (rotations[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
rotations[i] = 0;
|
||||
}
|
||||
rotations[i] |= (1 << i);
|
||||
}
|
||||
if (this.faces[i].material !== this.defaultTexture.material)
|
||||
{
|
||||
if (materials[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
materials[i] = 0;
|
||||
}
|
||||
materials[i] |= (1 << i);
|
||||
}
|
||||
if (this.faces[i].media !== this.defaultTexture.media)
|
||||
{
|
||||
if (medias[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
medias[i] = 0;
|
||||
}
|
||||
medias[i] |= (1 << i);
|
||||
}
|
||||
if (Utils.TEGlowByte(this.faces[i].glow) !== Utils.TEGlowByte(this.defaultTexture.glow))
|
||||
{
|
||||
if (glows[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
glows[i] = 0;
|
||||
}
|
||||
glows[i] |= (1 << i);
|
||||
}
|
||||
if (!this.faces[i].materialID.equals(this.defaultTexture.materialID))
|
||||
{
|
||||
if (materialIDs[i] === TextureEntry.MAX_UINT32)
|
||||
{
|
||||
materialIDs[i] = 0;
|
||||
}
|
||||
materialIDs[i] |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
// Textures
|
||||
this.getChunks( chunks, textures, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return face.textureID.getBuffer();
|
||||
});
|
||||
// Colour
|
||||
this.getChunks(chunks, rgbas, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return face.rgba.getBuffer(true);
|
||||
});
|
||||
// RepeatU
|
||||
this.getChunks( chunks, repeatus, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToFloatBuffer(face.repeatU);
|
||||
});
|
||||
// RepeatV
|
||||
this.getChunks(chunks, repeatvs, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToFloatBuffer(face.repeatV);
|
||||
});
|
||||
// OffsetU
|
||||
this.getChunks( chunks, offsetus, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToShortBuffer(Utils.TEOffsetShort(face.offsetU));
|
||||
});
|
||||
// OffsetV
|
||||
this.getChunks( chunks, offsetvs, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToShortBuffer(Utils.TEOffsetShort(face.offsetV));
|
||||
});
|
||||
// Rotation
|
||||
this.getChunks( chunks, rotations, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToShortBuffer(Utils.TERotationShort(face.rotation));
|
||||
});
|
||||
// Material
|
||||
this.getChunks( chunks, materials, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToByteBuffer(face.material);
|
||||
});
|
||||
// Media
|
||||
this.getChunks( chunks, medias, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToByteBuffer(face.media);
|
||||
});
|
||||
// Glows
|
||||
this.getChunks( chunks, glows, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return Utils.NumberToByteBuffer(Utils.TEGlowByte(face.glow));
|
||||
});
|
||||
// MaterialID
|
||||
this.getChunks(chunks, materialIDs, (face: TextureEntryFace): Buffer =>
|
||||
{
|
||||
return face.materialID.getBuffer();
|
||||
});
|
||||
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
|
||||
getChunks(chunks: Buffer[], items: number[], func: (item: TextureEntryFace) => Buffer)
|
||||
{
|
||||
if (this.defaultTexture !== null)
|
||||
{
|
||||
if (chunks.length > 0)
|
||||
{
|
||||
// Finish off the last chunk
|
||||
const zero = Buffer.allocUnsafe(1);
|
||||
zero[0] = 0;
|
||||
chunks.push(zero);
|
||||
}
|
||||
chunks.push(func(this.defaultTexture));
|
||||
const existingChunks: {
|
||||
buf: Buffer,
|
||||
bitfield: number
|
||||
}[] = [];
|
||||
for (let i = items.length - 1; i > -1; i--)
|
||||
{
|
||||
if (items[i] !== TextureEntry.MAX_UINT32)
|
||||
{
|
||||
const bitField = items[i];
|
||||
const buf = func(this.faces[i]);
|
||||
|
||||
let found = false;
|
||||
for (const ch of existingChunks)
|
||||
{
|
||||
if (ch.buf.compare(buf) === 0)
|
||||
{
|
||||
ch.bitfield = ch.bitfield | bitField;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
existingChunks.push({
|
||||
bitfield: bitField,
|
||||
buf: buf
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const chunk of existingChunks)
|
||||
{
|
||||
chunks.push(TextureEntry.getFaceBitfieldBuffer(chunk.bitfield));
|
||||
chunks.push(chunk.buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toBase64(): string
|
||||
{
|
||||
return this.toBuffer().toString('base64');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,31 +13,28 @@ export class TextureEntryFace
|
||||
static MEDIA_MASK = 0x01;
|
||||
static TEX_MAP_MASK = 0x06;
|
||||
|
||||
textureID: UUID;
|
||||
rgba: Color4;
|
||||
repeatU: number;
|
||||
repeatV: number;
|
||||
offsetU: number;
|
||||
offsetV: number;
|
||||
rotation: number;
|
||||
glow: number;
|
||||
materialID: UUID;
|
||||
bumpiness: Bumpiness = Bumpiness.None;
|
||||
shininess: Shininess = Shininess.None;
|
||||
mappingType: MappingType = MappingType.Default;
|
||||
fullBright = false;
|
||||
mediaFlags = false;
|
||||
private _textureID: UUID;
|
||||
private _rgba: Color4;
|
||||
private _repeatU: number;
|
||||
private _repeatV: number;
|
||||
private _offsetU: number;
|
||||
private _offsetV: number;
|
||||
private _rotation: number;
|
||||
private _glow: number;
|
||||
private _materialID: UUID;
|
||||
private _bumpiness: Bumpiness = Bumpiness.None;
|
||||
private _shininess: Shininess = Shininess.None;
|
||||
private _mappingType: MappingType = MappingType.Default;
|
||||
private _fullBright = false;
|
||||
private _mediaFlags = false;
|
||||
|
||||
private materialb: number;
|
||||
private mediab: number;
|
||||
private _material: number;
|
||||
private _media: number;
|
||||
private hasAttribute: TextureFlags;
|
||||
private defaultTexture: TextureEntryFace | null;
|
||||
|
||||
constructor(def: TextureEntryFace | null)
|
||||
{
|
||||
this.rgba = Color4.white;
|
||||
this.repeatU = 1.0;
|
||||
this.repeatV = 1.0;
|
||||
this.defaultTexture = def;
|
||||
if (this.defaultTexture == null)
|
||||
{
|
||||
@@ -49,49 +46,213 @@ export class TextureEntryFace
|
||||
}
|
||||
}
|
||||
|
||||
get rgba(): Color4
|
||||
{
|
||||
if (this._rgba === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.rgba;
|
||||
}
|
||||
return this._rgba;
|
||||
}
|
||||
|
||||
set rgba(value: Color4)
|
||||
{
|
||||
this._rgba = value;
|
||||
}
|
||||
|
||||
get repeatU(): number
|
||||
{
|
||||
if (this._repeatU === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.repeatU;
|
||||
}
|
||||
return this._repeatU;
|
||||
}
|
||||
|
||||
set repeatU(value: number)
|
||||
{
|
||||
this._repeatU = value;
|
||||
}
|
||||
|
||||
get repeatV(): number
|
||||
{
|
||||
if (this._repeatV === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.repeatV;
|
||||
}
|
||||
return this._repeatV;
|
||||
}
|
||||
|
||||
set repeatV(value: number)
|
||||
{
|
||||
this._repeatV = value;
|
||||
}
|
||||
|
||||
get offsetU(): number
|
||||
{
|
||||
if (this._offsetU === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.offsetU;
|
||||
}
|
||||
return this._offsetU;
|
||||
}
|
||||
|
||||
set offsetU(value: number)
|
||||
{
|
||||
this._offsetU = value;
|
||||
}
|
||||
|
||||
get offsetV(): number
|
||||
{
|
||||
if (this._offsetV === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.offsetV;
|
||||
}
|
||||
return this._offsetV;
|
||||
}
|
||||
|
||||
set offsetV(value: number)
|
||||
{
|
||||
this._offsetV = value;
|
||||
}
|
||||
|
||||
get rotation(): number
|
||||
{
|
||||
if (this._rotation === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.rotation;
|
||||
}
|
||||
return this._rotation;
|
||||
}
|
||||
|
||||
set rotation(value: number)
|
||||
{
|
||||
this._rotation = value;
|
||||
}
|
||||
|
||||
get glow(): number
|
||||
{
|
||||
if (this._glow === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.glow
|
||||
}
|
||||
return this._glow;
|
||||
}
|
||||
|
||||
set glow(value: number)
|
||||
{
|
||||
this._glow = value;
|
||||
}
|
||||
|
||||
get textureID(): UUID
|
||||
{
|
||||
if (this._textureID === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.textureID;
|
||||
}
|
||||
return this._textureID;
|
||||
}
|
||||
|
||||
set textureID(value: UUID)
|
||||
{
|
||||
this._textureID = value;
|
||||
}
|
||||
|
||||
get materialID(): UUID
|
||||
{
|
||||
if (this._materialID === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.materialID;
|
||||
}
|
||||
return this._materialID;
|
||||
}
|
||||
|
||||
set materialID(value: UUID)
|
||||
{
|
||||
this._materialID = value;
|
||||
}
|
||||
|
||||
get material(): number
|
||||
{
|
||||
return this.materialb;
|
||||
if (this._material === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.material;
|
||||
}
|
||||
return this._material;
|
||||
}
|
||||
|
||||
set material(material: number)
|
||||
{
|
||||
this.materialb = material;
|
||||
this._material = material;
|
||||
if ((this.hasAttribute & TextureFlags.Material) !== 0)
|
||||
{
|
||||
this.bumpiness = this.materialb & TextureEntryFace.BUMP_MASK;
|
||||
this.shininess = this.materialb & TextureEntryFace.SHINY_MASK;
|
||||
this.fullBright = ((this.materialb & TextureEntryFace.FULLBRIGHT_MASK) !== 0);
|
||||
this._bumpiness = this._material & TextureEntryFace.BUMP_MASK;
|
||||
this._shininess = this._material & TextureEntryFace.SHINY_MASK;
|
||||
this._fullBright = ((this._material & TextureEntryFace.FULLBRIGHT_MASK) !== 0);
|
||||
}
|
||||
else if (this.defaultTexture !== null)
|
||||
{
|
||||
this.bumpiness = this.defaultTexture.bumpiness;
|
||||
this.shininess = this.defaultTexture.shininess;
|
||||
this.fullBright = this.defaultTexture.fullBright;
|
||||
this._bumpiness = this.defaultTexture._bumpiness;
|
||||
this._shininess = this.defaultTexture._shininess;
|
||||
this._fullBright = this.defaultTexture._fullBright;
|
||||
}
|
||||
}
|
||||
|
||||
get media(): number
|
||||
{
|
||||
return this.mediab;
|
||||
if (this._media === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.media;
|
||||
}
|
||||
return this._media;
|
||||
}
|
||||
|
||||
set media(media: number)
|
||||
{
|
||||
this.mediab = media;
|
||||
this._media = media;
|
||||
if ((this.hasAttribute & TextureFlags.Media) !== 0)
|
||||
{
|
||||
this.mappingType = media & TextureEntryFace.TEX_MAP_MASK;
|
||||
this.mediaFlags = ((media & TextureEntryFace.MEDIA_MASK) !== 0);
|
||||
this._mappingType = media & TextureEntryFace.TEX_MAP_MASK;
|
||||
this._mediaFlags = ((media & TextureEntryFace.MEDIA_MASK) !== 0);
|
||||
}
|
||||
else if (this.defaultTexture !== null)
|
||||
{
|
||||
this.mappingType = this.defaultTexture.mappingType;
|
||||
this.mediaFlags = this.defaultTexture.mediaFlags;
|
||||
this._mappingType = this.defaultTexture.mappingType;
|
||||
this._mediaFlags = this.defaultTexture.mediaFlags;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('No media attribute and default texture is null');
|
||||
}
|
||||
}
|
||||
|
||||
get mappingType(): number
|
||||
{
|
||||
if (this._mappingType === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.mappingType;
|
||||
}
|
||||
return this._mappingType;
|
||||
}
|
||||
|
||||
set mappingType(value: number)
|
||||
{
|
||||
this._mappingType = value;
|
||||
}
|
||||
|
||||
get mediaFlags(): boolean
|
||||
{
|
||||
if (this._mediaFlags === undefined && this.defaultTexture !== null)
|
||||
{
|
||||
return this.defaultTexture.mediaFlags;
|
||||
}
|
||||
return this._mediaFlags;
|
||||
}
|
||||
|
||||
set mediaFlags(value: boolean)
|
||||
{
|
||||
this._mediaFlags = value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -162,4 +162,17 @@ export class UUID
|
||||
}
|
||||
return new UUID(buf3, 0);
|
||||
}
|
||||
|
||||
public CRC(): number
|
||||
{
|
||||
let retval = 0;
|
||||
const bytes: Buffer = this.getBuffer();
|
||||
|
||||
retval += ((bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]);
|
||||
retval += ((bytes[7] << 24) + (bytes[6] << 16) + (bytes[5] << 8) + bytes[4]);
|
||||
retval += ((bytes[11] << 24) + (bytes[10] << 16) + (bytes[9] << 8) + bytes[8]);
|
||||
retval += ((bytes[15] << 24) + (bytes[14] << 16) + (bytes[13] << 8) + bytes[12]);
|
||||
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import * as Long from 'long';
|
||||
import {GlobalPosition, HTTPAssets} from '..';
|
||||
import {GlobalPosition, HTTPAssets, Vector3} from '..';
|
||||
import {Quaternion} from './Quaternion';
|
||||
|
||||
export class Utils
|
||||
{
|
||||
static TWO_PI = 6.283185307179586476925286766559;
|
||||
static CUT_QUANTA = 0.00002;
|
||||
static SCALE_QUANTA = 0.01;
|
||||
static SHEAR_QUANTA = 0.01;
|
||||
static TAPER_QUANTA = 0.01;
|
||||
static REV_QUANTA = 0.015;
|
||||
static HOLLOW_QUANTA = 0.00002;
|
||||
|
||||
static StringToBuffer(str: string): Buffer
|
||||
{
|
||||
return Buffer.from(str + '\0', 'utf8');
|
||||
}
|
||||
|
||||
static BufferToStringSimple(buf: Buffer, startPos?: number): string
|
||||
{
|
||||
if (buf.length === 0)
|
||||
@@ -22,12 +32,24 @@ export class Utils
|
||||
return buf.toString('utf8');
|
||||
}
|
||||
}
|
||||
|
||||
static Clamp(value: number, min: number, max: number)
|
||||
{
|
||||
value = (value > max) ? max : value;
|
||||
value = (value < min) ? min : value;
|
||||
return value;
|
||||
}
|
||||
|
||||
static fillArray<T>(value: T, count: number)
|
||||
{
|
||||
const arr: T[] = new Array<T>(count);
|
||||
while (count--)
|
||||
{
|
||||
arr[count] = value;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
static JSONStringify(obj: object, space: number)
|
||||
{
|
||||
const cache: any[] = [];
|
||||
@@ -51,11 +73,12 @@ export class Utils
|
||||
return value;
|
||||
}, space);
|
||||
}
|
||||
|
||||
static BufferToString(buf: Buffer, startPos?: number):
|
||||
{
|
||||
readLength: number,
|
||||
result: string
|
||||
}
|
||||
{
|
||||
readLength: number,
|
||||
result: string
|
||||
}
|
||||
{
|
||||
if (buf.length === 0)
|
||||
{
|
||||
@@ -143,6 +166,7 @@ export class Utils
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
static FloatToByte(val: number, lower: number, upper: number)
|
||||
{
|
||||
val = Utils.Clamp(val, lower, upper);
|
||||
@@ -150,6 +174,7 @@ export class Utils
|
||||
val /= (upper - lower);
|
||||
return Math.round(val * 255);
|
||||
}
|
||||
|
||||
static ByteToFloat(byte: number, lower: number, upper: number)
|
||||
{
|
||||
const ONE_OVER_BYTEMAX: number = 1.0 / 255;
|
||||
@@ -182,16 +207,19 @@ export class Utils
|
||||
}
|
||||
return fval;
|
||||
}
|
||||
|
||||
static Base64EncodeString(str: string): string
|
||||
{
|
||||
const buff = new Buffer(str, 'utf8');
|
||||
return buff.toString('base64');
|
||||
}
|
||||
|
||||
static Base64DecodeString(str: string): string
|
||||
{
|
||||
const buff = new Buffer(str, 'base64');
|
||||
return buff.toString('utf8');
|
||||
}
|
||||
|
||||
static HexToLong(hex: string)
|
||||
{
|
||||
while (hex.length < 16)
|
||||
@@ -200,17 +228,224 @@ export class Utils
|
||||
}
|
||||
return new Long(parseInt(hex.substr(8), 16), parseInt(hex.substr(0, 8), 16));
|
||||
}
|
||||
|
||||
static ReadRotationFloat(buf: Buffer, pos: number): number
|
||||
{
|
||||
return ((buf[pos] | (buf[pos + 1] << 8)) / 32768.0) * (2 * Math.PI);
|
||||
return ((buf[pos] | (buf[pos + 1] << 8)) / 32768.0) * Utils.TWO_PI;
|
||||
}
|
||||
|
||||
static ReadGlowFloat(buf: Buffer, pos: number): number
|
||||
{
|
||||
return buf[pos] / 255;
|
||||
}
|
||||
|
||||
static ReadOffsetFloat(buf: Buffer, pos: number): number
|
||||
{
|
||||
const offset = buf.readInt16LE(pos);
|
||||
return offset / 32767.0;
|
||||
}
|
||||
|
||||
static TEOffsetShort(num: number)
|
||||
{
|
||||
num = Utils.Clamp(num, -1.0, 1.0);
|
||||
num *= 32767.0;
|
||||
return Math.round(num);
|
||||
}
|
||||
|
||||
static IEEERemainder(x: number, y: number)
|
||||
{
|
||||
if (isNaN(x))
|
||||
{
|
||||
return x; // IEEE 754-2008: NaN payload must be preserved
|
||||
}
|
||||
if (isNaN(y))
|
||||
{
|
||||
return y; // IEEE 754-2008: NaN payload must be preserved
|
||||
}
|
||||
const regularMod = x % y;
|
||||
if (isNaN(regularMod))
|
||||
{
|
||||
return NaN;
|
||||
}
|
||||
if (regularMod === 0)
|
||||
{
|
||||
if (Math.sign(x) < 0)
|
||||
{
|
||||
return -0;
|
||||
}
|
||||
}
|
||||
const alternativeResult = regularMod - (Math.abs(y) * Math.sign(x));
|
||||
if (Math.abs(alternativeResult) === Math.abs(regularMod))
|
||||
{
|
||||
const divisionResult = x / y;
|
||||
const roundedResult = Math.round(divisionResult);
|
||||
if (Math.abs(roundedResult) > Math.abs(divisionResult))
|
||||
{
|
||||
return alternativeResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
return regularMod;
|
||||
}
|
||||
}
|
||||
if (Math.abs(alternativeResult) < Math.abs(regularMod))
|
||||
{
|
||||
return alternativeResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
return regularMod;
|
||||
}
|
||||
}
|
||||
|
||||
static TERotationShort(rotation: number)
|
||||
{
|
||||
return Math.floor(((Utils.IEEERemainder(rotation, Utils.TWO_PI) / Utils.TWO_PI) * 32768.0) + 0.5);
|
||||
}
|
||||
|
||||
static TEGlowByte(glow: number)
|
||||
{
|
||||
return (glow * 255.0);
|
||||
}
|
||||
|
||||
static NumberToByteBuffer(num: number): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(1);
|
||||
buf.writeUInt8(num, 0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static NumberToShortBuffer(num: number): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(2);
|
||||
buf.writeInt16LE(num, 0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static NumberToFloatBuffer(num: number): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(4);
|
||||
buf.writeFloatLE(num, 0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static numberOrZero(num: number | undefined): number
|
||||
{
|
||||
if (num === undefined)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
static vector3OrZero(vec: Vector3 | undefined): Vector3
|
||||
{
|
||||
if (vec === undefined)
|
||||
{
|
||||
return Vector3.getZero();
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static quaternionOrZero(quat: Quaternion | undefined): Quaternion
|
||||
{
|
||||
if (quat === undefined)
|
||||
{
|
||||
return Quaternion.getIdentity();
|
||||
}
|
||||
return quat;
|
||||
}
|
||||
|
||||
static packBeginCut(beginCut: number): number
|
||||
{
|
||||
return Math.round(beginCut / Utils.CUT_QUANTA);
|
||||
}
|
||||
|
||||
static packEndCut(endCut: number): number
|
||||
{
|
||||
return (50000 - Math.round(endCut / Utils.CUT_QUANTA));
|
||||
}
|
||||
|
||||
static packPathScale(pathScale: number): number
|
||||
{
|
||||
return (200 - Math.round(pathScale / Utils.SCALE_QUANTA));
|
||||
}
|
||||
|
||||
static packPathShear(pathShear: number): number
|
||||
{
|
||||
return Math.round(pathShear / Utils.SHEAR_QUANTA);
|
||||
}
|
||||
|
||||
static packPathTwist(pathTwist: number): number
|
||||
{
|
||||
return Math.round(pathTwist / Utils.SCALE_QUANTA);
|
||||
}
|
||||
|
||||
static packPathTaper(pathTaper: number): number
|
||||
{
|
||||
return Math.round(pathTaper / Utils.TAPER_QUANTA);
|
||||
}
|
||||
|
||||
static packPathRevolutions(pathRevolutions: number): number
|
||||
{
|
||||
return Math.round((pathRevolutions - 1) / Utils.REV_QUANTA);
|
||||
}
|
||||
|
||||
static packProfileHollow(profileHollow: number): number
|
||||
{
|
||||
return Math.round(profileHollow / Utils.HOLLOW_QUANTA);
|
||||
}
|
||||
|
||||
static unpackBeginCut(beginCut: number): number
|
||||
{
|
||||
return beginCut * Utils.CUT_QUANTA;
|
||||
}
|
||||
|
||||
static unpackEndCut(endCut: number): number
|
||||
{
|
||||
return (50000 - endCut) * Utils.CUT_QUANTA;
|
||||
}
|
||||
|
||||
static unpackPathScale(pathScale: number): number
|
||||
{
|
||||
return (200 - pathScale) * Utils.SCALE_QUANTA;
|
||||
}
|
||||
|
||||
static unpackPathShear(pathShear: number): number
|
||||
{
|
||||
return pathShear * Utils.SHEAR_QUANTA;
|
||||
}
|
||||
|
||||
static unpackPathTwist(pathTwist: number): number
|
||||
{
|
||||
return pathTwist * Utils.SCALE_QUANTA;
|
||||
}
|
||||
|
||||
static unpackPathTaper(pathTaper: number): number
|
||||
{
|
||||
return pathTaper * Utils.TAPER_QUANTA;
|
||||
}
|
||||
|
||||
static unpackPathRevolutions(pathRevolutions: number): number
|
||||
{
|
||||
return pathRevolutions * Utils.REV_QUANTA + 1;
|
||||
}
|
||||
|
||||
static unpackProfileHollow(profileHollow: number): number
|
||||
{
|
||||
return profileHollow * Utils.HOLLOW_QUANTA;
|
||||
}
|
||||
|
||||
static nullTerminatedString(str: string)
|
||||
{
|
||||
const index = str.indexOf('\0');
|
||||
if (index === -1)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
else
|
||||
{
|
||||
return str.substr(0, index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,4 +65,8 @@ export class Vector2 extends vec2
|
||||
{
|
||||
return '<' + this.x + ', ' + this.y + '>';
|
||||
}
|
||||
toArray()
|
||||
{
|
||||
return [this.x, this.y];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,16 @@ export class Vector3 extends vec3
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(buf?: Buffer | number[] | Vector3, pos?: number, double?: boolean)
|
||||
constructor(buf?: Buffer | number[] | Vector3 | vec3, pos?: number, double?: boolean)
|
||||
{
|
||||
if (buf instanceof Vector3)
|
||||
if (buf instanceof vec3)
|
||||
{
|
||||
super();
|
||||
this.x = buf.x;
|
||||
this.y = buf.y;
|
||||
this.z = buf.z;
|
||||
}
|
||||
else if (buf instanceof Vector3)
|
||||
{
|
||||
super();
|
||||
this.x = buf.x;
|
||||
@@ -117,6 +124,18 @@ export class Vector3 extends vec3
|
||||
{
|
||||
return '<' + this.x + ', ' + this.y + ', ' + this.z + '>';
|
||||
}
|
||||
|
||||
|
||||
getBuffer(double: boolean = false): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe((double) ? 24 : 12);
|
||||
this.writeToBuffer(buf, 0, double);
|
||||
return buf;
|
||||
}
|
||||
compareApprox(vec: Vector3)
|
||||
{
|
||||
return vec.equals(this, 0.00001);
|
||||
}
|
||||
toArray()
|
||||
{
|
||||
return [this.x, this.y, this.z];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ import {CommandsBase} from './CommandsBase';
|
||||
import {UUID} from '../UUID';
|
||||
import * as LLSD from '@caspertech/llsd';
|
||||
import {Utils} from '../Utils';
|
||||
import {AssetType, HTTPAssets, PacketFlags} from '../..';
|
||||
import {AssetType, FolderType, HTTPAssets, LLMesh, Material, PacketFlags, TransferStatus} from '../..';
|
||||
import {PermissionMask} from '../../enums/PermissionMask';
|
||||
import * as zlib from 'zlib';
|
||||
import {ZlibOptions} from 'zlib';
|
||||
import {Material} from '../public/Material';
|
||||
import {Color4} from '../Color4';
|
||||
import {TransferRequestMessage} from '../messages/TransferRequest';
|
||||
import {TransferChannelType} from '../../enums/TransferChannelType';
|
||||
@@ -16,7 +15,6 @@ import {Message} from '../../enums/Message';
|
||||
import {Packet} from '../Packet';
|
||||
import {TransferPacketMessage} from '../messages/TransferPacket';
|
||||
import {TransferAbortMessage} from '../messages/TransferAbort';
|
||||
import {TransferStatus} from '../../enums/TransferStatus';
|
||||
|
||||
export class AssetCommands extends CommandsBase
|
||||
{
|
||||
@@ -205,7 +203,12 @@ export class AssetCommands extends CommandsBase
|
||||
return;
|
||||
}
|
||||
const binData = new LLSD.Binary(Array.from(reslt), 'BASE64');
|
||||
const obj = LLSD.LLSD.parseBinary(binData);
|
||||
const llsdResult = LLSD.LLSD.parseBinary(binData);
|
||||
let obj = [];
|
||||
if (llsdResult.result)
|
||||
{
|
||||
obj = llsdResult.result;
|
||||
}
|
||||
if (obj.length > 0)
|
||||
{
|
||||
for (const mat of obj)
|
||||
@@ -313,6 +316,83 @@ export class AssetCommands extends CommandsBase
|
||||
}
|
||||
}
|
||||
|
||||
async uploadMesh(name: string, description: string, mesh: Buffer, confirmCostCallback: (cost: number) => boolean): Promise<UUID>
|
||||
{
|
||||
const decodedMesh = await LLMesh.from(mesh);
|
||||
if (!decodedMesh.creatorID.equals(this.agent.agentID) && !decodedMesh.creatorID.equals(UUID.zero()))
|
||||
{
|
||||
throw new Error('Unable to upload - copyright violation');
|
||||
}
|
||||
const faces = [];
|
||||
const faceCount = decodedMesh.lodLevels['high_lod'].length;
|
||||
for (let x = 0; x < faceCount; x++)
|
||||
{
|
||||
faces.push({
|
||||
'diffuse_color': [1.000000000000001, 1.000000000000001, 1.000000000000001, 1.000000000000001],
|
||||
'fullbright': false
|
||||
});
|
||||
}
|
||||
const prim = {
|
||||
'face_list': faces,
|
||||
'position': [0.000000000000001, 0.000000000000001, 0.000000000000001],
|
||||
'rotation': [0.000000000000001, 0.000000000000001, 0.000000000000001, 1.000000000000001],
|
||||
'scale': [2.000000000000001, 2.000000000000001, 2.000000000000001],
|
||||
'material': 3,
|
||||
'physics_shape_type': 2,
|
||||
'mesh': 0
|
||||
};
|
||||
const assetResources = {
|
||||
'instance_list': [prim],
|
||||
'mesh_list': [new LLSD.Binary(Array.from(mesh))],
|
||||
'texture_list': [],
|
||||
'metric': 'MUT_Unspecified'
|
||||
};
|
||||
const uploadMap = {
|
||||
'name': name,
|
||||
'description': description,
|
||||
'asset_resources': assetResources,
|
||||
'asset_type': 'mesh',
|
||||
'inventory_type': 'object',
|
||||
'folder_id': new LLSD.UUID(await this.agent.inventory.findFolderForType(FolderType.Object)),
|
||||
'texture_folder_id': new LLSD.UUID(await this.agent.inventory.findFolderForType(FolderType.Texture)),
|
||||
'everyone_mask': PermissionMask.All,
|
||||
'group_mask': PermissionMask.All,
|
||||
'next_owner_mask': PermissionMask.All
|
||||
};
|
||||
const result = await this.currentRegion.caps.capsRequestXML('NewFileAgentInventory', uploadMap);
|
||||
if (result['state'] === 'upload' && result['upload_price'])
|
||||
{
|
||||
const cost = result['upload_price'];
|
||||
if (await confirmCostCallback(cost))
|
||||
{
|
||||
const uploader = result['uploader'];
|
||||
const uploadResult = await this.currentRegion.caps.capsPerformXMLRequest(uploader, assetResources);
|
||||
if (uploadResult['new_inventory_item'] && uploadResult['new_asset'])
|
||||
{
|
||||
const inventoryItem = new UUID(uploadResult['new_inventory_item'].toString());
|
||||
const item = await this.agent.inventory.fetchInventoryItem(inventoryItem);
|
||||
if (item !== null)
|
||||
{
|
||||
item.assetID = new UUID(uploadResult['new_asset'].toString());
|
||||
}
|
||||
return inventoryItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
throw new Error('Upload failed - no new inventory item returned');
|
||||
}
|
||||
}
|
||||
throw new Error('Upload cost declined')
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(result);
|
||||
console.log(JSON.stringify(result.error));
|
||||
throw new Error('Upload failed');
|
||||
}
|
||||
}
|
||||
|
||||
uploadAsset(type: HTTPAssets, data: Buffer, name: string, description: string): Promise<UUID>
|
||||
{
|
||||
return new Promise<UUID>((resolve, reject) =>
|
||||
@@ -325,9 +405,9 @@ export class AssetCommands extends CommandsBase
|
||||
'inventory_type': Utils.HTTPAssetTypeToInventoryType(type),
|
||||
'name': name,
|
||||
'description': description,
|
||||
'everyone_mask': (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19),
|
||||
'group_mask': (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19),
|
||||
'next_owner_mask': (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19),
|
||||
'everyone_mask': PermissionMask.All,
|
||||
'group_mask': PermissionMask.All,
|
||||
'next_owner_mask': PermissionMask.All,
|
||||
'expected_upload_cost': 0
|
||||
}).then((response: any) =>
|
||||
{
|
||||
@@ -342,11 +422,40 @@ export class AssetCommands extends CommandsBase
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
else if (response['error'])
|
||||
{
|
||||
reject(response['error']['message']);
|
||||
}
|
||||
else
|
||||
{
|
||||
reject('Unable to upload asset');
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
console.log('Got err');
|
||||
console.log(err);
|
||||
reject(err);
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this.agent)
|
||||
{
|
||||
throw(new Error('Missing agent'));
|
||||
}
|
||||
else if (!this.agent.inventory)
|
||||
{
|
||||
throw(new Error('Missing agent inventory'));
|
||||
}
|
||||
else if (!this.agent.inventory.main)
|
||||
{
|
||||
throw new Error('Missing agent inventory main skeleton');
|
||||
}
|
||||
else if (!this.agent.inventory.main.root)
|
||||
{
|
||||
throw new Error('Missing agent inventory main skeleton root');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,19 @@ import {Packet} from '../Packet';
|
||||
import {OnlineNotificationMessage} from '../messages/OnlineNotification';
|
||||
import {OfflineNotificationMessage} from '../messages/OfflineNotification';
|
||||
import {TerminateFriendshipMessage} from '../messages/TerminateFriendship';
|
||||
import {AssetType, Friend, FriendOnlineEvent, FriendRemovedEvent, FriendRequestEvent, FriendRightsEvent, MapInfoReplyEvent, MapLocation, PacketFlags, RightsFlags, UUID, Vector3} from '../..';
|
||||
import {
|
||||
FolderType,
|
||||
Friend,
|
||||
FriendOnlineEvent,
|
||||
FriendRemovedEvent,
|
||||
FriendRequestEvent,
|
||||
FriendRightsEvent,
|
||||
MapLocation,
|
||||
PacketFlags,
|
||||
RightsFlags,
|
||||
UUID,
|
||||
Vector3
|
||||
} from '../..';
|
||||
import {AcceptFriendshipMessage} from '../messages/AcceptFriendship';
|
||||
import {ImprovedInstantMessageMessage} from '../messages/ImprovedInstantMessage';
|
||||
import {InstantMessageDialog} from '../../enums/InstantMessageDialog';
|
||||
@@ -249,7 +261,7 @@ export class FriendCommands extends CommandsBase
|
||||
accept.FolderData = [];
|
||||
accept.FolderData.push(
|
||||
{
|
||||
'FolderID': this.agent.inventory.findFolderForType(AssetType.CallingCard)
|
||||
'FolderID': this.agent.inventory.findFolderForType(FolderType.CallingCard)
|
||||
}
|
||||
);
|
||||
const sequenceNo = this.circuit.sendMessage(accept, PacketFlags.Reliable);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import {CommandsBase} from './CommandsBase';
|
||||
import {InventoryFolder} from '../InventoryFolder';
|
||||
import {ChatSourceType, InventoryOfferedEvent, PacketFlags, UUID, Vector3} from '../..';
|
||||
import {AssetType, ChatSourceType, InventoryOfferedEvent, PacketFlags, UUID, Vector3} from '../..';
|
||||
import {InstantMessageDialog} from '../../enums/InstantMessageDialog';
|
||||
import {ImprovedInstantMessageMessage} from '../messages/ImprovedInstantMessage';
|
||||
import {Utils} from '../Utils';
|
||||
import {InventoryType} from '../../enums/InventoryType';
|
||||
import {FolderType} from '../../enums/FolderType';
|
||||
|
||||
export class InventoryCommands extends CommandsBase
|
||||
{
|
||||
@@ -19,8 +21,8 @@ export class InventoryCommands extends CommandsBase
|
||||
{
|
||||
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
|
||||
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
|
||||
|
||||
const folder = this.agent.inventory.findFolderForType(event.type);
|
||||
const folderType = (event.type as unknown) as FolderType;
|
||||
const folder = this.agent.inventory.findFolderForType(folderType);
|
||||
const binary = Buffer.allocUnsafe(16);
|
||||
folder.writeToBuffer(binary, 0);
|
||||
|
||||
|
||||
@@ -5,19 +5,26 @@ import {RegionHandleRequestMessage} from '../messages/RegionHandleRequest';
|
||||
import {Message} from '../../enums/Message';
|
||||
import {FilterResponse} from '../../enums/FilterResponse';
|
||||
import {RegionIDAndHandleReplyMessage} from '../messages/RegionIDAndHandleReply';
|
||||
import {AssetType, PacketFlags, PCode, Vector3} from '../..';
|
||||
import {
|
||||
AssetType,
|
||||
GameObject,
|
||||
InventoryItemFlags,
|
||||
NewObjectEvent,
|
||||
PacketFlags,
|
||||
Parcel,
|
||||
PCode,
|
||||
PrimFlags,
|
||||
Vector3
|
||||
} from '../..';
|
||||
import {ObjectGrabMessage} from '../messages/ObjectGrab';
|
||||
import {ObjectDeGrabMessage} from '../messages/ObjectDeGrab';
|
||||
import {ObjectGrabUpdateMessage} from '../messages/ObjectGrabUpdate';
|
||||
import {GameObject} from '../public/GameObject';
|
||||
import {ObjectSelectMessage} from '../messages/ObjectSelect';
|
||||
import {ObjectPropertiesMessage} from '../messages/ObjectProperties';
|
||||
import {Utils} from '../Utils';
|
||||
import {ObjectDeselectMessage} from '../messages/ObjectDeselect';
|
||||
import * as micromatch from 'micromatch';
|
||||
import * as LLSD from '@caspertech/llsd';
|
||||
import {PrimFlags} from '../../enums/PrimFlags';
|
||||
import {Parcel} from '../public/Parcel';
|
||||
import {ParcelPropertiesRequestMessage} from '../messages/ParcelPropertiesRequest';
|
||||
import {RequestTaskInventoryMessage} from '../messages/RequestTaskInventory';
|
||||
import {ReplyTaskInventoryMessage} from '../messages/ReplyTaskInventory';
|
||||
@@ -25,8 +32,14 @@ import {InventoryItem} from '../InventoryItem';
|
||||
import {AssetTypeLL} from '../../enums/AssetTypeLL';
|
||||
import {SaleTypeLL} from '../../enums/SaleTypeLL';
|
||||
import {InventoryTypeLL} from '../../enums/InventoryTypeLL';
|
||||
import {ObjectAddMessage} from '../messages/ObjectAdd';
|
||||
import {Quaternion} from '../Quaternion';
|
||||
import Timer = NodeJS.Timer;
|
||||
import {NewObjectEvent} from '../../events/NewObjectEvent';
|
||||
import {RezObjectMessage} from '../messages/RezObject';
|
||||
import {PermissionMask} from '../../enums/PermissionMask';
|
||||
import {from} from 'rxjs';
|
||||
import {SelectedObjectEvent} from '../../events/SelectedObjectEvent';
|
||||
import uuid = require('uuid');
|
||||
|
||||
export class RegionCommands extends CommandsBase
|
||||
{
|
||||
@@ -305,7 +318,7 @@ export class RegionCommands extends CommandsBase
|
||||
return this.currentRegion.regionName;
|
||||
}
|
||||
|
||||
private async resolveObjects(objects: GameObject[], onlyUnresolved: boolean = false)
|
||||
private async resolveObjects(objects: GameObject[], onlyUnresolved: boolean = false, skipInventory = false)
|
||||
{
|
||||
// First, create a map of all object IDs
|
||||
const objs: {[key: number]: GameObject} = {};
|
||||
@@ -396,7 +409,7 @@ export class RegionCommands extends CommandsBase
|
||||
{
|
||||
count++;
|
||||
const ky = parseInt(k, 10);
|
||||
if (objs[ky] !== undefined)
|
||||
if (objs[ky] !== undefined && !skipInventory)
|
||||
{
|
||||
const o = objs[ky];
|
||||
if ((o.resolveAttempts === undefined || o.resolveAttempts < 3) && o.FullID !== undefined && o.name !== undefined && o.Flags !== undefined && !(o.Flags & PrimFlags.InventoryEmpty) && (!o.inventory || o.inventory.length === 0))
|
||||
@@ -786,7 +799,8 @@ export class RegionCommands extends CommandsBase
|
||||
resolve(event.object);
|
||||
}
|
||||
});
|
||||
tmr = setTimeout(() => {
|
||||
tmr = setTimeout(() =>
|
||||
{
|
||||
subscription.unsubscribe();
|
||||
reject(new Error('Timeout'));
|
||||
}, timeout)
|
||||
@@ -810,13 +824,272 @@ export class RegionCommands extends CommandsBase
|
||||
resolve(event.object);
|
||||
}
|
||||
});
|
||||
tmr = setTimeout(() => {
|
||||
tmr = setTimeout(() =>
|
||||
{
|
||||
subscription.unsubscribe();
|
||||
reject(new Error('Timeout'));
|
||||
}, timeout)
|
||||
});
|
||||
}
|
||||
|
||||
private async buildPart(obj: GameObject, posOffset: Vector3, meshCallback: (object: GameObject, meshData: UUID) => UUID | null)
|
||||
{
|
||||
// Rez a prim
|
||||
let newObject: GameObject;
|
||||
if (obj.extraParams !== undefined && obj.extraParams.meshData !== null)
|
||||
{
|
||||
const inventoryID: UUID | null = await meshCallback(obj, obj.extraParams.meshData.meshData);
|
||||
if (inventoryID !== null)
|
||||
{
|
||||
newObject = await this.createPrim(obj, posOffset, inventoryID);
|
||||
}
|
||||
else
|
||||
{
|
||||
newObject = await this.createPrim(obj, posOffset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newObject = await this.createPrim(obj, posOffset);
|
||||
}
|
||||
await newObject.setExtraParams(obj.extraParams);
|
||||
if (obj.TextureEntry !== undefined)
|
||||
{
|
||||
await newObject.setTextureEntry(obj.TextureEntry);
|
||||
}
|
||||
if (obj.name !== undefined)
|
||||
{
|
||||
await newObject.setName(obj.name);
|
||||
}
|
||||
if (obj.description !== undefined)
|
||||
{
|
||||
await newObject.setDescription(obj.description);
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
|
||||
buildObject(obj: GameObject, meshCallback: (object: GameObject, meshData: UUID) => UUID | null): Promise<GameObject>
|
||||
{
|
||||
return new Promise<GameObject>(async (resolve, reject) =>
|
||||
{
|
||||
|
||||
const parts = [];
|
||||
console.log('Rezzing root prim');
|
||||
parts.push(this.buildPart(obj, Vector3.getZero(), meshCallback));
|
||||
console.log('Building child prims');
|
||||
if (obj.children && obj.Position)
|
||||
{
|
||||
for (const child of obj.children)
|
||||
{
|
||||
parts.push(this.buildPart(child, obj.Position, meshCallback));
|
||||
}
|
||||
}
|
||||
Promise.all(parts).then(async (results) =>
|
||||
{
|
||||
console.log('Linking prims');
|
||||
const rootObj = results[0];
|
||||
for (const childObject of results)
|
||||
{
|
||||
if (childObject !== rootObj)
|
||||
{
|
||||
await childObject.linkTo(rootObj);
|
||||
}
|
||||
}
|
||||
console.log('All done');
|
||||
resolve(rootObj);
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private echo(st: string): boolean
|
||||
{
|
||||
//console.log(st);
|
||||
return true;
|
||||
}
|
||||
|
||||
createPrim(obj: GameObject, posOffset: Vector3, inventoryID?: UUID): Promise<GameObject>
|
||||
{
|
||||
console.log('Create prim');
|
||||
return new Promise(async (resolve, reject) =>
|
||||
{
|
||||
const timeRequested = (new Date().getTime() / 1000) - this.currentRegion.timeOffset;
|
||||
|
||||
if (obj.Position === undefined)
|
||||
{
|
||||
obj.Position = Vector3.getZero();
|
||||
}
|
||||
if (obj.Rotation === undefined)
|
||||
{
|
||||
obj.Rotation = Quaternion.getIdentity();
|
||||
}
|
||||
let finalPos = Vector3.getZero();
|
||||
let finalRot = Quaternion.getIdentity();
|
||||
if (posOffset.x === 0.0 && posOffset.y === 0.0 && posOffset.z === 0.0)
|
||||
{
|
||||
finalPos = obj.Position;
|
||||
finalRot = obj.Rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
const finalPosOffset: Vector3 = obj.Position;
|
||||
finalPos = new Vector3(new Vector3(finalPosOffset).add(new Vector3(posOffset)));
|
||||
finalRot = obj.Rotation;
|
||||
}
|
||||
let msg: ObjectAddMessage | RezObjectMessage | null = null;
|
||||
let fromInventory = false;
|
||||
if (inventoryID === undefined || this.agent.inventory.itemsByID[inventoryID.toString()] === undefined)
|
||||
{
|
||||
console.log('Regular prim');
|
||||
// First, rez object in scene
|
||||
msg = new ObjectAddMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID,
|
||||
GroupID: UUID.zero()
|
||||
};
|
||||
msg.ObjectData = {
|
||||
PCode: Utils.numberOrZero(obj.PCode),
|
||||
Material: Utils.numberOrZero(obj.Material),
|
||||
AddFlags: PrimFlags.CreateSelected,
|
||||
PathCurve: Utils.numberOrZero(obj.PathCurve),
|
||||
ProfileCurve: Utils.numberOrZero(obj.ProfileCurve),
|
||||
PathBegin: Utils.packBeginCut(Utils.numberOrZero(obj.PathBegin)),
|
||||
PathEnd: Utils.packEndCut(Utils.numberOrZero(obj.PathEnd)),
|
||||
PathScaleX: Utils.packPathScale(Utils.numberOrZero(obj.PathScaleX)),
|
||||
PathScaleY: Utils.packPathScale(Utils.numberOrZero(obj.PathScaleY)),
|
||||
PathShearX: Utils.packPathShear(Utils.numberOrZero(obj.PathShearX)),
|
||||
PathShearY: Utils.packPathShear(Utils.numberOrZero(obj.PathShearY)),
|
||||
PathTwist: Utils.packPathTwist(Utils.numberOrZero(obj.PathTwist)),
|
||||
PathTwistBegin: Utils.packPathTwist(Utils.numberOrZero(obj.PathTwistBegin)),
|
||||
PathRadiusOffset: Utils.packPathTwist(Utils.numberOrZero(obj.PathRadiusOffset)),
|
||||
PathTaperX: Utils.packPathTaper(Utils.numberOrZero(obj.PathTaperX)),
|
||||
PathTaperY: Utils.packPathTaper(Utils.numberOrZero(obj.PathTaperY)),
|
||||
PathRevolutions: Utils.packPathRevolutions(Utils.numberOrZero(obj.PathRevolutions)),
|
||||
PathSkew: Utils.packPathTwist(Utils.numberOrZero(obj.PathSkew)),
|
||||
ProfileBegin: Utils.packBeginCut(Utils.numberOrZero(obj.ProfileBegin)),
|
||||
ProfileEnd: Utils.packEndCut(Utils.numberOrZero(obj.ProfileEnd)),
|
||||
ProfileHollow: Utils.packProfileHollow(Utils.numberOrZero(obj.ProfileHollow)),
|
||||
BypassRaycast: 1,
|
||||
RayStart: finalPos,
|
||||
RayEnd: finalPos,
|
||||
RayTargetID: UUID.zero(),
|
||||
RayEndIsIntersection: 0,
|
||||
Scale: Utils.vector3OrZero(obj.Scale),
|
||||
Rotation: finalRot,
|
||||
State: Utils.numberOrZero(obj.State)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('Rezzing ' + this.agent.inventory.itemsByID[inventoryID.toString()].name);
|
||||
fromInventory = true;
|
||||
const invItem = this.agent.inventory.itemsByID[inventoryID.toString()];
|
||||
const queryID = UUID.random();
|
||||
msg = new RezObjectMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID,
|
||||
GroupID: UUID.zero()
|
||||
};
|
||||
msg.RezData = {
|
||||
FromTaskID: UUID.zero(),
|
||||
BypassRaycast: 1,
|
||||
RayStart: finalPos,
|
||||
RayEnd: finalPos,
|
||||
RayTargetID: UUID.zero(),
|
||||
RayEndIsIntersection: false,
|
||||
RezSelected: true,
|
||||
RemoveItem: false,
|
||||
ItemFlags: invItem.flags,
|
||||
GroupMask: PermissionMask.All,
|
||||
EveryoneMask: PermissionMask.All,
|
||||
NextOwnerMask: PermissionMask.All,
|
||||
};
|
||||
msg.InventoryData = {
|
||||
ItemID: invItem.itemID,
|
||||
FolderID: invItem.parentID,
|
||||
CreatorID: invItem.permissions.creator,
|
||||
OwnerID: invItem.permissions.owner,
|
||||
GroupID: invItem.permissions.group,
|
||||
BaseMask: invItem.permissions.baseMask,
|
||||
OwnerMask: invItem.permissions.ownerMask,
|
||||
GroupMask: invItem.permissions.groupMask,
|
||||
EveryoneMask: invItem.permissions.everyoneMask,
|
||||
NextOwnerMask: invItem.permissions.nextOwnerMask,
|
||||
GroupOwned: false,
|
||||
TransactionID: queryID,
|
||||
Type: invItem.type,
|
||||
InvType: invItem.inventoryType,
|
||||
Flags: invItem.flags,
|
||||
SaleType: invItem.saleType,
|
||||
SalePrice: invItem.salePrice,
|
||||
Name: Utils.StringToBuffer(invItem.name),
|
||||
Description: Utils.StringToBuffer(invItem.description),
|
||||
CreationDate: Math.round(invItem.created.getTime() / 1000),
|
||||
CRC: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const objSub = this.currentRegion.clientEvents.onSelectedObjectEvent.subscribe(async (evt: SelectedObjectEvent) =>
|
||||
{
|
||||
if (evt.object.creatorID !== undefined &&
|
||||
evt.object.creatorID.equals(this.agent.agentID) &&
|
||||
evt.object.creationDate !== undefined &&
|
||||
!evt.object.claimedForBuild)
|
||||
{
|
||||
let claim = false;
|
||||
const creationDate = evt.object.creationDate.toNumber() / 1000000;
|
||||
if (fromInventory && inventoryID !== undefined && evt.object.itemID.equals(inventoryID))
|
||||
{
|
||||
claim = true;
|
||||
}
|
||||
else if (!fromInventory && evt.object.itemID.equals(UUID.zero()) && creationDate > timeRequested)
|
||||
{
|
||||
claim = true;
|
||||
}
|
||||
if (claim)
|
||||
{
|
||||
evt.object.claimedForBuild = true;
|
||||
objSub.unsubscribe();
|
||||
|
||||
if (!fromInventory)
|
||||
{
|
||||
await evt.object.setShape(obj.PathCurve, obj.ProfileCurve, obj.PathBegin, obj.PathEnd, obj.PathScaleX, obj.PathScaleY, obj.PathShearX, obj.PathShearY, obj.PathTwist, obj.PathTwistBegin, obj.PathRadiusOffset, obj.PathTaperX, obj.PathTaperY, obj.PathRevolutions, obj.PathSkew, obj.ProfileBegin, obj.ProfileEnd, obj.ProfileHollow);
|
||||
}
|
||||
|
||||
|
||||
// Set the object's position properly
|
||||
try
|
||||
{
|
||||
await evt.object.setGeometry(finalPos, finalRot, obj.Scale);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.log('Failed to set object position :/');
|
||||
}
|
||||
resolve(evt.object);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (obj.Position !== undefined && obj.Scale !== undefined)
|
||||
{
|
||||
// Move the camera to look directly at prim for faster capture
|
||||
const campos = new Vector3(finalPos);
|
||||
campos.z += 5.0 + obj.Scale.z;
|
||||
console.log('Moving camera to ' + campos.toString());
|
||||
await this.currentRegion.clientCommands.agent.setCamera(campos, finalPos, 4096, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0]));
|
||||
}
|
||||
if (msg !== null)
|
||||
{
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
console.log('Requested rez');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getObjectByLocalID(id: number, resolve: boolean, waitFor: number = 0)
|
||||
{
|
||||
let obj = null;
|
||||
|
||||
@@ -34,4 +34,10 @@ export class FlexibleData
|
||||
buf[pos++] = (this.Wind) * 10;
|
||||
this.Force.writeToBuffer(buf, pos, false);
|
||||
}
|
||||
getBuffer(): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(16);
|
||||
this.writeToBuffer(buf, 0);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,12 @@ import * as Long from 'long';
|
||||
import {IGameObjectData} from '../interfaces/IGameObjectData';
|
||||
import {
|
||||
HoleType,
|
||||
HTTPAssets,
|
||||
HTTPAssets, PacketFlags,
|
||||
PCode,
|
||||
PhysicsShapeType,
|
||||
PrimFlags,
|
||||
ProfileShape, SculptType,
|
||||
ProfileShape,
|
||||
SculptType,
|
||||
SoundFlags,
|
||||
Utils
|
||||
} from '../..';
|
||||
@@ -29,6 +30,16 @@ import {InventoryType} from '../../enums/InventoryType';
|
||||
import {LLWearable} from '../LLWearable';
|
||||
import {TextureAnim} from './TextureAnim';
|
||||
import {ExtraParams} from './ExtraParams';
|
||||
import {ObjectExtraParamsMessage} from '../messages/ObjectExtraParams';
|
||||
import {ExtraParamType} from '../../enums/ExtraParamType';
|
||||
import {ObjectImageMessage} from '../messages/ObjectImage';
|
||||
import {ObjectNameMessage} from '../messages/ObjectName';
|
||||
import {ObjectDescriptionMessage} from '../messages/ObjectDescription';
|
||||
import {ObjectPositionMessage} from '../messages/ObjectPosition';
|
||||
import {MultipleObjectUpdateMessage} from '../messages/MultipleObjectUpdate';
|
||||
import {UpdateType} from '../../enums/UpdateType';
|
||||
import {ObjectLinkMessage} from '../messages/ObjectLink';
|
||||
import {ObjectShapeMessage} from '../messages/ObjectShape';
|
||||
|
||||
export class GameObject implements IGameObjectData
|
||||
{
|
||||
@@ -139,11 +150,13 @@ export class GameObject implements IGameObjectData
|
||||
|
||||
resolveAttempts = 0;
|
||||
|
||||
claimedForBuild = false;
|
||||
|
||||
private static getFromXMLJS(obj: any, param: string): any
|
||||
{
|
||||
if (obj[param] === undefined)
|
||||
{
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
let retParam;
|
||||
if (Array.isArray(obj[param]))
|
||||
@@ -178,149 +191,149 @@ export class GameObject implements IGameObjectData
|
||||
const go = new GameObject();
|
||||
go.Flags = 0;
|
||||
let prop: any;
|
||||
if (this.getFromXMLJS(obj, 'AllowedDrop'))
|
||||
if (this.getFromXMLJS(obj, 'AllowedDrop') !== undefined)
|
||||
{
|
||||
go.Flags = go.Flags | PrimFlags.AllowInventoryDrop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'CreatorID'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'CreatorID')) !== undefined)
|
||||
{
|
||||
go.creatorID = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'FolderID'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'FolderID')) !== undefined)
|
||||
{
|
||||
go.folderID = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'InventorySerial'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'InventorySerial')) !== undefined)
|
||||
{
|
||||
go.inventorySerial = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'UUID'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'UUID')) !== undefined)
|
||||
{
|
||||
go.FullID = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'LocalId'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'LocalId')) !== undefined)
|
||||
{
|
||||
go.ID = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'Name'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'Name')) !== undefined)
|
||||
{
|
||||
go.name = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'Material'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'Material')) !== undefined)
|
||||
{
|
||||
go.Material = prop;
|
||||
}
|
||||
if (prop = Vector3.fromXMLJS(obj, 'GroupPosition'))
|
||||
if ((prop = Vector3.fromXMLJS(obj, 'GroupPosition')) !== undefined)
|
||||
{
|
||||
if (root)
|
||||
{
|
||||
go.Position = prop;
|
||||
}
|
||||
}
|
||||
if (prop = Vector3.fromXMLJS(obj, 'OffsetPosition'))
|
||||
if ((prop = Vector3.fromXMLJS(obj, 'OffsetPosition')) !== undefined)
|
||||
{
|
||||
if (!root)
|
||||
{
|
||||
go.Position = prop;
|
||||
}
|
||||
}
|
||||
if (prop = Quaternion.fromXMLJS(obj, 'RotationOffset'))
|
||||
if ((prop = Quaternion.fromXMLJS(obj, 'RotationOffset')) !== undefined)
|
||||
{
|
||||
go.Rotation = prop;
|
||||
}
|
||||
if (prop = Vector3.fromXMLJS(obj, 'Velocity'))
|
||||
if ((prop = Vector3.fromXMLJS(obj, 'Velocity')) !== undefined)
|
||||
{
|
||||
go.Velocity = prop;
|
||||
}
|
||||
if (prop = Vector3.fromXMLJS(obj, 'AngularVelocity'))
|
||||
if ((prop = Vector3.fromXMLJS(obj, 'AngularVelocity')) !== undefined)
|
||||
{
|
||||
go.AngularVelocity = prop;
|
||||
}
|
||||
if (prop = Vector3.fromXMLJS(obj, 'Acceleration'))
|
||||
if ((prop = Vector3.fromXMLJS(obj, 'Acceleration')) !== undefined)
|
||||
{
|
||||
go.Acceleration = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'Description'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'Description')) !== undefined)
|
||||
{
|
||||
go.description = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'Text'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'Text')) !== undefined)
|
||||
{
|
||||
go.Text = prop;
|
||||
}
|
||||
if (prop = Color4.fromXMLJS(obj, 'Color'))
|
||||
if ((prop = Color4.fromXMLJS(obj, 'Color')) !== undefined)
|
||||
{
|
||||
go.TextColor = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'SitName'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'SitName')) !== undefined)
|
||||
{
|
||||
go.sitName = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'TouchName'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'TouchName')) !== undefined)
|
||||
{
|
||||
go.touchName = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'ClickAction'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'ClickAction')) !== undefined)
|
||||
{
|
||||
go.ClickAction = prop;
|
||||
}
|
||||
if (prop = Vector3.fromXMLJS(obj, 'Scale'))
|
||||
if ((prop = Vector3.fromXMLJS(obj, 'Scale')) !== undefined)
|
||||
{
|
||||
go.Scale = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'ParentID'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'ParentID')) !== undefined)
|
||||
{
|
||||
go.ParentID = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'Category'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'Category')) !== undefined)
|
||||
{
|
||||
go.category = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'SalePrice'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'SalePrice')) !== undefined)
|
||||
{
|
||||
go.salePrice = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'ObjectSaleType'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'ObjectSaleType')) !== undefined)
|
||||
{
|
||||
go.saleType = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'OwnershipCost'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'OwnershipCost')) !== undefined)
|
||||
{
|
||||
go.ownershipCost = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'GroupID'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'GroupID')) !== undefined)
|
||||
{
|
||||
go.groupID = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'OwnerID'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'OwnerID')) !== undefined)
|
||||
{
|
||||
go.OwnerID = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'LastOwnerID'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'LastOwnerID')) !== undefined)
|
||||
{
|
||||
go.lastOwnerID = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'BaseMask'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'BaseMask')) !== undefined)
|
||||
{
|
||||
go.baseMask = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'OwnerMask'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'OwnerMask')) !== undefined)
|
||||
{
|
||||
go.ownerMask = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'GroupMask'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'GroupMask')) !== undefined)
|
||||
{
|
||||
go.groupMask = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'EveryoneMask'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'EveryoneMask')) !== undefined)
|
||||
{
|
||||
go.everyoneMask = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'NextOwnerMask'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'NextOwnerMask')) !== undefined)
|
||||
{
|
||||
go.nextOwnerMask = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'Flags'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'Flags')) !== undefined)
|
||||
{
|
||||
let flags = 0;
|
||||
if (typeof prop === 'string')
|
||||
@@ -337,121 +350,125 @@ export class GameObject implements IGameObjectData
|
||||
}
|
||||
go.Flags = flags;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'TextureAnimation'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'TextureAnimation')) !== undefined)
|
||||
{
|
||||
const buf = Buffer.from(prop, 'base64');
|
||||
go.textureAnim = TextureAnim.from(buf);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'ParticleSystem'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'ParticleSystem')) !== undefined)
|
||||
{
|
||||
const buf = Buffer.from(prop, 'base64');
|
||||
go.Particles = ParticleSystem.from(buf);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'PhysicsShapeType'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'PhysicsShapeType')) !== undefined)
|
||||
{
|
||||
go.physicsShapeType = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'SoundID'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'SoundID')) !== undefined)
|
||||
{
|
||||
go.Sound = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'SoundGain'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'SoundGain')) !== undefined)
|
||||
{
|
||||
go.SoundGain = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'SoundFlags'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'SoundFlags')) !== undefined)
|
||||
{
|
||||
go.SoundFlags = prop;
|
||||
}
|
||||
if (prop = UUID.fromXMLJS(obj, 'SoundRadius'))
|
||||
if ((prop = UUID.fromXMLJS(obj, 'SoundRadius')) !== undefined)
|
||||
{
|
||||
go.SoundRadius = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(obj, 'Shape'))
|
||||
if ((prop = this.getFromXMLJS(obj, 'Shape')) !== undefined)
|
||||
{
|
||||
const shape = prop;
|
||||
if (prop = this.getFromXMLJS(shape, 'ProfileCurve'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'ProfileCurve')) !== undefined)
|
||||
{
|
||||
go.ProfileCurve = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'TextureEntry'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'TextureEntry')) !== undefined)
|
||||
{
|
||||
const buf = Buffer.from(prop, 'base64');
|
||||
go.TextureEntry = new TextureEntry(buf);
|
||||
go.TextureEntry = TextureEntry.from(buf);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathBegin'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathBegin')) !== undefined)
|
||||
{
|
||||
go.PathBegin = prop;
|
||||
go.PathBegin = Utils.unpackBeginCut(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathCurve'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathCurve')) !== undefined)
|
||||
{
|
||||
go.PathCurve = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathEnd'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathEnd')) !== undefined)
|
||||
{
|
||||
go.PathEnd = prop;
|
||||
go.PathEnd = Utils.unpackEndCut(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathRadiusOffset'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathRadiusOffset')) !== undefined)
|
||||
{
|
||||
go.PathRadiusOffset = prop;
|
||||
go.PathRadiusOffset = Utils.unpackPathTwist(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathRevolutions'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathRevolutions')) !== undefined)
|
||||
{
|
||||
go.PathRevolutions = prop;
|
||||
go.PathRevolutions = Utils.unpackPathRevolutions(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathScaleX'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathScaleX')) !== undefined)
|
||||
{
|
||||
go.PathScaleX = prop;
|
||||
go.PathScaleX = Utils.unpackPathScale(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathScaleY'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathScaleY')) !== undefined)
|
||||
{
|
||||
go.PathScaleY = prop;
|
||||
go.PathScaleY = Utils.unpackPathScale(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathShearX'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathShearX')) !== undefined)
|
||||
{
|
||||
go.PathShearX = prop;
|
||||
go.PathShearX = Utils.unpackPathShear(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathSkew'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathShearY')) !== undefined)
|
||||
{
|
||||
go.PathSkew = prop;
|
||||
go.PathShearY = Utils.unpackPathShear(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathTaperX'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathSkew')) !== undefined)
|
||||
{
|
||||
go.PathTaperX = prop;
|
||||
go.PathSkew = Utils.unpackPathShear(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathTaperY'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathTaperX')) !== undefined)
|
||||
{
|
||||
go.PathTaperY = prop;
|
||||
go.PathTaperX = Utils.unpackPathTaper(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathTwist'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathTaperY')) !== undefined)
|
||||
{
|
||||
go.PathTwist = prop;
|
||||
go.PathTaperY = Utils.unpackPathTaper(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PathTwistBegin'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathTwist')) !== undefined)
|
||||
{
|
||||
go.PathTwistBegin = prop;
|
||||
go.PathTwist = Utils.unpackPathTwist(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'PCode'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'PathTwistBegin')) !== undefined)
|
||||
{
|
||||
go.PathTwistBegin = Utils.unpackPathTwist(prop);
|
||||
}
|
||||
if ((prop = this.getFromXMLJS(shape, 'PCode')) !== undefined)
|
||||
{
|
||||
go.PCode = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'ProfileBegin'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'ProfileBegin')) !== undefined)
|
||||
{
|
||||
go.ProfileBegin = prop;
|
||||
go.ProfileBegin = Utils.unpackBeginCut(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'ProfileEnd'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'ProfileEnd')) !== undefined)
|
||||
{
|
||||
go.ProfileEnd = prop;
|
||||
go.ProfileEnd = Utils.unpackEndCut(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'ProfileHollow'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'ProfileHollow')) !== undefined)
|
||||
{
|
||||
go.ProfileHollow = prop;
|
||||
go.ProfileHollow = Utils.unpackProfileHollow(prop);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'State'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'State')) !== undefined)
|
||||
{
|
||||
go.State = prop;
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'ProfileShape'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'ProfileShape')) !== undefined)
|
||||
{
|
||||
if (!go.ProfileCurve)
|
||||
{
|
||||
@@ -459,7 +476,7 @@ export class GameObject implements IGameObjectData
|
||||
}
|
||||
go.ProfileCurve = go.ProfileCurve | parseInt(ProfileShape[prop], 10);
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'HollowShape'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'HollowShape')) !== undefined)
|
||||
{
|
||||
if (!go.ProfileCurve)
|
||||
{
|
||||
@@ -467,7 +484,7 @@ export class GameObject implements IGameObjectData
|
||||
}
|
||||
go.ProfileCurve = go.ProfileCurve | parseInt(HoleType[prop], 10);
|
||||
}
|
||||
if (this.getFromXMLJS(shape, 'SculptEntry'))
|
||||
if (this.getFromXMLJS(shape, 'SculptEntry') !== undefined)
|
||||
{
|
||||
const type = this.getFromXMLJS(shape, 'SculptType');
|
||||
if (type !== false && type !== undefined)
|
||||
@@ -475,6 +492,10 @@ export class GameObject implements IGameObjectData
|
||||
const id = UUID.fromXMLJS(shape, 'SculptTexture');
|
||||
if (id instanceof UUID)
|
||||
{
|
||||
if (!go.extraParams)
|
||||
{
|
||||
go.extraParams = new ExtraParams();
|
||||
}
|
||||
if (type & SculptType.Mesh)
|
||||
{
|
||||
go.extraParams.setMeshData(type, id);
|
||||
@@ -486,7 +507,7 @@ export class GameObject implements IGameObjectData
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.getFromXMLJS(shape, 'FlexiEntry'))
|
||||
if (this.getFromXMLJS(shape, 'FlexiEntry') !== undefined)
|
||||
{
|
||||
const flexiSoftness = this.getFromXMLJS(shape, 'FlexiSoftness');
|
||||
const flexiTension = this.getFromXMLJS(shape, 'FlexiTension');
|
||||
@@ -505,10 +526,14 @@ export class GameObject implements IGameObjectData
|
||||
flexiForceY !== false &&
|
||||
flexiForceZ !== false)
|
||||
{
|
||||
if (!go.extraParams)
|
||||
{
|
||||
go.extraParams = new ExtraParams();
|
||||
}
|
||||
go.extraParams.setFlexiData(flexiSoftness, flexiTension, flexiDrag, flexiGravity, flexiWind, new Vector3([flexiForceX, flexiForceY, flexiForceZ]));
|
||||
}
|
||||
}
|
||||
if (this.getFromXMLJS(shape, 'LightEntry'))
|
||||
if (this.getFromXMLJS(shape, 'LightEntry') !== undefined)
|
||||
{
|
||||
const lightColorR = this.getFromXMLJS(shape, 'LightColorR');
|
||||
const lightColorG = this.getFromXMLJS(shape, 'LightColorG');
|
||||
@@ -527,6 +552,10 @@ export class GameObject implements IGameObjectData
|
||||
lightFalloff !== false &&
|
||||
lightIntensity !== false)
|
||||
{
|
||||
if (!go.extraParams)
|
||||
{
|
||||
go.extraParams = new ExtraParams();
|
||||
}
|
||||
go.extraParams.setLightData(
|
||||
new Color4(lightColorR, lightColorG, lightColorB, lightColorA),
|
||||
lightRadius,
|
||||
@@ -536,18 +565,14 @@ export class GameObject implements IGameObjectData
|
||||
);
|
||||
}
|
||||
}
|
||||
if (prop = this.getFromXMLJS(shape, 'ExtraParams'))
|
||||
if ((prop = this.getFromXMLJS(shape, 'ExtraParams')) !== undefined)
|
||||
{
|
||||
const buf = Buffer.from(prop, 'base64');
|
||||
go.extraParams = ExtraParams.from(buf);
|
||||
}
|
||||
}
|
||||
// TODO: TaskInventory
|
||||
|
||||
|
||||
|
||||
console.log('BURP');
|
||||
process.exit(0);
|
||||
return go;
|
||||
}
|
||||
|
||||
static fromXML(xml: string)
|
||||
@@ -572,7 +597,21 @@ export class GameObject implements IGameObjectData
|
||||
throw new Error('Root part not found');
|
||||
}
|
||||
const rootPart = GameObject.partFromXMLJS(result['SceneObjectPart'][0], true);
|
||||
|
||||
rootPart.children = [];
|
||||
rootPart.totalChildren = 0;
|
||||
if (result['OtherParts'] && Array.isArray(result['OtherParts']) && result['OtherParts'].length > 0)
|
||||
{
|
||||
const obj = result['OtherParts'][0];
|
||||
if (obj['SceneObjectPart'])
|
||||
{
|
||||
for (const part of obj['SceneObjectPart'])
|
||||
{
|
||||
rootPart.children.push(GameObject.partFromXMLJS(part, false));
|
||||
rootPart.totalChildren++;
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(rootPart);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -603,6 +642,359 @@ export class GameObject implements IGameObjectData
|
||||
return '';
|
||||
}
|
||||
|
||||
setIfDefined(def?: number, v?: number)
|
||||
{
|
||||
if (def === undefined)
|
||||
{
|
||||
def = 0;
|
||||
}
|
||||
if (v === undefined)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
else
|
||||
{
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
async setShape(PathCurve?: number,
|
||||
ProfileCurve?: number,
|
||||
PathBegin?: number,
|
||||
PathEnd?: number,
|
||||
PathScaleX?: number,
|
||||
PathScaleY?: number,
|
||||
PathShearX?: number,
|
||||
PathShearY?: number,
|
||||
PathTwist?: number,
|
||||
PathTwistBegin?: number,
|
||||
PathRadiusOffset?: number,
|
||||
PathTaperX?: number,
|
||||
PathTaperY?: number,
|
||||
PathRevolutions?: number,
|
||||
PathSkew?: number,
|
||||
ProfileBegin?: number,
|
||||
ProfileEnd?: number,
|
||||
ProfileHollow?: number)
|
||||
{
|
||||
this.PathCurve = this.setIfDefined(this.PathCurve, PathCurve);
|
||||
this.ProfileCurve = this.setIfDefined(this.ProfileCurve, ProfileCurve);
|
||||
this.PathBegin = this.setIfDefined(this.PathBegin, PathBegin);
|
||||
this.PathEnd = this.setIfDefined(this.PathEnd, PathEnd);
|
||||
this.PathScaleX = this.setIfDefined(this.PathScaleX, PathScaleX);
|
||||
this.PathScaleY = this.setIfDefined(this.PathScaleY, PathScaleY);
|
||||
this.PathShearX = this.setIfDefined(this.PathShearX, PathShearX);
|
||||
this.PathShearY = this.setIfDefined(this.PathShearY, PathShearY);
|
||||
this.PathTwist = this.setIfDefined(this.PathTwist, PathTwist);
|
||||
this.PathTwistBegin = this.setIfDefined(this.PathTwistBegin, PathTwistBegin);
|
||||
this.PathRadiusOffset = this.setIfDefined(this.PathRadiusOffset, PathRadiusOffset);
|
||||
this.PathTaperX = this.setIfDefined(this.PathTaperX, PathTaperX);
|
||||
this.PathTaperY = this.setIfDefined(this.PathTaperY, PathTaperY);
|
||||
this.PathRevolutions = this.setIfDefined(this.PathRevolutions, PathRevolutions);
|
||||
this.PathSkew = this.setIfDefined(this.PathSkew, PathSkew);
|
||||
this.ProfileBegin = this.setIfDefined(this.ProfileBegin, ProfileBegin);
|
||||
this.ProfileEnd = this.setIfDefined(this.ProfileEnd, ProfileEnd);
|
||||
this.ProfileHollow = this.setIfDefined(this.ProfileHollow, ProfileHollow);
|
||||
if (!this.region)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const msg = new ObjectShapeMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.ObjectData = [
|
||||
{
|
||||
ObjectLocalID: this.ID,
|
||||
PathCurve: this.PathCurve,
|
||||
ProfileCurve: this.ProfileCurve,
|
||||
PathBegin: Utils.packBeginCut(this.PathBegin),
|
||||
PathEnd: Utils.packEndCut(this.PathEnd),
|
||||
PathScaleX: Utils.packPathScale(this.PathScaleX),
|
||||
PathScaleY: Utils.packPathScale(this.PathScaleY),
|
||||
PathShearX: Utils.packPathShear(this.PathShearX),
|
||||
PathShearY: Utils.packPathShear(this.PathShearY),
|
||||
PathTwist: Utils.packPathTwist(this.PathTwist),
|
||||
PathTwistBegin: Utils.packPathTwist(this.PathTwistBegin),
|
||||
PathRadiusOffset: Utils.packPathTwist(this.PathRadiusOffset),
|
||||
PathTaperX: Utils.packPathTaper(this.PathTaperX),
|
||||
PathTaperY: Utils.packPathTaper(this.PathTaperY),
|
||||
PathRevolutions: Utils.packPathRevolutions(this.PathRevolutions),
|
||||
PathSkew: Utils.packPathTwist(this.PathSkew),
|
||||
ProfileBegin: Utils.packBeginCut(this.ProfileBegin),
|
||||
ProfileEnd: Utils.packEndCut(this.ProfileEnd),
|
||||
ProfileHollow: Utils.packProfileHollow(this.ProfileHollow)
|
||||
}
|
||||
];
|
||||
await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000);
|
||||
}
|
||||
|
||||
async setName(name: string)
|
||||
{
|
||||
this.name = name;
|
||||
if (!this.region)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const msg = new ObjectNameMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.ObjectData = [
|
||||
{
|
||||
LocalID: this.ID,
|
||||
Name: Utils.StringToBuffer(name)
|
||||
}
|
||||
];
|
||||
await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000);
|
||||
}
|
||||
|
||||
private compareParam(name: string, param1: number | undefined, param2: number | undefined): boolean
|
||||
{
|
||||
if (param1 === undefined)
|
||||
{
|
||||
param1 = 0;
|
||||
}
|
||||
if (param2 === undefined)
|
||||
{
|
||||
param2 = 0;
|
||||
}
|
||||
if (Math.abs(param1 - param2) < 0.0001)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('Failed ' + name + ' - ' + param1 + ' vs ' + param2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
compareShape(obj: GameObject): boolean
|
||||
{
|
||||
return this.compareParam('PathCurve', this.PathCurve, obj.PathCurve) &&
|
||||
this.compareParam('ProfileCurve', this.ProfileCurve, obj.ProfileCurve) &&
|
||||
this.compareParam('PathBegin', this.PathBegin, obj.PathBegin) &&
|
||||
this.compareParam('PathEnd', this.PathEnd, obj.PathEnd) &&
|
||||
this.compareParam('PathScaleX', this.PathScaleX, obj.PathScaleX) &&
|
||||
this.compareParam('PathScaleY', this.PathScaleY, obj.PathScaleY) &&
|
||||
this.compareParam('PathShearX', this.PathShearX, obj.PathShearX) &&
|
||||
this.compareParam('PathShearY', this.PathShearY, obj.PathShearY) &&
|
||||
this.compareParam('PathTwist', this.PathTwist, obj.PathTwist) &&
|
||||
this.compareParam('PathTwistBegin', this.PathTwistBegin, obj.PathTwistBegin) &&
|
||||
this.compareParam('PathRadiusOffset', this.PathRadiusOffset, obj.PathRadiusOffset) &&
|
||||
this.compareParam('PathTaperX', this.PathTaperX, obj.PathTaperX) &&
|
||||
this.compareParam('PathTaperY', this.PathTaperY, obj.PathTaperY) &&
|
||||
this.compareParam('PathRevolutions', this.PathRevolutions, obj.PathRevolutions) &&
|
||||
this.compareParam('PathSkew', this.PathSkew, obj.PathSkew) &&
|
||||
this.compareParam('ProfileBegin', this.ProfileBegin, obj.ProfileBegin) &&
|
||||
this.compareParam('ProfileEnd', this.ProfileEnd, obj.ProfileEnd) &&
|
||||
this.compareParam('PRofileHollow', this.ProfileHollow, obj.ProfileHollow);
|
||||
}
|
||||
|
||||
async setGeometry(pos?: Vector3, rot?: Quaternion, scale?: Vector3)
|
||||
{
|
||||
const data = [];
|
||||
if (pos !== undefined)
|
||||
{
|
||||
this.Position = pos;
|
||||
data.push({
|
||||
ObjectLocalID: this.ID,
|
||||
Type: UpdateType.Position,
|
||||
Data: pos.getBuffer()
|
||||
});
|
||||
}
|
||||
if (rot !== undefined)
|
||||
{
|
||||
this.Rotation = rot;
|
||||
data.push({
|
||||
ObjectLocalID: this.ID,
|
||||
Type: UpdateType.Rotation,
|
||||
Data: rot.getBuffer()
|
||||
})
|
||||
}
|
||||
if (scale !== undefined)
|
||||
{
|
||||
this.Scale = scale;
|
||||
data.push({
|
||||
ObjectLocalID: this.ID,
|
||||
Type: UpdateType.Scale,
|
||||
Data: scale.getBuffer()
|
||||
})
|
||||
}
|
||||
if (!this.region || data.length === 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const msg = new MultipleObjectUpdateMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.ObjectData = data;
|
||||
await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 30000);
|
||||
}
|
||||
|
||||
async linkTo(root: GameObject)
|
||||
{
|
||||
const msg = new ObjectLinkMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.ObjectData = [
|
||||
{
|
||||
ObjectLocalID: root.ID
|
||||
},
|
||||
{
|
||||
ObjectLocalID: this.ID
|
||||
}
|
||||
];
|
||||
await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 30000);
|
||||
}
|
||||
|
||||
async setDescription(desc: string)
|
||||
{
|
||||
this.description = desc;
|
||||
if (!this.region)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const msg = new ObjectDescriptionMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.ObjectData = [
|
||||
{
|
||||
LocalID: this.ID,
|
||||
Description: Utils.StringToBuffer(desc)
|
||||
}
|
||||
];
|
||||
await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000);
|
||||
}
|
||||
|
||||
async setTextureEntry(e: TextureEntry)
|
||||
{
|
||||
this.TextureEntry = e;
|
||||
if (!this.region)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await this.setTextureAndMediaURL();
|
||||
}
|
||||
|
||||
private async setTextureAndMediaURL()
|
||||
{
|
||||
const msg = new ObjectImageMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
if (this.MediaURL === undefined)
|
||||
{
|
||||
this.MediaURL = '';
|
||||
}
|
||||
if (this.TextureEntry === undefined)
|
||||
{
|
||||
this.TextureEntry = new TextureEntry();
|
||||
}
|
||||
msg.ObjectData = [
|
||||
{
|
||||
ObjectLocalID: this.ID,
|
||||
TextureEntry: this.TextureEntry.toBuffer(),
|
||||
MediaURL: Utils.StringToBuffer(this.MediaURL)
|
||||
}
|
||||
];
|
||||
await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000);
|
||||
}
|
||||
|
||||
async setExtraParams(ex: ExtraParams)
|
||||
{
|
||||
this.extraParams = ex;
|
||||
if (!this.region)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set ExtraParams
|
||||
const msg = new ObjectExtraParamsMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.ObjectData = [];
|
||||
let params = 0;
|
||||
if (ex.lightData !== null)
|
||||
{
|
||||
params++;
|
||||
const data = ex.lightData.getBuffer();
|
||||
msg.ObjectData.push({
|
||||
ObjectLocalID: this.ID,
|
||||
ParamType: ExtraParamType.Light,
|
||||
ParamInUse: (ex.lightData.Intensity !== 0.0),
|
||||
ParamData: data,
|
||||
ParamSize: data.length
|
||||
});
|
||||
}
|
||||
if (ex.flexibleData !== null)
|
||||
{
|
||||
params++;
|
||||
const data = ex.flexibleData.getBuffer();
|
||||
msg.ObjectData.push({
|
||||
ObjectLocalID: this.ID,
|
||||
ParamType: ExtraParamType.Flexible,
|
||||
ParamInUse: true,
|
||||
ParamData: data,
|
||||
ParamSize: data.length
|
||||
});
|
||||
}
|
||||
if (ex.lightImageData !== null)
|
||||
{
|
||||
params++;
|
||||
const data = ex.lightImageData.getBuffer();
|
||||
msg.ObjectData.push({
|
||||
ObjectLocalID: this.ID,
|
||||
ParamType: ExtraParamType.LightImage,
|
||||
ParamInUse: true,
|
||||
ParamData: data,
|
||||
ParamSize: data.length
|
||||
});
|
||||
}
|
||||
if (ex.sculptData !== null)
|
||||
{
|
||||
params++;
|
||||
const data = ex.sculptData.getBuffer();
|
||||
msg.ObjectData.push({
|
||||
ObjectLocalID: this.ID,
|
||||
ParamType: ExtraParamType.Sculpt,
|
||||
ParamInUse: true,
|
||||
ParamData: data,
|
||||
ParamSize: data.length
|
||||
});
|
||||
}
|
||||
if (ex.meshData !== null)
|
||||
{
|
||||
params++;
|
||||
const data = ex.meshData.getBuffer();
|
||||
msg.ObjectData.push({
|
||||
ObjectLocalID: this.ID,
|
||||
ParamType: ExtraParamType.Mesh,
|
||||
ParamInUse: true,
|
||||
ParamData: data,
|
||||
ParamSize: data.length
|
||||
});
|
||||
}
|
||||
if (params > 0)
|
||||
{
|
||||
const ack = this.region.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
await this.region.circuit.waitForAck(ack, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
private async getInventoryXML(xml: XMLElementOrXMLNode, inv: InventoryItem)
|
||||
{
|
||||
if (!inv.assetID.equals(UUID.zero()))
|
||||
@@ -695,29 +1087,30 @@ export class GameObject implements IGameObjectData
|
||||
shape.ele('ProfileCurve', this.ProfileCurve);
|
||||
if (this.TextureEntry)
|
||||
{
|
||||
shape.ele('TextureEntry', this.TextureEntry.binary.toString('base64'));
|
||||
shape.ele('TextureEntry', this.TextureEntry.toBase64());
|
||||
}
|
||||
if (this.extraParams)
|
||||
{
|
||||
shape.ele('ExtraParams', this.extraParams.toBase64());
|
||||
}
|
||||
shape.ele('PathBegin', this.PathBegin);
|
||||
shape.ele('PathBegin', Utils.packBeginCut(Utils.numberOrZero(this.PathBegin)));
|
||||
shape.ele('PathCurve', this.PathCurve);
|
||||
shape.ele('PathEnd', this.PathEnd);
|
||||
shape.ele('PathRadiusOffset', this.PathRadiusOffset);
|
||||
shape.ele('PathRevolutions', this.PathRevolutions);
|
||||
shape.ele('PathScaleX', this.PathScaleX);
|
||||
shape.ele('PathScaleY', this.PathScaleY);
|
||||
shape.ele('PathShearX', this.PathShearX);
|
||||
shape.ele('PathSkew', this.PathSkew);
|
||||
shape.ele('PathTaperX', this.PathTaperX);
|
||||
shape.ele('PathTaperY', this.PathTaperY);
|
||||
shape.ele('PathTwist', this.PathTwist);
|
||||
shape.ele('PathTwistBegin', this.PathTwistBegin);
|
||||
shape.ele('PathEnd', Utils.packEndCut(Utils.numberOrZero(this.PathEnd)));
|
||||
shape.ele('PathRadiusOffset', Utils.packPathTwist(Utils.numberOrZero(this.PathRadiusOffset)));
|
||||
shape.ele('PathRevolutions', Utils.packPathRevolutions(Utils.numberOrZero(this.PathRevolutions)));
|
||||
shape.ele('PathScaleX', Utils.packPathScale(Utils.numberOrZero(this.PathScaleX)));
|
||||
shape.ele('PathScaleY', Utils.packPathScale(Utils.numberOrZero(this.PathScaleY)));
|
||||
shape.ele('PathShearX', Utils.packPathShear(Utils.numberOrZero(this.PathShearX)));
|
||||
shape.ele('PathShearY', Utils.packPathShear(Utils.numberOrZero(this.PathShearY)));
|
||||
shape.ele('PathSkew', Utils.packPathTwist(Utils.numberOrZero(this.PathSkew)));
|
||||
shape.ele('PathTaperX', Utils.packPathTaper(Utils.numberOrZero(this.PathTaperX)));
|
||||
shape.ele('PathTaperY', Utils.packPathTaper(Utils.numberOrZero(this.PathTaperY)));
|
||||
shape.ele('PathTwist', Utils.packPathTwist(Utils.numberOrZero(this.PathTwist)));
|
||||
shape.ele('PathTwistBegin', Utils.packPathTwist(Utils.numberOrZero(this.PathTwistBegin)));
|
||||
shape.ele('PCode', this.PCode);
|
||||
shape.ele('ProfileBegin', this.ProfileBegin);
|
||||
shape.ele('ProfileEnd', this.ProfileEnd);
|
||||
shape.ele('ProfileHollow', this.ProfileHollow);
|
||||
shape.ele('ProfileBegin', Utils.packBeginCut(Utils.numberOrZero(this.ProfileBegin)));
|
||||
shape.ele('ProfileEnd', Utils.packEndCut(Utils.numberOrZero(this.ProfileEnd)));
|
||||
shape.ele('ProfileHollow', Utils.packProfileHollow(Utils.numberOrZero(this.ProfileHollow)));
|
||||
shape.ele('State', this.State);
|
||||
|
||||
if (this.ProfileCurve)
|
||||
|
||||
630
lib/classes/public/LLMesh.ts
Normal file
630
lib/classes/public/LLMesh.ts
Normal file
@@ -0,0 +1,630 @@
|
||||
import {Utils} from '../Utils';
|
||||
import * as zlib from 'zlib';
|
||||
import * as LLSD from '@caspertech/llsd';
|
||||
import {UUID} from '../UUID';
|
||||
import {LLSubMesh} from './interfaces/LLSubMesh';
|
||||
import {Vector3} from '../Vector3';
|
||||
import {Vector2} from '../Vector2';
|
||||
import {LLSkin} from './interfaces/LLSkin';
|
||||
import {mat4} from '../../tsm/mat4';
|
||||
import {LLPhysicsConvex} from './interfaces/LLPhysicsConvex';
|
||||
|
||||
export class LLMesh
|
||||
{
|
||||
version: number;
|
||||
lodLevels: {[key: string]: LLSubMesh[]} = {};
|
||||
physicsConvex: LLPhysicsConvex;
|
||||
skin?: LLSkin;
|
||||
creatorID: UUID;
|
||||
date: Date;
|
||||
|
||||
static async from(buf: Buffer): Promise<LLMesh>
|
||||
{
|
||||
const llmesh = new LLMesh();
|
||||
const binData = new LLSD.Binary(Array.from(buf), 'BASE64');
|
||||
let obj = LLSD.LLSD.parseBinary(binData);
|
||||
if (obj['result'] === undefined)
|
||||
{
|
||||
throw new Error('Failed to decode header');
|
||||
}
|
||||
if (obj['position'] === undefined)
|
||||
{
|
||||
throw new Error('Position not reported');
|
||||
}
|
||||
const startPos = parseInt(obj['position'], 10);
|
||||
obj = obj['result'];
|
||||
if (!obj['version'])
|
||||
{
|
||||
throw new Error('No version found');
|
||||
}
|
||||
if (!obj['creator'])
|
||||
{
|
||||
throw new Error('Creator UUID not found');
|
||||
}
|
||||
if (obj['date'] === undefined)
|
||||
{
|
||||
throw new Error('Date not found');
|
||||
}
|
||||
llmesh.creatorID = new UUID(obj['creator'].toString());
|
||||
llmesh.date = obj['date'];
|
||||
llmesh.version = parseInt(obj['version'], 10);
|
||||
for (const key of Object.keys(obj))
|
||||
{
|
||||
const o = obj[key];
|
||||
if (typeof o === 'object' && o !== null && o['offset'] !== undefined)
|
||||
{
|
||||
const bufFrom = startPos + parseInt(o['offset'], 10);
|
||||
const bufTo = startPos + parseInt(o['offset'], 10) + parseInt(o['size'], 10);
|
||||
const partBuf = buf.slice(bufFrom, bufTo);
|
||||
const deflated = await this.inflate(partBuf);
|
||||
|
||||
const mesh = LLSD.LLSD.parseBinary(new LLSD.Binary(Array.from(deflated), 'BASE64'));
|
||||
if (mesh['result'] === undefined)
|
||||
{
|
||||
throw new Error('Failed to parse compressed submesh data');
|
||||
}
|
||||
if (key === 'physics_convex')
|
||||
{
|
||||
llmesh.physicsConvex = this.parsePhysicsConvex(mesh['result']);
|
||||
}
|
||||
else if (key === 'skin')
|
||||
{
|
||||
llmesh.skin = this.parseSkin(mesh['result']);
|
||||
}
|
||||
else if (key === 'physics_havok' || key === 'physics_cost_data')
|
||||
{
|
||||
// Used by the simulator
|
||||
}
|
||||
else
|
||||
{
|
||||
llmesh.lodLevels[key] = this.parseLODLevel(mesh['result']);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return llmesh;
|
||||
}
|
||||
static parseSkin(mesh: any): LLSkin
|
||||
{
|
||||
if (!mesh['joint_names'])
|
||||
{
|
||||
throw new Error('Joint names missing from skin');
|
||||
}
|
||||
if (!mesh['bind_shape_matrix'])
|
||||
{
|
||||
throw new Error('Bind shape matrix missing from skin');
|
||||
}
|
||||
if (!mesh['inverse_bind_matrix'])
|
||||
{
|
||||
throw new Error('Inverse bind matrix missing from skin');
|
||||
}
|
||||
const skin: LLSkin = {
|
||||
jointNames: mesh['joint_names'],
|
||||
bindShapeMatrix: new mat4(mesh['bind_shape_matrix']),
|
||||
inverseBindMatrix: []
|
||||
};
|
||||
if (mesh['inverse_bind_matrix'])
|
||||
{
|
||||
skin.inverseBindMatrix = [];
|
||||
for (const inv of mesh['inverse_bind_matrix'])
|
||||
{
|
||||
skin.inverseBindMatrix.push(new mat4(inv));
|
||||
}
|
||||
}
|
||||
if (mesh['alt_inverse_bind_matrix'])
|
||||
{
|
||||
skin.altInverseBindMatrix = [];
|
||||
for (const inv of mesh['alt_inverse_bind_matrix'])
|
||||
{
|
||||
skin.altInverseBindMatrix.push(new mat4(inv));
|
||||
}
|
||||
}
|
||||
if (mesh['pelvis_offset'])
|
||||
{
|
||||
skin.pelvisOffset = new mat4(mesh['pelvis_offset']);
|
||||
}
|
||||
return skin;
|
||||
}
|
||||
|
||||
static fixReal(arr: number[]): number[]
|
||||
{
|
||||
const newArr = [];
|
||||
for (let num of arr)
|
||||
{
|
||||
if ((num >> 0) === num && !((num === 0) && ((1 / num) === -Infinity)))
|
||||
{
|
||||
num += 0.0000000001;
|
||||
}
|
||||
newArr.push(num);
|
||||
}
|
||||
return newArr;
|
||||
}
|
||||
static parsePhysicsConvex(mesh: any): LLPhysicsConvex
|
||||
{
|
||||
const conv: LLPhysicsConvex = {
|
||||
boundingVerts: [],
|
||||
domain: {
|
||||
min: new Vector3([-0.5, -0.5, -0.5]),
|
||||
max: new Vector3([0.5, 0.5, 0.5])
|
||||
}
|
||||
};
|
||||
if (mesh['Min'])
|
||||
{
|
||||
conv.domain.min.x = mesh['Min'][0];
|
||||
conv.domain.min.y = mesh['Min'][1];
|
||||
conv.domain.min.z = mesh['Min'][2];
|
||||
}
|
||||
if (mesh['Max'])
|
||||
{
|
||||
conv.domain.max.x = mesh['Max'][0];
|
||||
conv.domain.max.y = mesh['Max'][1];
|
||||
conv.domain.max.z = mesh['Max'][2];
|
||||
}
|
||||
if (mesh['HullList'])
|
||||
{
|
||||
if (!mesh['Positions'])
|
||||
{
|
||||
throw new Error('Positions must be supplied if hull list is present');
|
||||
}
|
||||
conv.positions = this.decodeByteDomain3(mesh['Positions'].toArray(), conv.domain.min, conv.domain.max);
|
||||
conv.hullList = mesh['HullList'].toArray();
|
||||
if (conv.hullList === undefined)
|
||||
{
|
||||
throw new Error('HullList undefined');
|
||||
}
|
||||
else
|
||||
{
|
||||
let totalPoints = 0;
|
||||
for (const hull of conv.hullList)
|
||||
{
|
||||
totalPoints += hull;
|
||||
}
|
||||
if (conv.positions.length !== totalPoints)
|
||||
{
|
||||
throw new Error('Hull list expected number of points does not match number of positions: ' + totalPoints + ' vs ' + conv.positions.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!mesh['BoundingVerts'])
|
||||
{
|
||||
throw new Error('BoundingVerts is required');
|
||||
}
|
||||
conv.boundingVerts = this.decodeByteDomain3(mesh['BoundingVerts'].toArray(), conv.domain.min, conv.domain.max);
|
||||
return conv;
|
||||
}
|
||||
static parseLODLevel(mesh: any): LLSubMesh[]
|
||||
{
|
||||
const list: LLSubMesh[] = [];
|
||||
for (const submesh of mesh)
|
||||
{
|
||||
const decoded: LLSubMesh = {
|
||||
positionDomain: {
|
||||
min: new Vector3([-0.5, -0.5, -0.5]),
|
||||
max: new Vector3([0.5, 0.5, 0.5])
|
||||
}
|
||||
};
|
||||
if (submesh['NoGeometry'])
|
||||
{
|
||||
decoded.noGeometry = true;
|
||||
list.push(decoded);
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded.position = [];
|
||||
if (!submesh['Position'])
|
||||
{
|
||||
throw new Error('Submesh does not contain position data');
|
||||
}
|
||||
if (decoded.positionDomain !== undefined)
|
||||
{
|
||||
if (submesh['PositionDomain'])
|
||||
{
|
||||
if (submesh['PositionDomain']['Max'] !== undefined)
|
||||
{
|
||||
const dom = submesh['PositionDomain']['Max'];
|
||||
decoded.positionDomain.max.x = dom[0];
|
||||
decoded.positionDomain.max.y = dom[1];
|
||||
decoded.positionDomain.max.z = dom[2];
|
||||
}
|
||||
if (submesh['PositionDomain']['Min'] !== undefined)
|
||||
{
|
||||
const dom = submesh['PositionDomain']['Min'];
|
||||
decoded.positionDomain.min.x = dom[0];
|
||||
decoded.positionDomain.min.y = dom[1];
|
||||
decoded.positionDomain.min.z = dom[2];
|
||||
}
|
||||
}
|
||||
decoded.position = this.decodeByteDomain3(submesh['Position'].toArray(), decoded.positionDomain.min, decoded.positionDomain.max);
|
||||
}
|
||||
if (submesh['Normal'])
|
||||
{
|
||||
decoded.normal = this.decodeByteDomain3(submesh['Normal'].toArray(), new Vector3([-1.0, -1.0, -1.0]), new Vector3([1.0, 1.0, 1.0]));
|
||||
if (decoded.normal.length !== decoded.position.length)
|
||||
{
|
||||
throw new Error('Normal length does not match vertex position length');
|
||||
}
|
||||
}
|
||||
if (submesh['TexCoord0'])
|
||||
{
|
||||
decoded.texCoord0Domain = {
|
||||
min: new Vector2([-0.5, -0.5]),
|
||||
max: new Vector2([0.5, 0.5])
|
||||
};
|
||||
if (submesh['TexCoord0Domain'])
|
||||
{
|
||||
if (submesh['TexCoord0Domain']['Max'] !== undefined)
|
||||
{
|
||||
const dom = submesh['TexCoord0Domain']['Max'];
|
||||
decoded.texCoord0Domain.max.x = dom[0];
|
||||
decoded.texCoord0Domain.max.y = dom[1];
|
||||
}
|
||||
if (submesh['TexCoord0Domain']['Min'] !== undefined)
|
||||
{
|
||||
const dom = submesh['TexCoord0Domain']['Min'];
|
||||
decoded.texCoord0Domain.min.x = dom[0];
|
||||
decoded.texCoord0Domain.min.y = dom[1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('TexCoord0Domain is required if Texcoord0 is present');
|
||||
}
|
||||
decoded.texCoord0 = this.decodeByteDomain2(submesh['TexCoord0'].toArray(), decoded.texCoord0Domain.min, decoded.texCoord0Domain.max);
|
||||
}
|
||||
if (!submesh['TriangleList'])
|
||||
{
|
||||
throw new Error('TriangleList is required');
|
||||
}
|
||||
const indexBuf = new Buffer(submesh['TriangleList'].toArray());
|
||||
decoded.triangleList = [];
|
||||
for (let pos = 0; pos < indexBuf.length; pos = pos + 2)
|
||||
{
|
||||
const vertIndex = indexBuf.readUInt16LE(pos);
|
||||
if (vertIndex >= decoded.position.length)
|
||||
{
|
||||
throw new Error('Vertex index out of range: ' + vertIndex)
|
||||
}
|
||||
decoded.triangleList.push(vertIndex);
|
||||
}
|
||||
if (submesh['Weights'])
|
||||
{
|
||||
const skinBuf = new Buffer(submesh['Weights'].toArray());
|
||||
decoded.weights = [];
|
||||
let pos = 0;
|
||||
while (pos < skinBuf.length)
|
||||
{
|
||||
const entry: {[key: number]: number} = {};
|
||||
for (let x = 0; x < 4; x++)
|
||||
{
|
||||
const jointNum = skinBuf.readUInt8(pos++);
|
||||
if (jointNum === 0xFF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const value = skinBuf.readUInt16LE(pos); pos = pos + 2;
|
||||
entry[jointNum] = value;
|
||||
}
|
||||
decoded.weights.push(entry);
|
||||
}
|
||||
if (decoded.weights.length !== decoded.position.length)
|
||||
{
|
||||
throw new Error('Weight list differs in length from position list');
|
||||
}
|
||||
}
|
||||
list.push(decoded);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
static decodeByteDomain3(posArray: number[], minDomain: Vector3, maxDomain: Vector3): Vector3[]
|
||||
{
|
||||
const result: Vector3[] = [];
|
||||
const buf = new Buffer(posArray);
|
||||
for (let idx = 0; idx < posArray.length; idx = idx + 6)
|
||||
{
|
||||
const posX = this.normalizeDomain(buf.readUInt16LE(idx), minDomain.x, maxDomain.x);
|
||||
const posY = this.normalizeDomain(buf.readUInt16LE(idx + 2), minDomain.y, maxDomain.y);
|
||||
const posZ = this.normalizeDomain(buf.readUInt16LE(idx + 4), minDomain.z, maxDomain.z);
|
||||
result.push(new Vector3([posX, posY, posZ]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static decodeByteDomain2(posArray: number[], minDomain: Vector2, maxDomain: Vector2): Vector2[]
|
||||
{
|
||||
const result: Vector2[] = [];
|
||||
const buf = new Buffer(posArray);
|
||||
for (let idx = 0; idx < posArray.length; idx = idx + 4)
|
||||
{
|
||||
const posX = this.normalizeDomain(buf.readUInt16LE(idx), minDomain.x, maxDomain.x);
|
||||
const posY = this.normalizeDomain(buf.readUInt16LE(idx + 2), minDomain.y, maxDomain.y);
|
||||
result.push(new Vector2([posX, posY]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static normalizeDomain(value: number, min: number, max: number)
|
||||
{
|
||||
return ((value / 65535) * (max - min)) + min;
|
||||
}
|
||||
static inflate(buf: Buffer): Promise<Buffer>
|
||||
{
|
||||
return new Promise<Buffer>((resolve, reject) =>
|
||||
{
|
||||
zlib.inflate(buf, (error: (Error| null), result: Buffer) =>
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
reject(error)
|
||||
}
|
||||
else
|
||||
{
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
static deflate(buf: Buffer): Promise<Buffer>
|
||||
{
|
||||
return new Promise<Buffer>((resolve, reject) =>
|
||||
{
|
||||
zlib.deflate(buf, { level: 9}, (error: (Error| null), result: Buffer) =>
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
reject(error)
|
||||
}
|
||||
else
|
||||
{
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
private encodeSubMesh(mesh: LLSubMesh)
|
||||
{
|
||||
const data: {
|
||||
NoGeometry?: true,
|
||||
Position?: any, // LLSD.Binary
|
||||
PositionDomain?: {
|
||||
Min: number[],
|
||||
Max: number[]
|
||||
},
|
||||
Normal?: any, // LLSD.Binary
|
||||
TexCoord0?: any, // LLSD.Binary
|
||||
TexCoord0Domain?: {
|
||||
Min: number[],
|
||||
Max: number[]
|
||||
},
|
||||
TriangleList?: any, // LLSD.Binary
|
||||
Weights?: any // LLSD.Binary
|
||||
} = {};
|
||||
if (mesh.noGeometry === true)
|
||||
{
|
||||
data.NoGeometry = true;
|
||||
return data;
|
||||
}
|
||||
if (!mesh.position)
|
||||
{
|
||||
throw new Error('No position data when encoding submesh');
|
||||
}
|
||||
if (mesh.positionDomain !== undefined)
|
||||
{
|
||||
data.Position = new LLSD.Binary(Array.from(this.expandFromDomain(mesh.position, mesh.positionDomain.min, mesh.positionDomain.max)));
|
||||
data.PositionDomain = {
|
||||
Min: LLMesh.fixReal(mesh.positionDomain.min.toArray()),
|
||||
Max: LLMesh.fixReal(mesh.positionDomain.max.toArray())
|
||||
};
|
||||
}
|
||||
if (mesh.texCoord0 && mesh.texCoord0Domain !== undefined)
|
||||
{
|
||||
data.TexCoord0 = new LLSD.Binary(Array.from(this.expandFromDomain(mesh.texCoord0, mesh.texCoord0Domain.min, mesh.texCoord0Domain.max)));
|
||||
data.TexCoord0Domain = {
|
||||
Min: LLMesh.fixReal(mesh.texCoord0Domain.min.toArray()),
|
||||
Max: LLMesh.fixReal(mesh.texCoord0Domain.max.toArray())
|
||||
};
|
||||
}
|
||||
if (mesh.normal)
|
||||
{
|
||||
data.Normal = new LLSD.Binary(Array.from(this.expandFromDomain(mesh.normal, new Vector3([-1.0, -1.0, -1.0]), new Vector3([1.0, 1.0, 1.0]))));
|
||||
}
|
||||
if (mesh.triangleList)
|
||||
{
|
||||
const triangles = Buffer.allocUnsafe(mesh.triangleList.length * 2);
|
||||
let pos = 0;
|
||||
for (let x = 0; x < mesh.triangleList.length; x++)
|
||||
{
|
||||
triangles.writeUInt16LE(mesh.triangleList[x], pos); pos = pos + 2;
|
||||
}
|
||||
data.TriangleList = new LLSD.Binary(Array.from(triangles));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('Triangle list is required');
|
||||
}
|
||||
if (mesh.weights)
|
||||
{
|
||||
// Calculate how much space we need
|
||||
let spaceNeeded = 0;
|
||||
for (const weight of mesh.weights)
|
||||
{
|
||||
const keys = Object.keys(weight);
|
||||
spaceNeeded = spaceNeeded + keys.length * 3;
|
||||
if (keys.length < 4)
|
||||
{
|
||||
spaceNeeded = spaceNeeded + 1;
|
||||
}
|
||||
}
|
||||
const weightBuff = Buffer.allocUnsafe(spaceNeeded);
|
||||
let pos = 0;
|
||||
for (const weight of mesh.weights)
|
||||
{
|
||||
const keys = Object.keys(weight);
|
||||
for (const jointID of keys)
|
||||
{
|
||||
weightBuff.writeUInt8(parseInt(jointID, 10), pos++);
|
||||
weightBuff.writeUInt16LE(weight[parseInt(jointID, 10)], pos); pos = pos + 2;
|
||||
}
|
||||
if (keys.length < 4)
|
||||
{
|
||||
weightBuff.writeUInt8(0xFF, pos++);
|
||||
}
|
||||
}
|
||||
data.Weights = new LLSD.Binary(Array.from(weightBuff));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
private expandFromDomain(data: Vector3[] | Vector2[], domainMin: Vector3 | Vector2, domainMax: Vector3 | Vector2)
|
||||
{
|
||||
let length = 4;
|
||||
if (data.length > 0 && data[0] instanceof Vector3)
|
||||
{
|
||||
length = 6;
|
||||
}
|
||||
const buf = Buffer.allocUnsafe(data.length * length);
|
||||
let pos = 0;
|
||||
for (const c of data)
|
||||
{
|
||||
const coord: Vector3 | Vector2 = c;
|
||||
const sizeX = domainMax.x - domainMin.x;
|
||||
const newX = Math.round(((coord.x - domainMin.x) / sizeX) * 65535);
|
||||
|
||||
const sizeY = domainMax.y - domainMin.y;
|
||||
const newY = Math.round(((coord.y - domainMin.y) / sizeY) * 65535);
|
||||
buf.writeUInt16LE(newX, pos); pos = pos + 2;
|
||||
buf.writeUInt16LE(newY, pos); pos = pos + 2;
|
||||
if (coord instanceof Vector3 && domainMin instanceof Vector3 && domainMax instanceof Vector3)
|
||||
{
|
||||
const sizeZ = domainMax.z - domainMin.z;
|
||||
const newZ = Math.round(((coord.z - domainMin.z) / sizeZ) * 65535);
|
||||
buf.writeUInt16LE(newZ, pos); pos = pos + 2;
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
private async encodeLODLevel(name: string, submeshes: LLSubMesh[]): Promise<Buffer>
|
||||
{
|
||||
const smList = [];
|
||||
for (const sub of submeshes)
|
||||
{
|
||||
smList.push(this.encodeSubMesh(sub))
|
||||
}
|
||||
const mesh = LLSD.LLSD.formatBinary(smList);
|
||||
return await LLMesh.deflate(Buffer.from(mesh.toArray()));
|
||||
}
|
||||
private async encodePhysicsConvex(conv: LLPhysicsConvex): Promise<Buffer>
|
||||
{
|
||||
const llsd: {
|
||||
'HullList'?: any,
|
||||
'Positions'?: any,
|
||||
'BoundingVerts'?: any,
|
||||
'Min': number[],
|
||||
'Max': number[];
|
||||
} = {
|
||||
Min: LLMesh.fixReal(conv.domain.min.toArray()),
|
||||
Max: LLMesh.fixReal(conv.domain.max.toArray())
|
||||
};
|
||||
const sizeX = conv.domain.max.x - conv.domain.min.x;
|
||||
const sizeY = conv.domain.max.y - conv.domain.min.y;
|
||||
const sizeZ = conv.domain.max.z - conv.domain.min.z;
|
||||
if (conv.hullList)
|
||||
{
|
||||
if (!conv.positions)
|
||||
{
|
||||
throw new Error('Positions must be present if hullList is set.')
|
||||
}
|
||||
llsd.HullList = new LLSD.Binary(conv.hullList);
|
||||
const buf = Buffer.allocUnsafe(conv.positions.length * 6);
|
||||
let pos = 0;
|
||||
for (const vec of conv.positions)
|
||||
{
|
||||
buf.writeUInt16LE(Math.round(((vec.x - conv.domain.min.x) / sizeX) * 65535), pos); pos = pos + 2;
|
||||
buf.writeUInt16LE(Math.round(((vec.y - conv.domain.min.y) / sizeY) * 65535), pos); pos = pos + 2;
|
||||
buf.writeUInt16LE(Math.round(((vec.z - conv.domain.min.z) / sizeZ) * 65535), pos); pos = pos + 2;
|
||||
}
|
||||
llsd.Positions = new LLSD.Binary(Array.from(buf));
|
||||
}
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(conv.boundingVerts.length * 6);
|
||||
let pos = 0;
|
||||
for (const vec of conv.boundingVerts)
|
||||
{
|
||||
buf.writeUInt16LE(Math.round(((vec.x - conv.domain.min.x) / sizeX)) * 65535, pos);
|
||||
pos = pos + 2;
|
||||
buf.writeUInt16LE(Math.round(((vec.y - conv.domain.min.y) / sizeY)) * 65535, pos);
|
||||
pos = pos + 2;
|
||||
buf.writeUInt16LE(Math.round(((vec.z - conv.domain.min.z) / sizeZ)) * 65535, pos);
|
||||
pos = pos + 2;
|
||||
}
|
||||
llsd.BoundingVerts = new LLSD.Binary(Array.from(buf));
|
||||
}
|
||||
const mesh = LLSD.LLSD.formatBinary(llsd);
|
||||
return await LLMesh.deflate(Buffer.from(mesh.toArray()));
|
||||
}
|
||||
private async encodeSkin(skin: LLSkin): Promise<Buffer>
|
||||
{
|
||||
const llsd: {[key: string]: any} = {};
|
||||
llsd['joint_names'] = skin.jointNames;
|
||||
llsd['bind_shape_matrix'] = skin.bindShapeMatrix.toArray();
|
||||
llsd['inverse_bind_matrix'] = [];
|
||||
for (const matrix of skin.inverseBindMatrix)
|
||||
{
|
||||
llsd['inverse_bind_matrix'].push(matrix.toArray())
|
||||
}
|
||||
if (skin.altInverseBindMatrix)
|
||||
{
|
||||
llsd['alt_inverse_bind_matrix'] = [];
|
||||
for (const matrix of skin.altInverseBindMatrix)
|
||||
{
|
||||
llsd['alt_inverse_bind_matrix'].push(matrix.toArray())
|
||||
}
|
||||
}
|
||||
if (skin.pelvisOffset)
|
||||
{
|
||||
llsd['pelvis_offset'] = skin.pelvisOffset.toArray();
|
||||
}
|
||||
const mesh = LLSD.LLSD.formatBinary(llsd);
|
||||
return await LLMesh.deflate(Buffer.from(mesh.toArray()));
|
||||
}
|
||||
async toAsset(): Promise<Buffer>
|
||||
{
|
||||
const llsd: {[key: string]: any} = {
|
||||
'creator': new LLSD.UUID(this.creatorID.toString()),
|
||||
'version': this.version,
|
||||
'date': null
|
||||
};
|
||||
let offset = 0;
|
||||
const bufs = [];
|
||||
for (const lod of Object.keys(this.lodLevels))
|
||||
{
|
||||
const lodBlob = await this.encodeLODLevel(lod, this.lodLevels[lod]);
|
||||
llsd[lod] = {
|
||||
'offset': offset,
|
||||
'size': lodBlob.length
|
||||
};
|
||||
offset += lodBlob.length;
|
||||
bufs.push(lodBlob);
|
||||
}
|
||||
if (this.physicsConvex)
|
||||
{
|
||||
const physBlob = await this.encodePhysicsConvex(this.physicsConvex);
|
||||
llsd['physics_convex'] = {
|
||||
'offset': offset,
|
||||
'size': physBlob.length
|
||||
};
|
||||
offset += physBlob.length;
|
||||
bufs.push(physBlob);
|
||||
|
||||
}
|
||||
if (this.skin)
|
||||
{
|
||||
const skinBlob = await this.encodeSkin(this.skin);
|
||||
llsd['skin'] = {
|
||||
'offset': offset,
|
||||
'size': skinBlob.length
|
||||
};
|
||||
offset += skinBlob.length;
|
||||
bufs.push(skinBlob);
|
||||
}
|
||||
bufs.unshift(Buffer.from(LLSD.LLSD.formatBinary(llsd).toArray()));
|
||||
return Buffer.concat(bufs);
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,10 @@ export class LightData
|
||||
buf.writeFloatLE(this.Cutoff, pos); pos = pos + 4;
|
||||
buf.writeFloatLE(this.Falloff, pos);
|
||||
}
|
||||
getBuffer(): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(16);
|
||||
this.writeToBuffer(buf, 0);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,10 @@ export class LightImageData
|
||||
this.texture.writeToBuffer(buf, pos); pos = pos + 16;
|
||||
this.params.writeToBuffer(buf, pos, false);
|
||||
}
|
||||
getBuffer(): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(28);
|
||||
this.writeToBuffer(buf, 0);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,10 @@ export class MeshData
|
||||
this.meshData.writeToBuffer(buf, pos); pos = pos + 16;
|
||||
buf.writeUInt8(this.type, pos);
|
||||
}
|
||||
getBuffer(): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(17);
|
||||
this.writeToBuffer(buf, 0);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,10 @@ export class SculptData
|
||||
this.texture.writeToBuffer(buf, pos); pos = pos + 16;
|
||||
buf.writeUInt8(this.type, pos);
|
||||
}
|
||||
getBuffer(): Buffer
|
||||
{
|
||||
const buf = Buffer.allocUnsafe(17);
|
||||
this.writeToBuffer(buf, 0);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
12
lib/classes/public/interfaces/LLPhysicsConvex.ts
Normal file
12
lib/classes/public/interfaces/LLPhysicsConvex.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {Vector3} from '../../Vector3';
|
||||
|
||||
export interface LLPhysicsConvex
|
||||
{
|
||||
hullList?: number[];
|
||||
positions?: Vector3[];
|
||||
boundingVerts: Vector3[];
|
||||
domain: {
|
||||
min: Vector3,
|
||||
max: Vector3
|
||||
}
|
||||
}
|
||||
10
lib/classes/public/interfaces/LLSkin.ts
Normal file
10
lib/classes/public/interfaces/LLSkin.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import {mat4} from '../../../tsm/mat4';
|
||||
|
||||
export interface LLSkin
|
||||
{
|
||||
jointNames: string[];
|
||||
bindShapeMatrix: mat4;
|
||||
inverseBindMatrix: mat4[];
|
||||
altInverseBindMatrix?: mat4[];
|
||||
pelvisOffset?: mat4;
|
||||
}
|
||||
21
lib/classes/public/interfaces/LLSubMesh.ts
Normal file
21
lib/classes/public/interfaces/LLSubMesh.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {Vector3} from '../../Vector3';
|
||||
import {Vector2} from '../../Vector2';
|
||||
|
||||
export interface LLSubMesh
|
||||
{
|
||||
noGeometry?: true,
|
||||
position?: Vector3[],
|
||||
positionDomain?: {
|
||||
min: Vector3,
|
||||
max: Vector3
|
||||
},
|
||||
normal?: Vector3[],
|
||||
texCoord0?: Vector2[],
|
||||
texCoord0Domain?: {
|
||||
min: Vector2,
|
||||
max: Vector2
|
||||
}
|
||||
triangleList?: number[],
|
||||
weights?: {[key: number]: number}[],
|
||||
|
||||
}
|
||||
32
lib/enums/FolderType.ts
Normal file
32
lib/enums/FolderType.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export enum FolderType
|
||||
{
|
||||
None = -1,
|
||||
Texture = 0,
|
||||
Sound = 1,
|
||||
CallingCard = 2,
|
||||
Landmark = 3,
|
||||
Clothing = 5,
|
||||
Object = 6,
|
||||
Notecard = 7,
|
||||
Root = 8,
|
||||
LSLText = 10,
|
||||
BodyPart = 13,
|
||||
Trash = 14,
|
||||
Snapshot = 15,
|
||||
LostAndFound = 16,
|
||||
Animation = 20,
|
||||
Gesture = 21,
|
||||
Favorites = 23,
|
||||
EnsembleStart = 26,
|
||||
EnsembleEnd = 45,
|
||||
CurrentOutfit = 46,
|
||||
Outfit = 47,
|
||||
MyOutfits = 48,
|
||||
Mesh = 49,
|
||||
Inbox = 50,
|
||||
Outbox = 51,
|
||||
BasicRoot = 52,
|
||||
MarketplaceListings = 53,
|
||||
MarkplaceStock = 54,
|
||||
Suitcase = 100
|
||||
}
|
||||
9
lib/enums/UpdateType.ts
Normal file
9
lib/enums/UpdateType.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export enum UpdateType
|
||||
{
|
||||
None = 0x00,
|
||||
Position = 0x01,
|
||||
Rotation = 0x02,
|
||||
Scale = 0x04,
|
||||
Linked = 0x08,
|
||||
Uniform = 0x10
|
||||
}
|
||||
6
lib/events/SelectedObjectEvent.ts
Normal file
6
lib/events/SelectedObjectEvent.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import {GameObject} from '..';
|
||||
|
||||
export class SelectedObjectEvent
|
||||
{
|
||||
object: GameObject
|
||||
}
|
||||
@@ -89,6 +89,8 @@ import {TransferStatus} from './enums/TransferStatus';
|
||||
import {LLWearable} from './classes/LLWearable';
|
||||
import {ParticleSystem} from './classes/ParticleSystem';
|
||||
import {ExtraParams} from './classes/public/ExtraParams';
|
||||
import {LLMesh} from './classes/public/LLMesh';
|
||||
import {FolderType} from './enums/FolderType';
|
||||
|
||||
export {
|
||||
Bot,
|
||||
@@ -98,7 +100,6 @@ export {
|
||||
ClientEvents,
|
||||
BVH,
|
||||
ChatSourceType,
|
||||
BotOptionFlags,
|
||||
UUID,
|
||||
Vector3,
|
||||
Vector2,
|
||||
@@ -107,9 +108,11 @@ export {
|
||||
LLWearable,
|
||||
ParticleSystem,
|
||||
ExtraParams,
|
||||
FolderType,
|
||||
|
||||
// Flags
|
||||
AgentFlags,
|
||||
BotOptionFlags,
|
||||
CompressedFlags,
|
||||
ControlFlags,
|
||||
DecodeFlags,
|
||||
@@ -183,6 +186,7 @@ export {
|
||||
RegionEnvironment,
|
||||
SculptData,
|
||||
MeshData,
|
||||
LLMesh,
|
||||
|
||||
// Public Interfaces
|
||||
GlobalPosition,
|
||||
|
||||
@@ -632,6 +632,10 @@ export class mat4
|
||||
|
||||
return this;
|
||||
}
|
||||
toArray()
|
||||
{
|
||||
return Array.from(this.values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user