overte-HifiExperiments/libraries/models/src/ModelItem.cpp

1338 lines
46 KiB
C++

//
// ModelItem.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QObject>
#include <Octree.h>
#include <RegisteredMetaTypes.h>
#include <SharedUtil.h> // usecTimestampNow()
#include <VoxelsScriptingInterface.h>
#include <VoxelDetail.h>
// This is not ideal, but adding script-engine as a linked library, will cause a circular reference
// I'm open to other potential solutions. Could we change cmake to allow libraries to reference each others
// headers, but not link to each other, this is essentially what this construct is doing, but would be
// better to add includes to the include path, but not link
#include "../../script-engine/src/ScriptEngine.h"
#include "ModelsScriptingInterface.h"
#include "ModelItem.h"
#include "ModelTree.h"
uint32_t ModelItem::_nextID = 0;
// for locally created models
std::map<uint32_t,uint32_t> ModelItem::_tokenIDsToIDs;
uint32_t ModelItem::_nextCreatorTokenID = 0;
uint32_t ModelItem::getIDfromCreatorTokenID(uint32_t creatorTokenID) {
if (_tokenIDsToIDs.find(creatorTokenID) != _tokenIDsToIDs.end()) {
return _tokenIDsToIDs[creatorTokenID];
}
return UNKNOWN_MODEL_ID;
}
uint32_t ModelItem::getNextCreatorTokenID() {
uint32_t creatorTokenID = _nextCreatorTokenID;
_nextCreatorTokenID++;
return creatorTokenID;
}
void ModelItem::handleAddModelResponse(const QByteArray& packet) {
const unsigned char* dataAt = reinterpret_cast<const unsigned char*>(packet.data());
int numBytesPacketHeader = numBytesForPacketHeader(packet);
dataAt += numBytesPacketHeader;
uint32_t creatorTokenID;
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
dataAt += sizeof(creatorTokenID);
uint32_t modelItemID;
memcpy(&modelItemID, dataAt, sizeof(modelItemID));
dataAt += sizeof(modelItemID);
// add our token to id mapping
_tokenIDsToIDs[creatorTokenID] = modelItemID;
}
ModelItem::ModelItem() {
rgbColor noColor = { 0, 0, 0 };
init(glm::vec3(0,0,0), 0, noColor, NEW_MODEL);
}
ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& properties) {
_id = modelItemID.id;
_creatorTokenID = modelItemID.creatorTokenID;
// init values with defaults before calling setProperties
uint64_t now = usecTimestampNow();
_lastEdited = now;
_lastUpdated = now;
_position = glm::vec3(0,0,0);
_radius = 0;
rgbColor noColor = { 0, 0, 0 };
memcpy(_color, noColor, sizeof(_color));
_shouldDie = false;
_modelURL = MODEL_DEFAULT_MODEL_URL;
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
// animation related
_animationURL = MODEL_DEFAULT_ANIMATION_URL;
_animationIsPlaying = false;
_animationFrameIndex = 0.0f;
_animationFPS = MODEL_DEFAULT_ANIMATION_FPS;
_glowLevel = 0.0f;
_jointMappingCompleted = false;
_lastAnimated = now;
setProperties(properties);
}
ModelItem::~ModelItem() {
}
void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t id) {
if (id == NEW_MODEL) {
_id = _nextID;
_nextID++;
} else {
_id = id;
}
quint64 now = usecTimestampNow();
_lastEdited = now;
_lastUpdated = now;
_position = position;
_radius = radius;
memcpy(_color, color, sizeof(_color));
_shouldDie = false;
_modelURL = MODEL_DEFAULT_MODEL_URL;
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
// animation related
_animationURL = MODEL_DEFAULT_ANIMATION_URL;
_animationIsPlaying = false;
_animationFrameIndex = 0.0f;
_animationFPS = MODEL_DEFAULT_ANIMATION_FPS;
_glowLevel = 0.0f;
_jointMappingCompleted = false;
_lastAnimated = now;
}
bool ModelItem::appendModelData(OctreePacketData* packetData) const {
bool success = packetData->appendValue(getID());
if (success) {
success = packetData->appendValue(getLastUpdated());
}
if (success) {
success = packetData->appendValue(getLastEdited());
}
if (success) {
success = packetData->appendValue(getRadius());
}
if (success) {
success = packetData->appendPosition(getPosition());
}
if (success) {
success = packetData->appendColor(getColor());
}
if (success) {
success = packetData->appendValue(getShouldDie());
}
// modelURL
if (success) {
uint16_t modelURLLength = _modelURL.size() + 1; // include NULL
success = packetData->appendValue(modelURLLength);
if (success) {
success = packetData->appendRawData((const unsigned char*)qPrintable(_modelURL), modelURLLength);
}
}
// modelRotation
if (success) {
success = packetData->appendValue(getModelRotation());
}
// animationURL
if (success) {
uint16_t animationURLLength = _animationURL.size() + 1; // include NULL
success = packetData->appendValue(animationURLLength);
if (success) {
success = packetData->appendRawData((const unsigned char*)qPrintable(_animationURL), animationURLLength);
}
}
// animationIsPlaying
if (success) {
success = packetData->appendValue(getAnimationIsPlaying());
}
// animationFrameIndex
if (success) {
success = packetData->appendValue(getAnimationFrameIndex());
}
// animationFPS
if (success) {
success = packetData->appendValue(getAnimationFPS());
}
return success;
}
bool ModelItem::new___appendModelData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
// bool headerFits = ...
// ALL this fits...
// object ID [16 bytes]
// ByteCountCoded(type code) [~1 byte]
// last edited [8 bytes]
// ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes]
// PropertyFlags<>( everything ) [1-2 bytes]
// ~27-35 bytes...
bool success = false;
quint16 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited();
ModelPropertyFlags propertyFlags(PROP_LAST_ITEM);
LevelDetails modelLevel = packetData->startLevel();
bool successIDFits = packetData->appendValue(getID());
bool successTypeFits = packetData->appendValue(getType());
bool successLastEditedFits = packetData->appendValue(getLastEdited());
bool successLastUpdatedFits = packetData->appendValue(updateDelta);
int propertyFlagsOffset = packetData->getUncompressedByteOffset();
bool successPropertyFlagsFits = packetData->appendValue(propertyFlags);
int propertyCount = 0;
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
&& successLastUpdatedFits && successPropertyFlagsFits;
if (headerFits) {
bool successPropertyFits;
propertyFlags -= PROP_LAST_ITEM; // clear the last item for now, we may or may not set it as the actual item
// These items would go here once supported....
// PROP_PAGED_PROPERTY,
// PROP_CUSTOM_PROPERTIES_INCLUDED,
// PROP_VISIBLE,
// PROP_POSITION
LevelDetails propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendPosition(getPosition());
if (successPropertyFits) {
propertyFlags |= PROP_POSITION;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_RADIUS
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getRadius());
if (successPropertyFits) {
propertyFlags |= PROP_RADIUS;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_MODEL_URL
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getModelURL());
if (successPropertyFits) {
propertyFlags |= PROP_MODEL_URL;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_ROTATION
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getModelRotation());
if (successPropertyFits) {
propertyFlags |= PROP_ROTATION;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_ROTATION
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendColor(getColor());
if (successPropertyFits) {
propertyFlags |= PROP_COLOR;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_SCRIPT
// script would go here...
// PROP_ANIMATION_URL
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getAnimationURL());
if (successPropertyFits) {
propertyFlags |= PROP_ANIMATION_URL;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_ANIMATION_FPS
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getAnimationFPS());
if (successPropertyFits) {
propertyFlags |= PROP_ANIMATION_FPS;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_ANIMATION_FRAME_INDEX
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getAnimationFrameIndex());
if (successPropertyFits) {
propertyFlags |= PROP_ANIMATION_FRAME_INDEX;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_ANIMATION_PLAYING
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getAnimationIsPlaying());
if (successPropertyFits) {
propertyFlags |= PROP_ANIMATION_PLAYING;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
// PROP_SHOULD_BE_DELETED
propertyLevel = packetData->startLevel();
successPropertyFits = packetData->appendValue(getShouldDie());
if (successPropertyFits) {
propertyFlags |= PROP_SHOULD_BE_DELETED;
propertyCount++;
packetData->endLevel(propertyLevel);
} else {
packetData->discardLevel(propertyLevel);
}
}
if (propertyCount > 0) {
// we need to...
// * update the PropertyFlags data.
// * shift the property stream "to left" if the property flags shrunk.
packetData->endLevel(modelLevel);
success = true;
} else {
packetData->discardLevel(modelLevel);
}
return success;
}
int ModelItem::expectedBytes() {
int expectedBytes = sizeof(uint32_t) // id
+ sizeof(float) // age
+ sizeof(quint64) // last updated
+ sizeof(quint64) // lasted edited
+ sizeof(float) // radius
+ sizeof(glm::vec3) // position
+ sizeof(rgbColor); // color
// potentially more...
return expectedBytes;
}
int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
int bytesRead = 0;
if (bytesLeftToRead >= expectedBytes()) {
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
const unsigned char* dataAt = data;
// id
memcpy(&_id, dataAt, sizeof(_id));
dataAt += sizeof(_id);
bytesRead += sizeof(_id);
// _lastUpdated
memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated));
dataAt += sizeof(_lastUpdated);
bytesRead += sizeof(_lastUpdated);
_lastUpdated -= clockSkew;
// _lastEdited
memcpy(&_lastEdited, dataAt, sizeof(_lastEdited));
dataAt += sizeof(_lastEdited);
bytesRead += sizeof(_lastEdited);
_lastEdited -= clockSkew;
// radius
memcpy(&_radius, dataAt, sizeof(_radius));
dataAt += sizeof(_radius);
bytesRead += sizeof(_radius);
// position
memcpy(&_position, dataAt, sizeof(_position));
dataAt += sizeof(_position);
bytesRead += sizeof(_position);
// color
memcpy(_color, dataAt, sizeof(_color));
dataAt += sizeof(_color);
bytesRead += sizeof(_color);
// shouldDie
memcpy(&_shouldDie, dataAt, sizeof(_shouldDie));
dataAt += sizeof(_shouldDie);
bytesRead += sizeof(_shouldDie);
// modelURL
uint16_t modelURLLength;
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
dataAt += sizeof(modelURLLength);
bytesRead += sizeof(modelURLLength);
QString modelURLString((const char*)dataAt);
setModelURL(modelURLString);
dataAt += modelURLLength;
bytesRead += modelURLLength;
// modelRotation
int bytes = unpackOrientationQuatFromBytes(dataAt, _modelRotation);
dataAt += bytes;
bytesRead += bytes;
if (args.bitstreamVersion >= VERSION_MODELS_HAVE_ANIMATION) {
// animationURL
uint16_t animationURLLength;
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
dataAt += sizeof(animationURLLength);
bytesRead += sizeof(animationURLLength);
QString animationURLString((const char*)dataAt);
setAnimationURL(animationURLString);
dataAt += animationURLLength;
bytesRead += animationURLLength;
// animationIsPlaying
memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying));
dataAt += sizeof(_animationIsPlaying);
bytesRead += sizeof(_animationIsPlaying);
// animationFrameIndex
memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex));
dataAt += sizeof(_animationFrameIndex);
bytesRead += sizeof(_animationFrameIndex);
// animationFPS
memcpy(&_animationFPS, dataAt, sizeof(_animationFPS));
dataAt += sizeof(_animationFPS);
bytesRead += sizeof(_animationFPS);
}
}
return bytesRead;
}
ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid) {
ModelItem newModelItem; // id and _lastUpdated will get set here...
const unsigned char* dataAt = data;
processedBytes = 0;
// the first part of the data is our octcode...
int octets = numberOfThreeBitSectionsInCode(data);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
// we don't actually do anything with this octcode...
dataAt += lengthOfOctcode;
processedBytes += lengthOfOctcode;
// id
uint32_t editID;
memcpy(&editID, dataAt, sizeof(editID));
dataAt += sizeof(editID);
processedBytes += sizeof(editID);
bool isNewModelItem = (editID == NEW_MODEL);
// special case for handling "new" modelItems
if (isNewModelItem) {
// If this is a NEW_MODEL, then we assume that there's an additional uint32_t creatorToken, that
// we want to send back to the creator as an map to the actual id
uint32_t creatorTokenID;
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
dataAt += sizeof(creatorTokenID);
processedBytes += sizeof(creatorTokenID);
newModelItem.setCreatorTokenID(creatorTokenID);
newModelItem._newlyCreated = true;
} else {
// look up the existing modelItem
const ModelItem* existingModelItem = tree->findModelByID(editID, true);
// copy existing properties before over-writing with new properties
if (existingModelItem) {
newModelItem = *existingModelItem;
} else {
// the user attempted to edit a modelItem that doesn't exist
qDebug() << "user attempted to edit a modelItem that doesn't exist...";
valid = false;
return newModelItem;
}
newModelItem._id = editID;
newModelItem._newlyCreated = false;
}
// if we got this far, then our result will be valid
valid = true;
// lastEdited
memcpy(&newModelItem._lastEdited, dataAt, sizeof(newModelItem._lastEdited));
dataAt += sizeof(newModelItem._lastEdited);
processedBytes += sizeof(newModelItem._lastEdited);
// All of the remaining items are optional, and may or may not be included based on their included values in the
// properties included bits
uint16_t packetContainsBits = 0;
if (!isNewModelItem) {
memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits));
dataAt += sizeof(packetContainsBits);
processedBytes += sizeof(packetContainsBits);
}
// radius
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_RADIUS) == MODEL_PACKET_CONTAINS_RADIUS)) {
memcpy(&newModelItem._radius, dataAt, sizeof(newModelItem._radius));
dataAt += sizeof(newModelItem._radius);
processedBytes += sizeof(newModelItem._radius);
}
// position
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_POSITION) == MODEL_PACKET_CONTAINS_POSITION)) {
memcpy(&newModelItem._position, dataAt, sizeof(newModelItem._position));
dataAt += sizeof(newModelItem._position);
processedBytes += sizeof(newModelItem._position);
}
// color
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_COLOR) == MODEL_PACKET_CONTAINS_COLOR)) {
memcpy(newModelItem._color, dataAt, sizeof(newModelItem._color));
dataAt += sizeof(newModelItem._color);
processedBytes += sizeof(newModelItem._color);
}
// shouldDie
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) {
memcpy(&newModelItem._shouldDie, dataAt, sizeof(newModelItem._shouldDie));
dataAt += sizeof(newModelItem._shouldDie);
processedBytes += sizeof(newModelItem._shouldDie);
}
// modelURL
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) {
uint16_t modelURLLength;
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
dataAt += sizeof(modelURLLength);
processedBytes += sizeof(modelURLLength);
QString tempString((const char*)dataAt);
newModelItem._modelURL = tempString;
dataAt += modelURLLength;
processedBytes += modelURLLength;
}
// modelRotation
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
int bytes = unpackOrientationQuatFromBytes(dataAt, newModelItem._modelRotation);
dataAt += bytes;
processedBytes += bytes;
}
// animationURL
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) {
uint16_t animationURLLength;
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
dataAt += sizeof(animationURLLength);
processedBytes += sizeof(animationURLLength);
QString tempString((const char*)dataAt);
newModelItem._animationURL = tempString;
dataAt += animationURLLength;
processedBytes += animationURLLength;
}
// animationIsPlaying
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) {
memcpy(&newModelItem._animationIsPlaying, dataAt, sizeof(newModelItem._animationIsPlaying));
dataAt += sizeof(newModelItem._animationIsPlaying);
processedBytes += sizeof(newModelItem._animationIsPlaying);
}
// animationFrameIndex
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) {
memcpy(&newModelItem._animationFrameIndex, dataAt, sizeof(newModelItem._animationFrameIndex));
dataAt += sizeof(newModelItem._animationFrameIndex);
processedBytes += sizeof(newModelItem._animationFrameIndex);
}
// animationFPS
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) {
memcpy(&newModelItem._animationFPS, dataAt, sizeof(newModelItem._animationFPS));
dataAt += sizeof(newModelItem._animationFPS);
processedBytes += sizeof(newModelItem._animationFPS);
}
const bool wantDebugging = false;
if (wantDebugging) {
qDebug("ModelItem::fromEditPacket()...");
qDebug() << " ModelItem id in packet:" << editID;
newModelItem.debugDump();
}
return newModelItem;
}
void ModelItem::debugDump() const {
qDebug("ModelItem id :%u", _id);
qDebug(" edited ago:%f", getEditedAgo());
qDebug(" should die:%s", debug::valueOf(getShouldDie()));
qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z);
qDebug(" radius:%f", getRadius());
qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]);
qDebug() << " modelURL:" << qPrintable(getModelURL());
}
bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& properties,
unsigned char* bufferOut, int sizeIn, int& sizeOut) {
bool success = true; // assume the best
unsigned char* copyAt = bufferOut;
sizeOut = 0;
// get the octal code for the modelItem
// this could be a problem if the caller doesn't include position....
glm::vec3 rootPosition(0);
float rootScale = 0.5f;
unsigned char* octcode = pointToOctalCode(rootPosition.x, rootPosition.y, rootPosition.z, rootScale);
// TODO: Consider this old code... including the correct octree for where the modelItem will go matters for
// modelItem servers with different jurisdictions, but for now, we'll send everything to the root, since the
// tree does the right thing...
//
//unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
// details[i].position.z, details[i].radius);
int octets = numberOfThreeBitSectionsInCode(octcode);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
// add it to our message
memcpy(copyAt, octcode, lengthOfOctcode);
copyAt += lengthOfOctcode;
sizeOut += lengthOfOctcode;
// Now add our edit content details...
bool isNewModelItem = (id.id == NEW_MODEL);
// id
memcpy(copyAt, &id.id, sizeof(id.id));
copyAt += sizeof(id.id);
sizeOut += sizeof(id.id);
// special case for handling "new" modelItems
if (isNewModelItem) {
// If this is a NEW_MODEL, then we assume that there's an additional uint32_t creatorToken, that
// we want to send back to the creator as an map to the actual id
memcpy(copyAt, &id.creatorTokenID, sizeof(id.creatorTokenID));
copyAt += sizeof(id.creatorTokenID);
sizeOut += sizeof(id.creatorTokenID);
}
// lastEdited
quint64 lastEdited = properties.getLastEdited();
memcpy(copyAt, &lastEdited, sizeof(lastEdited));
copyAt += sizeof(lastEdited);
sizeOut += sizeof(lastEdited);
// For new modelItems, all remaining items are mandatory, for an edited modelItem, All of the remaining items are
// optional, and may or may not be included based on their included values in the properties included bits
uint16_t packetContainsBits = properties.getChangedBits();
if (!isNewModelItem) {
memcpy(copyAt, &packetContainsBits, sizeof(packetContainsBits));
copyAt += sizeof(packetContainsBits);
sizeOut += sizeof(packetContainsBits);
}
// radius
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_RADIUS) == MODEL_PACKET_CONTAINS_RADIUS)) {
float radius = properties.getRadius() / (float) TREE_SCALE;
memcpy(copyAt, &radius, sizeof(radius));
copyAt += sizeof(radius);
sizeOut += sizeof(radius);
}
// position
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_POSITION) == MODEL_PACKET_CONTAINS_POSITION)) {
glm::vec3 position = properties.getPosition() / (float)TREE_SCALE;
memcpy(copyAt, &position, sizeof(position));
copyAt += sizeof(position);
sizeOut += sizeof(position);
}
// color
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_COLOR) == MODEL_PACKET_CONTAINS_COLOR)) {
rgbColor color = { properties.getColor().red, properties.getColor().green, properties.getColor().blue };
memcpy(copyAt, color, sizeof(color));
copyAt += sizeof(color);
sizeOut += sizeof(color);
}
// shoulDie
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) {
bool shouldDie = properties.getShouldDie();
memcpy(copyAt, &shouldDie, sizeof(shouldDie));
copyAt += sizeof(shouldDie);
sizeOut += sizeof(shouldDie);
}
// modelURL
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) {
uint16_t urlLength = properties.getModelURL().size() + 1;
memcpy(copyAt, &urlLength, sizeof(urlLength));
copyAt += sizeof(urlLength);
sizeOut += sizeof(urlLength);
memcpy(copyAt, qPrintable(properties.getModelURL()), urlLength);
copyAt += urlLength;
sizeOut += urlLength;
}
// modelRotation
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
int bytes = packOrientationQuatToBytes(copyAt, properties.getModelRotation());
copyAt += bytes;
sizeOut += bytes;
}
// animationURL
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) {
uint16_t urlLength = properties.getAnimationURL().size() + 1;
memcpy(copyAt, &urlLength, sizeof(urlLength));
copyAt += sizeof(urlLength);
sizeOut += sizeof(urlLength);
memcpy(copyAt, qPrintable(properties.getAnimationURL()), urlLength);
copyAt += urlLength;
sizeOut += urlLength;
}
// animationIsPlaying
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) {
bool animationIsPlaying = properties.getAnimationIsPlaying();
memcpy(copyAt, &animationIsPlaying, sizeof(animationIsPlaying));
copyAt += sizeof(animationIsPlaying);
sizeOut += sizeof(animationIsPlaying);
}
// animationFrameIndex
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) {
float animationFrameIndex = properties.getAnimationFrameIndex();
memcpy(copyAt, &animationFrameIndex, sizeof(animationFrameIndex));
copyAt += sizeof(animationFrameIndex);
sizeOut += sizeof(animationFrameIndex);
}
// animationFPS
if (isNewModelItem || ((packetContainsBits &
MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) {
float animationFPS = properties.getAnimationFPS();
memcpy(copyAt, &animationFPS, sizeof(animationFPS));
copyAt += sizeof(animationFPS);
sizeOut += sizeof(animationFPS);
}
bool wantDebugging = false;
if (wantDebugging) {
qDebug("encodeModelItemEditMessageDetails()....");
qDebug("ModelItem id :%u", id.id);
qDebug(" nextID:%u", _nextID);
}
// cleanup
delete[] octcode;
return success;
}
// adjust any internal timestamps to fix clock skew for this server
void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) {
unsigned char* dataAt = codeColorBuffer;
int octets = numberOfThreeBitSectionsInCode(dataAt);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
dataAt += lengthOfOctcode;
// id
uint32_t id;
memcpy(&id, dataAt, sizeof(id));
dataAt += sizeof(id);
// special case for handling "new" modelItems
if (id == NEW_MODEL) {
// If this is a NEW_MODEL, then we assume that there's an additional uint32_t creatorToken, that
// we want to send back to the creator as an map to the actual id
dataAt += sizeof(uint32_t);
}
// lastEdited
quint64 lastEditedInLocalTime;
memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime));
quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew;
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
const bool wantDebug = false;
if (wantDebug) {
qDebug("ModelItem::adjustEditPacketForClockSkew()...");
qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime;
qDebug() << " clockSkew: " << clockSkew;
qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime;
}
}
QMap<QString, AnimationPointer> ModelItem::_loadedAnimations; // TODO: improve cleanup by leveraging the AnimationPointer(s)
AnimationCache ModelItem::_animationCache;
// This class/instance will cleanup the animations once unloaded.
class ModelAnimationsBookkeeper {
public:
~ModelAnimationsBookkeeper() {
ModelItem::cleanupLoadedAnimations();
}
};
ModelAnimationsBookkeeper modelAnimationsBookkeeperInstance;
void ModelItem::cleanupLoadedAnimations() {
foreach(AnimationPointer animation, _loadedAnimations) {
animation.clear();
}
_loadedAnimations.clear();
}
Animation* ModelItem::getAnimation(const QString& url) {
AnimationPointer animation;
// if we don't already have this model then create it and initialize it
if (_loadedAnimations.find(url) == _loadedAnimations.end()) {
animation = _animationCache.getAnimation(url);
_loadedAnimations[url] = animation;
} else {
animation = _loadedAnimations[url];
}
return animation.data();
}
void ModelItem::mapJoints(const QStringList& modelJointNames) {
// if we don't have animation, or we're already joint mapped then bail early
if (!hasAnimation() || _jointMappingCompleted) {
return;
}
Animation* myAnimation = getAnimation(_animationURL);
if (!_jointMappingCompleted) {
QStringList animationJointNames = myAnimation->getJointNames();
if (modelJointNames.size() > 0 && animationJointNames.size() > 0) {
_jointMapping.resize(modelJointNames.size());
for (int i = 0; i < modelJointNames.size(); i++) {
_jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]);
}
_jointMappingCompleted = true;
}
}
}
QVector<glm::quat> ModelItem::getAnimationFrame() {
QVector<glm::quat> frameData;
if (hasAnimation() && _jointMappingCompleted) {
Animation* myAnimation = getAnimation(_animationURL);
QVector<FBXAnimationFrame> frames = myAnimation->getFrames();
int frameCount = frames.size();
if (frameCount > 0) {
int animationFrameIndex = (int)glm::floor(_animationFrameIndex) % frameCount;
QVector<glm::quat> rotations = frames[animationFrameIndex].rotations;
frameData.resize(_jointMapping.size());
for (int j = 0; j < _jointMapping.size(); j++) {
int rotationIndex = _jointMapping[j];
if (rotationIndex != -1 && rotationIndex < rotations.size()) {
frameData[j] = rotations[rotationIndex];
}
}
}
}
return frameData;
}
void ModelItem::update(const quint64& updateTime) {
_lastUpdated = updateTime;
setShouldDie(getShouldDie());
quint64 now = usecTimestampNow();
// only advance the frame index if we're playing
if (getAnimationIsPlaying()) {
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
const bool wantDebugging = false;
if (wantDebugging) {
qDebug() << "ModelItem::update() now=" << now;
qDebug() << " updateTime=" << updateTime;
qDebug() << " _lastAnimated=" << _lastAnimated;
qDebug() << " deltaTime=" << deltaTime;
}
_lastAnimated = now;
_animationFrameIndex += deltaTime * _animationFPS;
if (wantDebugging) {
qDebug() << " _animationFrameIndex=" << _animationFrameIndex;
}
} else {
_lastAnimated = now;
}
}
void ModelItem::copyChangedProperties(const ModelItem& other) {
*this = other;
}
ModelItemProperties ModelItem::getProperties() const {
ModelItemProperties properties;
properties.copyFromModelItem(*this);
return properties;
}
void ModelItem::setProperties(const ModelItemProperties& properties) {
properties.copyToModelItem(*this);
}
ModelItemProperties::ModelItemProperties() :
_position(0),
_color(),
_radius(MODEL_DEFAULT_RADIUS),
_shouldDie(false),
_modelURL(""),
_modelRotation(MODEL_DEFAULT_MODEL_ROTATION),
_animationURL(""),
_animationIsPlaying(false),
_animationFrameIndex(0.0),
_animationFPS(MODEL_DEFAULT_ANIMATION_FPS),
_glowLevel(0.0f),
_id(UNKNOWN_MODEL_ID),
_idSet(false),
_lastEdited(usecTimestampNow()),
_positionChanged(false),
_colorChanged(false),
_radiusChanged(false),
_shouldDieChanged(false),
_modelURLChanged(false),
_modelRotationChanged(false),
_animationURLChanged(false),
_animationIsPlayingChanged(false),
_animationFrameIndexChanged(false),
_animationFPSChanged(false),
_glowLevelChanged(false),
_defaultSettings(true)
{
}
uint16_t ModelItemProperties::getChangedBits() const {
uint16_t changedBits = 0;
if (_radiusChanged) {
changedBits += MODEL_PACKET_CONTAINS_RADIUS;
}
if (_positionChanged) {
changedBits += MODEL_PACKET_CONTAINS_POSITION;
}
if (_colorChanged) {
changedBits += MODEL_PACKET_CONTAINS_COLOR;
}
if (_shouldDieChanged) {
changedBits += MODEL_PACKET_CONTAINS_SHOULDDIE;
}
if (_modelURLChanged) {
changedBits += MODEL_PACKET_CONTAINS_MODEL_URL;
}
if (_modelRotationChanged) {
changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION;
}
if (_animationURLChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_URL;
}
if (_animationIsPlayingChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_PLAYING;
}
if (_animationFrameIndexChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FRAME;
}
if (_animationFPSChanged) {
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FPS;
}
return changedBits;
}
QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const {
QScriptValue properties = engine->newObject();
QScriptValue position = vec3toScriptValue(engine, _position);
properties.setProperty("position", position);
QScriptValue color = xColorToScriptValue(engine, _color);
properties.setProperty("color", color);
properties.setProperty("radius", _radius);
properties.setProperty("shouldDie", _shouldDie);
properties.setProperty("modelURL", _modelURL);
QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation);
properties.setProperty("modelRotation", modelRotation);
properties.setProperty("animationURL", _animationURL);
properties.setProperty("animationIsPlaying", _animationIsPlaying);
properties.setProperty("animationFrameIndex", _animationFrameIndex);
properties.setProperty("animationFPS", _animationFPS);
properties.setProperty("glowLevel", _glowLevel);
if (_idSet) {
properties.setProperty("id", _id);
properties.setProperty("isKnownID", (_id != UNKNOWN_MODEL_ID));
}
return properties;
}
void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) {
QScriptValue position = object.property("position");
if (position.isValid()) {
QScriptValue x = position.property("x");
QScriptValue y = position.property("y");
QScriptValue z = position.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
if (_defaultSettings || newPosition != _position) {
_position = newPosition;
_positionChanged = true;
}
}
}
QScriptValue color = object.property("color");
if (color.isValid()) {
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
xColor newColor;
newColor.red = red.toVariant().toInt();
newColor.green = green.toVariant().toInt();
newColor.blue = blue.toVariant().toInt();
if (_defaultSettings || (newColor.red != _color.red ||
newColor.green != _color.green ||
newColor.blue != _color.blue)) {
_color = newColor;
_colorChanged = true;
}
}
}
QScriptValue radius = object.property("radius");
if (radius.isValid()) {
float newRadius;
newRadius = radius.toVariant().toFloat();
if (_defaultSettings || newRadius != _radius) {
_radius = newRadius;
_radiusChanged = true;
}
}
QScriptValue shouldDie = object.property("shouldDie");
if (shouldDie.isValid()) {
bool newShouldDie;
newShouldDie = shouldDie.toVariant().toBool();
if (_defaultSettings || newShouldDie != _shouldDie) {
_shouldDie = newShouldDie;
_shouldDieChanged = true;
}
}
QScriptValue modelURL = object.property("modelURL");
if (modelURL.isValid()) {
QString newModelURL;
newModelURL = modelURL.toVariant().toString();
if (_defaultSettings || newModelURL != _modelURL) {
_modelURL = newModelURL;
_modelURLChanged = true;
}
}
QScriptValue modelRotation = object.property("modelRotation");
if (modelRotation.isValid()) {
QScriptValue x = modelRotation.property("x");
QScriptValue y = modelRotation.property("y");
QScriptValue z = modelRotation.property("z");
QScriptValue w = modelRotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newModelRotation;
newModelRotation.x = x.toVariant().toFloat();
newModelRotation.y = y.toVariant().toFloat();
newModelRotation.z = z.toVariant().toFloat();
newModelRotation.w = w.toVariant().toFloat();
if (_defaultSettings || newModelRotation != _modelRotation) {
_modelRotation = newModelRotation;
_modelRotationChanged = true;
}
}
}
QScriptValue animationURL = object.property("animationURL");
if (animationURL.isValid()) {
QString newAnimationURL;
newAnimationURL = animationURL.toVariant().toString();
if (_defaultSettings || newAnimationURL != _animationURL) {
_animationURL = newAnimationURL;
_animationURLChanged = true;
}
}
QScriptValue animationIsPlaying = object.property("animationIsPlaying");
if (animationIsPlaying.isValid()) {
bool newIsAnimationPlaying;
newIsAnimationPlaying = animationIsPlaying.toVariant().toBool();
if (_defaultSettings || newIsAnimationPlaying != _animationIsPlaying) {
_animationIsPlaying = newIsAnimationPlaying;
_animationIsPlayingChanged = true;
}
}
QScriptValue animationFrameIndex = object.property("animationFrameIndex");
if (animationFrameIndex.isValid()) {
float newFrameIndex;
newFrameIndex = animationFrameIndex.toVariant().toFloat();
if (_defaultSettings || newFrameIndex != _animationFrameIndex) {
_animationFrameIndex = newFrameIndex;
_animationFrameIndexChanged = true;
}
}
QScriptValue animationFPS = object.property("animationFPS");
if (animationFPS.isValid()) {
float newFPS;
newFPS = animationFPS.toVariant().toFloat();
if (_defaultSettings || newFPS != _animationFPS) {
_animationFPS = newFPS;
_animationFPSChanged = true;
}
}
QScriptValue glowLevel = object.property("glowLevel");
if (glowLevel.isValid()) {
float newGlowLevel;
newGlowLevel = glowLevel.toVariant().toFloat();
if (_defaultSettings || newGlowLevel != _glowLevel) {
_glowLevel = newGlowLevel;
_glowLevelChanged = true;
}
}
_lastEdited = usecTimestampNow();
}
void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const {
bool somethingChanged = false;
if (_positionChanged) {
modelItem.setPosition(_position / (float) TREE_SCALE);
somethingChanged = true;
}
if (_colorChanged) {
modelItem.setColor(_color);
somethingChanged = true;
}
if (_radiusChanged) {
modelItem.setRadius(_radius / (float) TREE_SCALE);
somethingChanged = true;
}
if (_shouldDieChanged) {
modelItem.setShouldDie(_shouldDie);
somethingChanged = true;
}
if (_modelURLChanged) {
modelItem.setModelURL(_modelURL);
somethingChanged = true;
}
if (_modelRotationChanged) {
modelItem.setModelRotation(_modelRotation);
somethingChanged = true;
}
if (_animationURLChanged) {
modelItem.setAnimationURL(_animationURL);
somethingChanged = true;
}
if (_animationIsPlayingChanged) {
modelItem.setAnimationIsPlaying(_animationIsPlaying);
somethingChanged = true;
}
if (_animationFrameIndexChanged) {
modelItem.setAnimationFrameIndex(_animationFrameIndex);
somethingChanged = true;
}
if (_animationFPSChanged) {
modelItem.setAnimationFPS(_animationFPS);
somethingChanged = true;
}
if (_glowLevelChanged) {
modelItem.setGlowLevel(_glowLevel);
somethingChanged = true;
}
if (somethingChanged) {
bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - _lastEdited;
qDebug() << "ModelItemProperties::copyToModelItem() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " _lastEdited=" << _lastEdited;
}
modelItem.setLastEdited(_lastEdited);
}
}
void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
_position = modelItem.getPosition() * (float) TREE_SCALE;
_color = modelItem.getXColor();
_radius = modelItem.getRadius() * (float) TREE_SCALE;
_shouldDie = modelItem.getShouldDie();
_modelURL = modelItem.getModelURL();
_modelRotation = modelItem.getModelRotation();
_animationURL = modelItem.getAnimationURL();
_animationIsPlaying = modelItem.getAnimationIsPlaying();
_animationFrameIndex = modelItem.getAnimationFrameIndex();
_animationFPS = modelItem.getAnimationFPS();
_glowLevel = modelItem.getGlowLevel();
_id = modelItem.getID();
_idSet = true;
_positionChanged = false;
_colorChanged = false;
_radiusChanged = false;
_shouldDieChanged = false;
_modelURLChanged = false;
_modelRotationChanged = false;
_animationURLChanged = false;
_animationIsPlayingChanged = false;
_animationFrameIndexChanged = false;
_animationFPSChanged = false;
_glowLevelChanged = false;
_defaultSettings = false;
}
QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties) {
return properties.copyToScriptValue(engine);
}
void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties) {
properties.copyFromScriptValue(object);
}
QScriptValue ModelItemIDtoScriptValue(QScriptEngine* engine, const ModelItemID& id) {
QScriptValue obj = engine->newObject();
obj.setProperty("id", id.id);
obj.setProperty("creatorTokenID", id.creatorTokenID);
obj.setProperty("isKnownID", id.isKnownID);
return obj;
}
void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& id) {
id.id = object.property("id").toVariant().toUInt();
id.creatorTokenID = object.property("creatorTokenID").toVariant().toUInt();
id.isKnownID = object.property("isKnownID").toVariant().toBool();
}