mirror of
https://github.com/overte-org/overte.git
synced 2025-04-29 22:02:38 +02:00
250 lines
12 KiB
JavaScript
250 lines
12 KiB
JavaScript
"use strict";
|
|
/* eslint-env commonjs */
|
|
/* eslint-disable comma-dangle */
|
|
/* global console */
|
|
|
|
// var require = function(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); }
|
|
module.exports = DopplegangerAttachments;
|
|
|
|
DopplegangerAttachments.version = '0.0.1b';
|
|
DopplegangerAttachments.WANT_DEBUG = false;
|
|
|
|
var _modelHelper = require('./model-helper.js'),
|
|
modelHelper = _modelHelper.modelHelper,
|
|
ModelReadyWatcher = _modelHelper.ModelReadyWatcher,
|
|
utils = require('./utils.js');
|
|
|
|
function log() {
|
|
// eslint-disable-next-line no-console
|
|
(typeof console === 'object' ? console.log : print)('doppleganger-attachments | ' + [].slice.call(arguments).join(' '));
|
|
}
|
|
log(DopplegangerAttachments.version);
|
|
|
|
function debugPrint() {
|
|
DopplegangerAttachments.WANT_DEBUG && log.apply(this, arguments);
|
|
}
|
|
|
|
function DopplegangerAttachments(doppleganger, options) {
|
|
utils.assign(this, {
|
|
_options: options,
|
|
doppleganger: doppleganger,
|
|
attachments: undefined,
|
|
manualJointSync: true,
|
|
attachmentsUpdated: utils.signal(function attachmentsUpdated(currentAttachments, previousAttachments){}),
|
|
});
|
|
this._initialize();
|
|
debugPrint('DopplegangerAttachments...', JSON.stringify(options));
|
|
}
|
|
DopplegangerAttachments.prototype = {
|
|
// "hash" the current attachments (so that changes can be detected)
|
|
getAttachmentsHash: function() {
|
|
return JSON.stringify(this.doppleganger.avatar.getAttachmentsVariant());
|
|
},
|
|
// create a pure Object copy of the current native attachments variant
|
|
_cloneAttachmentsVariant: function() {
|
|
return JSON.parse(JSON.stringify(this.doppleganger.avatar.getAttachmentsVariant()));
|
|
},
|
|
// fetch and resolve attachments to include jointIndex and other relevant $metadata
|
|
_getResolvedAttachments: function() {
|
|
var attachments = this._cloneAttachmentsVariant(),
|
|
objectID = this.doppleganger.objectID;
|
|
function toString() {
|
|
return '[attachment #' + this.$index + ' ' + this.$path + ' @ ' + this.jointName + '{' + this.$jointIndex + '}]';
|
|
}
|
|
return attachments.map(function(attachment, i) {
|
|
var jointIndex = modelHelper.getJointIndex(objectID, attachment.jointName),
|
|
path = (attachment.modelUrl+'').split(/[?#]/)[0].split('/').slice(-3).join('/');
|
|
return Object.defineProperties(attachment, {
|
|
$hash: { value: JSON.stringify(attachment) },
|
|
$index: { value: i },
|
|
$jointIndex: { value: jointIndex },
|
|
$path: { value: path },
|
|
toString: { value: toString },
|
|
});
|
|
});
|
|
},
|
|
// compare before / after attachment sets to determine which ones need to be (re)created
|
|
refreshAttachments: function() {
|
|
if (!this.doppleganger.objectID) {
|
|
return log('refreshAttachments -- canceling; !this.doppleganger.objectID');
|
|
}
|
|
var before = this.attachments || [],
|
|
beforeIndex = before.reduce(function(out, att, index) {
|
|
out[att.$hash] = index; return out;
|
|
}, {});
|
|
var after = this._getResolvedAttachments(),
|
|
afterIndex = after.reduce(function(out, att, index) {
|
|
out[att.$hash] = index; return out;
|
|
}, {});
|
|
|
|
Object.keys(beforeIndex).concat(Object.keys(afterIndex)).forEach(function(hash) {
|
|
if (hash in beforeIndex && hash in afterIndex) {
|
|
// print('reusing previous attachment', hash);
|
|
after[afterIndex[hash]] = before[beforeIndex[hash]];
|
|
} else if (!(hash in afterIndex)) {
|
|
var attachment = before[beforeIndex[hash]];
|
|
attachment.properties && attachment.properties.objectID &&
|
|
modelHelper.deleteObject(attachment.properties.objectID);
|
|
delete attachment.properties;
|
|
}
|
|
});
|
|
this.attachments = after;
|
|
this._createAttachmentObjects();
|
|
this.attachmentsUpdated(after, before);
|
|
},
|
|
_createAttachmentObjects: function() {
|
|
try {
|
|
var attachments = this.attachments,
|
|
parentID = this.doppleganger.objectID,
|
|
jointNames = this.doppleganger.jointNames,
|
|
properties = modelHelper.getProperties(this.doppleganger.objectID),
|
|
modelType = properties && properties.type;
|
|
utils.assert(modelType === 'model' || modelType === 'Model', 'unrecognized doppleganger modelType:' + modelType);
|
|
debugPrint('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({
|
|
modelType: modelType,
|
|
attachments: attachments.length,
|
|
parentID: parentID,
|
|
jointNames: jointNames.join(' | '),
|
|
},0,2));
|
|
return attachments.map(utils.bind(this, function(attachment, i) {
|
|
var objectType = modelHelper.type(attachment.properties && attachment.properties.objectID);
|
|
if (objectType === 'overlay') {
|
|
debugPrint('skipping already-provisioned attachment object', objectType, attachment.properties && attachment.properties.name);
|
|
return attachment;
|
|
}
|
|
var jointIndex = attachment.$jointIndex, // jointNames.indexOf(attachment.jointName),
|
|
scale = this.doppleganger.avatar.scale * (attachment.scale||1.0);
|
|
|
|
attachment.properties = utils.assign({
|
|
name: attachment.toString(),
|
|
type: modelType,
|
|
modelURL: attachment.modelUrl,
|
|
scale: scale,
|
|
dimensions: modelHelper.type(parentID) === 'entity' ?
|
|
Vec3.multiply(attachment.scale||1.0, Vec3.ONE) : undefined,
|
|
visible: false,
|
|
collisionless: true,
|
|
dynamic: false,
|
|
shapeType: 'none',
|
|
lifetime: 60,
|
|
}, !this.manualJointSync && {
|
|
parentID: parentID,
|
|
parentJointIndex: jointIndex,
|
|
localPosition: attachment.translation,
|
|
localRotation: Quat.fromVec3Degrees(attachment.rotation),
|
|
});
|
|
var objectID = attachment.properties.objectID = modelHelper.addObject(attachment.properties);
|
|
utils.assert(!Uuid.isNull(objectID), 'could not create attachment: ' + [objectID, JSON.stringify(attachment.properties,0,2)]);
|
|
attachment._resource = ModelCache.prefetch(attachment.properties.modelURL);
|
|
attachment._modelReadier = new ModelReadyWatcher({
|
|
resource: attachment._resource,
|
|
objectID: objectID,
|
|
});
|
|
this.doppleganger.activeChanged.connect(attachment._modelReadier, '_stop');
|
|
|
|
attachment._modelReadier.modelReady.connect(this, function(err, result) {
|
|
if (err) {
|
|
log('>>>>> modelReady ERROR <<<<<: ' + err, attachment.properties.modelURL);
|
|
modelHelper.deleteObject(objectID);
|
|
return objectID = null;
|
|
}
|
|
debugPrint('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==',
|
|
result.jointNames && result.jointNames.length, JSON.stringify(result.naturalDimensions), result.objectID);
|
|
var properties = modelHelper.getProperties(result.objectID),
|
|
naturalDimensions = attachment.properties.naturalDimensions = properties.naturalDimensions || result.naturalDimensions;
|
|
modelHelper.editObject(objectID, {
|
|
dimensions: naturalDimensions ? Vec3.multiply(attachment.scale, naturalDimensions) : undefined,
|
|
localRotation: Quat.normalize({}),
|
|
localPosition: Vec3.ZERO,
|
|
});
|
|
this.onJointsUpdated(parentID); // trigger once to initialize position/rotation
|
|
// give time for model overlay to "settle", then make it visible
|
|
Script.setTimeout(utils.bind(this, function() {
|
|
modelHelper.editObject(objectID, {
|
|
visible: true,
|
|
});
|
|
attachment._loaded = true;
|
|
}), 100);
|
|
});
|
|
return attachment;
|
|
}));
|
|
} catch (e) {
|
|
log('_createAttachmentObjects ERROR:', e.stack || e, e.fileName, e.lineNumber);
|
|
}
|
|
},
|
|
|
|
_removeAttachmentObjects: function() {
|
|
if (this.attachments) {
|
|
this.attachments.forEach(function(attachment) {
|
|
if (attachment.properties) {
|
|
if (attachment.properties.objectID) {
|
|
modelHelper.deleteObject(attachment.properties.objectID);
|
|
}
|
|
delete attachment.properties.objectID;
|
|
}
|
|
});
|
|
delete this.attachments;
|
|
}
|
|
},
|
|
|
|
onJointsUpdated: function onJointsUpdated(objectID, jointUpdates) {
|
|
var jointOrientations = modelHelper.getJointOrientations(objectID),
|
|
jointPositions = modelHelper.getJointPositions(objectID),
|
|
parentID = objectID,
|
|
avatarScale = this.doppleganger.scale,
|
|
manualJointSync = this.manualJointSync;
|
|
|
|
if (!this.attachments) {
|
|
this.refreshAttachments();
|
|
}
|
|
var updatedObjects = this.attachments.reduce(function(updates, attachment, i) {
|
|
if (!attachment.properties || !attachment._loaded) {
|
|
return updates;
|
|
}
|
|
var objectID = attachment.properties.objectID,
|
|
jointIndex = attachment.$jointIndex,
|
|
jointOrientation = jointOrientations[jointIndex],
|
|
jointPosition = jointPositions[jointIndex];
|
|
|
|
var translation = Vec3.multiply(avatarScale, attachment.translation),
|
|
rotation = Quat.fromVec3Degrees(attachment.rotation);
|
|
|
|
var localPosition = Vec3.multiplyQbyV(jointOrientation, translation),
|
|
localRotation = rotation;
|
|
|
|
updates[objectID] = manualJointSync ? {
|
|
visible: true,
|
|
position: Vec3.sum(jointPosition, localPosition),
|
|
rotation: Quat.multiply(jointOrientation, localRotation),
|
|
scale: avatarScale * attachment.scale,
|
|
} : {
|
|
visible: true,
|
|
parentID: parentID,
|
|
parentJointIndex: jointIndex,
|
|
localRotation: localRotation,
|
|
localPosition: localPosition,
|
|
scale: attachment.scale,
|
|
};
|
|
return updates;
|
|
}, {});
|
|
modelHelper.editObjects(updatedObjects);
|
|
},
|
|
|
|
_initialize: function() {
|
|
var doppleganger = this.doppleganger;
|
|
if ('$attachmentControls' in doppleganger) {
|
|
throw new Error('only one set of attachment controls can be added per doppleganger');
|
|
}
|
|
doppleganger.$attachmentControls = this;
|
|
doppleganger.activeChanged.connect(this, function(active) {
|
|
if (active) {
|
|
doppleganger.jointsUpdated.connect(this, 'onJointsUpdated');
|
|
} else {
|
|
doppleganger.jointsUpdated.disconnect(this, 'onJointsUpdated');
|
|
this._removeAttachmentObjects();
|
|
}
|
|
});
|
|
|
|
Script.scriptEnding.connect(this, '_removeAttachmentObjects');
|
|
},
|
|
};
|