3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-27 13:55:26 +02:00

Merge pull request from ZappoMan/virtualEntities

Model Server to Entity Server Refactoring
This commit is contained in:
AndrewMeadows 2014-09-03 15:48:58 -07:00
commit bd55da4a83
158 changed files with 11370 additions and 5665 deletions
assignment-client
domain-server/src
examples
interface
libraries

View file

@ -6,7 +6,7 @@ include_glm()
# link in the shared libraries
link_hifi_libraries(
audio avatars octree voxels fbx particles models metavoxels
audio avatars octree voxels fbx particles entities metavoxels
networking animation shared script-engine embedded-webserver
)
@ -14,4 +14,4 @@ if (UNIX)
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK ${CMAKE_DL_LIBS})
endif (UNIX)
link_shared_dependencies()
link_shared_dependencies()

View file

@ -27,7 +27,7 @@
#include <VoxelConstants.h>
#include <ParticlesScriptingInterface.h> // TODO: consider moving to scriptengine.h
#include <ModelsScriptingInterface.h> // TODO: consider moving to scriptengine.h
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
#include "avatars/ScriptableAvatar.h"
@ -39,7 +39,7 @@ Agent::Agent(const QByteArray& packet) :
ThreadedAssignment(packet),
_voxelEditSender(),
_particleEditSender(),
_modelEditSender(),
_entityEditSender(),
_receivedAudioStream(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES,
InboundAudioStream::Settings(0, false, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, false,
DEFAULT_WINDOW_STARVE_THRESHOLD, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES,
@ -51,7 +51,7 @@ Agent::Agent(const QByteArray& packet) :
_scriptEngine.getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
_scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
_scriptEngine.getModelsScriptingInterface()->setPacketSender(&_modelEditSender);
_scriptEngine.getEntityScriptingInterface()->setPacketSender(&_entityEditSender);
}
void Agent::readPendingDatagrams() {
@ -79,8 +79,8 @@ void Agent::readPendingDatagrams() {
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->
queueReceivedPacket(matchedNode, receivedPacket);
break;
case NodeType::ModelServer:
_scriptEngine.getModelsScriptingInterface()->getJurisdictionListener()->
case NodeType::EntityServer:
_scriptEngine.getEntityScriptingInterface()->getJurisdictionListener()->
queueReceivedPacket(matchedNode, receivedPacket);
break;
}
@ -97,12 +97,12 @@ void Agent::readPendingDatagrams() {
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
} else if (datagramPacketType == PacketTypeModelAddResponse) {
} else if (datagramPacketType == PacketTypeEntityAddResponse) {
// this will keep creatorTokenIDs to IDs mapped correctly
ModelItem::handleAddModelResponse(receivedPacket);
EntityItemID::handleAddEntityResponse(receivedPacket);
// also give our local particle tree a chance to remap any internal locally created particles
_modelViewer.getTree()->handleAddModelResponse(receivedPacket);
_entityViewer.getTree()->handleAddEntityResponse(receivedPacket);
// Make sure our Node and NodeList knows we've heard from this node.
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
@ -112,8 +112,8 @@ void Agent::readPendingDatagrams() {
|| datagramPacketType == PacketTypeParticleErase
|| datagramPacketType == PacketTypeOctreeStats
|| datagramPacketType == PacketTypeVoxelData
|| datagramPacketType == PacketTypeModelData
|| datagramPacketType == PacketTypeModelErase
|| datagramPacketType == PacketTypeEntityData
|| datagramPacketType == PacketTypeEntityErase
) {
// Make sure our Node and NodeList knows we've heard from this node.
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
@ -145,8 +145,8 @@ void Agent::readPendingDatagrams() {
_particleViewer.processDatagram(mutablePacket, sourceNode);
}
if (datagramPacketType == PacketTypeModelData || datagramPacketType == PacketTypeModelErase) {
_modelViewer.processDatagram(mutablePacket, sourceNode);
if (datagramPacketType == PacketTypeEntityData || datagramPacketType == PacketTypeEntityErase) {
_entityViewer.processDatagram(mutablePacket, sourceNode);
}
if (datagramPacketType == PacketTypeVoxelData) {
@ -192,7 +192,7 @@ void Agent::run() {
<< NodeType::AvatarMixer
<< NodeType::VoxelServer
<< NodeType::ParticleServer
<< NodeType::ModelServer
<< NodeType::EntityServer
);
// figure out the URL for the script for this agent assignment
@ -244,22 +244,19 @@ void Agent::run() {
_scriptEngine.registerGlobalObject("VoxelViewer", &_voxelViewer);
// connect the VoxelViewer and the VoxelScriptingInterface to each other
JurisdictionListener* voxelJL = _scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener();
_voxelViewer.setJurisdictionListener(voxelJL);
_voxelViewer.setJurisdictionListener(_scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener());
_voxelViewer.init();
_scriptEngine.getVoxelsScriptingInterface()->setVoxelTree(_voxelViewer.getTree());
_scriptEngine.registerGlobalObject("ParticleViewer", &_particleViewer);
JurisdictionListener* particleJL = _scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener();
_particleViewer.setJurisdictionListener(particleJL);
_particleViewer.setJurisdictionListener(_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener());
_particleViewer.init();
_scriptEngine.getParticlesScriptingInterface()->setParticleTree(_particleViewer.getTree());
_scriptEngine.registerGlobalObject("ModelViewer", &_modelViewer);
JurisdictionListener* modelJL = _scriptEngine.getModelsScriptingInterface()->getJurisdictionListener();
_modelViewer.setJurisdictionListener(modelJL);
_modelViewer.init();
_scriptEngine.getModelsScriptingInterface()->setModelTree(_modelViewer.getTree());
_scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer);
_entityViewer.setJurisdictionListener(_scriptEngine.getEntityScriptingInterface()->getJurisdictionListener());
_entityViewer.init();
_scriptEngine.getEntityScriptingInterface()->setEntityTree(_entityViewer.getTree());
_scriptEngine.setScriptContents(scriptContents);
_scriptEngine.run();

View file

@ -19,9 +19,9 @@
#include <QtCore/QUrl>
#include <AvatarHashMap.h>
#include <ModelEditPacketSender.h>
#include <ModelTree.h>
#include <ModelTreeHeadlessViewer.h>
#include <EntityEditPacketSender.h>
#include <EntityTree.h>
#include <EntityTreeHeadlessViewer.h>
#include <ParticleEditPacketSender.h>
#include <ParticleTree.h>
#include <ParticleTreeHeadlessViewer.h>
@ -65,11 +65,11 @@ private:
ScriptEngine _scriptEngine;
VoxelEditPacketSender _voxelEditSender;
ParticleEditPacketSender _particleEditSender;
ModelEditPacketSender _modelEditSender;
EntityEditPacketSender _entityEditSender;
ParticleTreeHeadlessViewer _particleViewer;
VoxelTreeHeadlessViewer _voxelViewer;
ModelTreeHeadlessViewer _modelViewer;
EntityTreeHeadlessViewer _entityViewer;
MixedAudioStream _receivedAudioStream;
float _lastReceivedAudioLoudness;

View file

@ -16,7 +16,7 @@
#include "audio/AudioMixer.h"
#include "avatars/AvatarMixer.h"
#include "metavoxels/MetavoxelServer.h"
#include "models/ModelServer.h"
#include "entities/EntityServer.h"
#include "particles/ParticleServer.h"
#include "voxels/VoxelServer.h"
@ -42,8 +42,8 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet
return new ParticleServer(packet);
case Assignment::MetavoxelServerType:
return new MetavoxelServer(packet);
case Assignment::ModelServerType:
return new ModelServer(packet);
case Assignment::EntityServerType:
return new EntityServer(packet);
default:
return NULL;
}

View file

@ -0,0 +1,34 @@
//
// EntityNodeData.h
// assignment-client/src/entities
//
// Created by Brad Hefta-Gaub on 4/29/14
// Copyright 2014 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
//
#ifndef hifi_EntityNodeData_h
#define hifi_EntityNodeData_h
#include <PacketHeaders.h>
#include "../octree/OctreeQueryNode.h"
class EntityNodeData : public OctreeQueryNode {
public:
EntityNodeData() :
OctreeQueryNode(),
_lastDeletedEntitiesSentAt(0) { }
virtual PacketType getMyPacketType() const { return PacketTypeEntityData; }
quint64 getLastDeletedEntitiesSentAt() const { return _lastDeletedEntitiesSentAt; }
void setLastDeletedEntitiesSentAt(quint64 sentAt) { _lastDeletedEntitiesSentAt = sentAt; }
private:
quint64 _lastDeletedEntitiesSentAt;
};
#endif // hifi_EntityNodeData_h

View file

@ -0,0 +1,138 @@
//
// EntityServer.cpp
// assignment-client/src/entities
//
// Created by Brad Hefta-Gaub on 4/29/14
// Copyright 2014 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 <QTimer>
#include <EntityTree.h>
#include "EntityServer.h"
#include "EntityServerConsts.h"
#include "EntityNodeData.h"
const char* MODEL_SERVER_NAME = "Entity";
const char* MODEL_SERVER_LOGGING_TARGET_NAME = "entity-server";
const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
EntityServer::EntityServer(const QByteArray& packet) : OctreeServer(packet) {
// nothing special to do here...
}
EntityServer::~EntityServer() {
EntityTree* tree = (EntityTree*)_tree;
tree->removeNewlyCreatedHook(this);
}
OctreeQueryNode* EntityServer::createOctreeQueryNode() {
return new EntityNodeData();
}
Octree* EntityServer::createTree() {
EntityTree* tree = new EntityTree(true);
tree->addNewlyCreatedHook(this);
return tree;
}
void EntityServer::beforeRun() {
QTimer* pruneDeletedEntitiesTimer = new QTimer(this);
connect(pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
}
void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
unsigned char* copyAt = outputBuffer;
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(outputBuffer), PacketTypeEntityAddResponse);
int packetLength = numBytesPacketHeader;
copyAt += numBytesPacketHeader;
// encode the creatorTokenID
uint32_t creatorTokenID = newEntity.getCreatorTokenID();
memcpy(copyAt, &creatorTokenID, sizeof(creatorTokenID));
copyAt += sizeof(creatorTokenID);
packetLength += sizeof(creatorTokenID);
// encode the entity ID
QUuid entityID = newEntity.getID();
QByteArray encodedID = entityID.toRfc4122();
memcpy(copyAt, encodedID.constData(), encodedID.size());
copyAt += sizeof(entityID);
packetLength += sizeof(entityID);
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, senderNode);
}
// EntityServer will use the "special packets" to send list of recently deleted entities
bool EntityServer::hasSpecialPacketToSend(const SharedNodePointer& node) {
bool shouldSendDeletedEntities = false;
// check to see if any new entities have been added since we last sent to this node...
EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
if (nodeData) {
quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt();
EntityTree* tree = static_cast<EntityTree*>(_tree);
shouldSendDeletedEntities = tree->hasEntitiesDeletedSince(deletedEntitiesSentAt);
}
return shouldSendDeletedEntities;
}
int EntityServer::sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
size_t packetLength = 0;
EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
if (nodeData) {
quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt();
quint64 deletePacketSentAt = usecTimestampNow();
EntityTree* tree = static_cast<EntityTree*>(_tree);
bool hasMoreToSend = true;
// TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 entities?
packetsSent = 0;
while (hasMoreToSend) {
hasMoreToSend = tree->encodeEntitiesDeletedSince(queryNode->getSequenceNumber(), deletedEntitiesSentAt,
outputBuffer, MAX_PACKET_SIZE, packetLength);
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node));
queryNode->packetSent(outputBuffer, packetLength);
packetsSent++;
}
nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt);
}
// TODO: caller is expecting a packetLength, what if we send more than one packet??
return packetLength;
}
void EntityServer::pruneDeletedEntities() {
EntityTree* tree = static_cast<EntityTree*>(_tree);
if (tree->hasAnyDeletedEntities()) {
quint64 earliestLastDeletedEntitiesSent = usecTimestampNow() + 1; // in the future
foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) {
if (otherNode->getLinkedData()) {
EntityNodeData* nodeData = static_cast<EntityNodeData*>(otherNode->getLinkedData());
quint64 nodeLastDeletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt();
if (nodeLastDeletedEntitiesSentAt < earliestLastDeletedEntitiesSent) {
earliestLastDeletedEntitiesSent = nodeLastDeletedEntitiesSentAt;
}
}
}
tree->forgetEntitiesDeletedBefore(earliestLastDeletedEntitiesSent);
}
}

View file

@ -1,6 +1,6 @@
//
// ModelServer.h
// assignment-client/src/models
// EntityServer.h
// assignment-client/src/entities
//
// Created by Brad Hefta-Gaub on 4/29/14
// Copyright 2014 High Fidelity, Inc.
@ -9,43 +9,43 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelServer_h
#define hifi_ModelServer_h
#ifndef hifi_EntityServer_h
#define hifi_EntityServer_h
#include "../octree/OctreeServer.h"
#include "ModelItem.h"
#include "ModelServerConsts.h"
#include "ModelTree.h"
#include "EntityItem.h"
#include "EntityServerConsts.h"
#include "EntityTree.h"
/// Handles assignments of type ModelServer - sending models to various clients.
class ModelServer : public OctreeServer, public NewlyCreatedModelHook {
/// Handles assignments of type EntityServer - sending entities to various clients.
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
Q_OBJECT
public:
ModelServer(const QByteArray& packet);
~ModelServer();
EntityServer(const QByteArray& packet);
~EntityServer();
// Subclasses must implement these methods
virtual OctreeQueryNode* createOctreeQueryNode();
virtual Octree* createTree();
virtual char getMyNodeType() const { return NodeType::ModelServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
virtual char getMyNodeType() const { return NodeType::EntityServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; }
virtual const char* getMyServerName() const { return MODEL_SERVER_NAME; }
virtual const char* getMyLoggingServerTargetName() const { return MODEL_SERVER_LOGGING_TARGET_NAME; }
virtual const char* getMyDefaultPersistFilename() const { return LOCAL_MODELS_PERSIST_FILE; }
virtual PacketType getMyEditNackType() const { return PacketTypeModelEditNack; }
virtual PacketType getMyEditNackType() const { return PacketTypeEntityEditNack; }
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node);
virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent);
virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode);
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode);
public slots:
void pruneDeletedModels();
void pruneDeletedEntities();
private:
};
#endif // hifi_ModelServer_h
#endif // hifi_EntityServer_h

View file

@ -1,5 +1,5 @@
//
// ModelServerConsts.h
// EntityServerConsts.h
// assignment-client/src/models
//
// Created by Brad Hefta-Gaub on 4/29/14
@ -9,11 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelServerConsts_h
#define hifi_ModelServerConsts_h
#ifndef hifi_EntityServerConsts_h
#define hifi_EntityServerConsts_h
extern const char* MODEL_SERVER_NAME;
extern const char* MODEL_SERVER_LOGGING_TARGET_NAME;
extern const char* LOCAL_MODELS_PERSIST_FILE;
#endif // hifi_ModelServerConsts_h
#endif // hifi_EntityServerConsts_h

View file

@ -1,34 +0,0 @@
//
// ModelNodeData.h
// assignment-client/src/models
//
// Created by Brad Hefta-Gaub on 4/29/14
// Copyright 2014 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
//
#ifndef hifi_ModelNodeData_h
#define hifi_ModelNodeData_h
#include <PacketHeaders.h>
#include "../octree/OctreeQueryNode.h"
class ModelNodeData : public OctreeQueryNode {
public:
ModelNodeData() :
OctreeQueryNode(),
_lastDeletedModelsSentAt(0) { };
virtual PacketType getMyPacketType() const { return PacketTypeModelData; }
quint64 getLastDeletedModelsSentAt() const { return _lastDeletedModelsSentAt; }
void setLastDeletedModelsSentAt(quint64 sentAt) { _lastDeletedModelsSentAt = sentAt; }
private:
quint64 _lastDeletedModelsSentAt;
};
#endif // hifi_ModelNodeData_h

View file

@ -1,140 +0,0 @@
//
// ModelServer.cpp
// assignment-client/src/models
//
// Created by Brad Hefta-Gaub on 4/29/14
// Copyright 2014 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 <QTimer>
#include <ModelTree.h>
#include "ModelServer.h"
#include "ModelServerConsts.h"
#include "ModelNodeData.h"
const char* MODEL_SERVER_NAME = "Model";
const char* MODEL_SERVER_LOGGING_TARGET_NAME = "model-server";
const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
ModelServer::ModelServer(const QByteArray& packet) : OctreeServer(packet) {
// nothing special to do here...
}
ModelServer::~ModelServer() {
ModelTree* tree = (ModelTree*)_tree;
tree->removeNewlyCreatedHook(this);
}
OctreeQueryNode* ModelServer::createOctreeQueryNode() {
return new ModelNodeData();
}
Octree* ModelServer::createTree() {
ModelTree* tree = new ModelTree(true);
tree->addNewlyCreatedHook(this);
return tree;
}
void ModelServer::beforeRun() {
QTimer* pruneDeletedModelsTimer = new QTimer(this);
connect(pruneDeletedModelsTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedModels()));
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
pruneDeletedModelsTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
}
void ModelServer::modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
unsigned char* copyAt = outputBuffer;
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(outputBuffer), PacketTypeModelAddResponse);
int packetLength = numBytesPacketHeader;
copyAt += numBytesPacketHeader;
// encode the creatorTokenID
uint32_t creatorTokenID = newModel.getCreatorTokenID();
memcpy(copyAt, &creatorTokenID, sizeof(creatorTokenID));
copyAt += sizeof(creatorTokenID);
packetLength += sizeof(creatorTokenID);
// encode the model ID
uint32_t modelID = newModel.getID();
memcpy(copyAt, &modelID, sizeof(modelID));
copyAt += sizeof(modelID);
packetLength += sizeof(modelID);
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, senderNode);
}
// ModelServer will use the "special packets" to send list of recently deleted models
bool ModelServer::hasSpecialPacketToSend(const SharedNodePointer& node) {
bool shouldSendDeletedModels = false;
// check to see if any new models have been added since we last sent to this node...
ModelNodeData* nodeData = static_cast<ModelNodeData*>(node->getLinkedData());
if (nodeData) {
quint64 deletedModelsSentAt = nodeData->getLastDeletedModelsSentAt();
ModelTree* tree = static_cast<ModelTree*>(_tree);
shouldSendDeletedModels = tree->hasModelsDeletedSince(deletedModelsSentAt);
}
return shouldSendDeletedModels;
}
int ModelServer::sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
size_t packetLength = 0;
ModelNodeData* nodeData = static_cast<ModelNodeData*>(node->getLinkedData());
if (nodeData) {
quint64 deletedModelsSentAt = nodeData->getLastDeletedModelsSentAt();
quint64 deletePacketSentAt = usecTimestampNow();
ModelTree* tree = static_cast<ModelTree*>(_tree);
bool hasMoreToSend = true;
// TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 models?
packetsSent = 0;
while (hasMoreToSend) {
hasMoreToSend = tree->encodeModelsDeletedSince(queryNode->getSequenceNumber(), deletedModelsSentAt,
outputBuffer, MAX_PACKET_SIZE, packetLength);
//qDebug() << "sending PacketType_MODEL_ERASE packetLength:" << packetLength;
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node));
queryNode->packetSent(outputBuffer, packetLength);
packetsSent++;
}
nodeData->setLastDeletedModelsSentAt(deletePacketSentAt);
}
// TODO: caller is expecting a packetLength, what if we send more than one packet??
return packetLength;
}
void ModelServer::pruneDeletedModels() {
ModelTree* tree = static_cast<ModelTree*>(_tree);
if (tree->hasAnyDeletedModels()) {
//qDebug() << "there are some deleted models to consider...";
quint64 earliestLastDeletedModelsSent = usecTimestampNow() + 1; // in the future
foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) {
if (otherNode->getLinkedData()) {
ModelNodeData* nodeData = static_cast<ModelNodeData*>(otherNode->getLinkedData());
quint64 nodeLastDeletedModelsSentAt = nodeData->getLastDeletedModelsSentAt();
if (nodeLastDeletedModelsSentAt < earliestLastDeletedModelsSent) {
earliestLastDeletedModelsSent = nodeLastDeletedModelsSentAt;
}
}
}
//qDebug() << "earliestLastDeletedModelsSent=" << earliestLastDeletedModelsSent;
tree->forgetModelsDeletedBefore(earliestLastDeletedModelsSent);
}
}

View file

@ -103,17 +103,37 @@ void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendin
quint64 processTime = 0;
quint64 lockWaitTime = 0;
if (_myServer->wantsDebugReceiving()) {
if (debugProcessPacket || _myServer->wantsDebugReceiving()) {
qDebug() << "PROCESSING THREAD: got '" << packetType << "' packet - " << _receivedPacketCount
<< " command from client receivedBytes=" << packet.size()
<< " sequence=" << sequence << " transitTime=" << transitTime << " usecs";
}
if (debugProcessPacket) {
qDebug() << " numBytesPacketHeader=" << numBytesPacketHeader;
qDebug() << " sizeof(sequence)=" << sizeof(sequence);
qDebug() << " sizeof(sentAt)=" << sizeof(sentAt);
}
int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt);
if (debugProcessPacket) {
qDebug() << " atByte=" << atByte;
qDebug() << " packet.size()=" << packet.size();
if (atByte >= packet.size()) {
qDebug() << " ----- UNEXPECTED ---- got a packet without any edit details!!!! --------";
}
}
unsigned char* editData = (unsigned char*)&packetData[atByte];
while (atByte < packet.size()) {
int maxSize = packet.size() - atByte;
if (debugProcessPacket) {
qDebug() << " --- inside while loop ---";
qDebug() << " maxSize=" << maxSize;
qDebug("OctreeInboundPacketProcessor::processPacket() %c "
"packetData=%p packetLength=%d voxelData=%p atByte=%d maxSize=%d",
packetType, packetData, packet.size(), editData, atByte, maxSize);
@ -126,6 +146,12 @@ void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendin
reinterpret_cast<const unsigned char*>(packet.data()),
packet.size(),
editData, maxSize, sendingNode);
if (debugProcessPacket) {
qDebug() << "OctreeInboundPacketProcessor::processPacket() after processEditPacketData()..."
<< "editDataBytesRead=" << editDataBytesRead;
}
_myServer->getOctree()->unlock();
quint64 endProcess = usecTimestampNow();
@ -138,6 +164,13 @@ void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendin
// skip to next voxel edit record in the packet
editData += editDataBytesRead;
atByte += editDataBytesRead;
if (debugProcessPacket) {
qDebug() << " editDataBytesRead=" << editDataBytesRead;
qDebug() << " AFTER processEditPacketData atByte=" << atByte;
qDebug() << " AFTER processEditPacketData packet.size()=" << packet.size();
}
}
if (debugProcessPacket) {

View file

@ -58,7 +58,7 @@ OctreeQueryNode::~OctreeQueryNode() {
void OctreeQueryNode::nodeKilled() {
_isShuttingDown = true;
nodeBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications
elementBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications
if (_octreeSendThread) {
// just tell our thread we want to shutdown, this is asynchronous, and fast, we don't need or want it to block
// while the thread actually shuts down
@ -68,7 +68,7 @@ void OctreeQueryNode::nodeKilled() {
void OctreeQueryNode::forceNodeShutdown() {
_isShuttingDown = true;
nodeBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications
elementBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications
if (_octreeSendThread) {
// we really need to force our thread to shutdown, this is synchronous, we will block while the thread actually
// shuts down because we really need it to shutdown, and it's ok if we wait for it to complete
@ -345,8 +345,8 @@ void OctreeQueryNode::dumpOutOfView() {
int stillInView = 0;
int outOfView = 0;
OctreeElementBag tempBag;
while (!nodeBag.isEmpty()) {
OctreeElement* node = nodeBag.extract();
while (!elementBag.isEmpty()) {
OctreeElement* node = elementBag.extract();
if (node->isInView(_currentViewFrustum)) {
tempBag.insert(node);
stillInView++;
@ -358,7 +358,7 @@ void OctreeQueryNode::dumpOutOfView() {
while (!tempBag.isEmpty()) {
OctreeElement* node = tempBag.extract();
if (node->isInView(_currentViewFrustum)) {
nodeBag.insert(node);
elementBag.insert(node);
}
}
}

View file

@ -56,8 +56,9 @@ public:
int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; }
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
OctreeElementBag nodeBag;
OctreeElementBag elementBag;
CoverageMap map;
OctreeElementExtraEncodeData extraEncodeData;
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; }
ViewFrustum& getLastKnownViewFrustum() { return _lastKnownViewFrustum; }
@ -84,7 +85,7 @@ public:
return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression());
}
bool hasLodChanged() const { return _lodChanged; };
bool hasLodChanged() const { return _lodChanged; }
OctreeSceneStats stats;

View file

@ -119,7 +119,6 @@ quint64 OctreeSendThread::_totalWastedBytes = 0;
quint64 OctreeSendThread::_totalPackets = 0;
int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
OctreeServer::didHandlePacketSend(this);
// if we're shutting down, then exit early
@ -331,7 +330,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// If the current view frustum has changed OR we have nothing to send, then search against
// the current view frustum for things to send.
if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) {
if (viewFrustumChanged || nodeData->elementBag.isEmpty()) {
// if our view has changed, we need to reset these things...
if (viewFrustumChanged) {
@ -350,6 +349,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// track completed scenes and send out the stats packet accordingly
nodeData->stats.sceneCompleted();
nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged());
_myServer->getOctree()->releaseSceneEncodeData(&nodeData->extraEncodeData);
// TODO: add these to stats page
//::endSceneSleepTime = _usleepTime;
@ -360,9 +360,9 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
int packetsJustSent = handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
packetsSentThisInterval += packetsJustSent;
// If we're starting a full scene, then definitely we want to empty the nodeBag
// If we're starting a full scene, then definitely we want to empty the elementBag
if (isFullScene) {
nodeData->nodeBag.deleteAll();
nodeData->elementBag.deleteAll();
}
// TODO: add these to stats page
@ -374,16 +374,16 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// This is the start of "resending" the scene.
bool dontRestartSceneOnMove = false; // this is experimental
if (dontRestartSceneOnMove) {
if (nodeData->nodeBag.isEmpty()) {
nodeData->nodeBag.insert(_myServer->getOctree()->getRoot()); // only in case of empty
if (nodeData->elementBag.isEmpty()) {
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
} else {
nodeData->nodeBag.insert(_myServer->getOctree()->getRoot()); // original behavior, reset on move or empty
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
}
// If we have something in our nodeBag, then turn them into packets and send them out...
if (!nodeData->nodeBag.isEmpty()) {
// If we have something in our elementBag, then turn them into packets and send them out...
if (!nodeData->elementBag.isEmpty()) {
int bytesWritten = 0;
quint64 start = usecTimestampNow();
@ -393,6 +393,9 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
int extraPackingAttempts = 0;
bool completedScene = false;
OctreeElement* lastAttemptedSubTree = NULL;
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
float lockWaitElapsedUsec = OctreeServer::SKIP_TIME;
float encodeElapsedUsec = OctreeServer::SKIP_TIME;
@ -402,9 +405,12 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
quint64 startInside = usecTimestampNow();
bool lastNodeDidntFit = false; // assume each node fits
if (!nodeData->nodeBag.isEmpty()) {
OctreeElement* subTree = nodeData->nodeBag.extract();
if (!nodeData->elementBag.isEmpty()) {
OctreeElement* subTree = nodeData->elementBag.extract();
// TODO: look into breaking early if the same subtree keeps repeating for inclusion...
lastAttemptedSubTree = subTree;
/* TODO: Looking for a way to prevent locking and encoding a tree that is not
// going to result in any packets being sent...
//
@ -439,7 +445,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, voxelSizeScale,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, _myServer->getJurisdiction());
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
&nodeData->extraEncodeData);
// TODO: should this include the lock time or not? This stat is sent down to the client,
// it seems like it may be a good idea to include the lock time as part of the encode time
@ -452,14 +459,16 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
quint64 encodeStart = usecTimestampNow();
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->nodeBag, params);
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
quint64 encodeEnd = usecTimestampNow();
encodeElapsedUsec = (float)(encodeEnd - encodeStart);
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
// sent the entire scene. We want to know this below so we'll actually write this content into
// the packet and send it
completedScene = nodeData->nodeBag.isEmpty();
completedScene = nodeData->elementBag.isEmpty();
// if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case.
if (_packetData.getTargetSize() == MAX_OCTREE_PACKET_DATA_SIZE) {
@ -493,8 +502,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// if bytesWritten == 0 it means either the subTree couldn't fit or we had an empty bag... Both cases
// mean we should send the previous packet contents and reset it.
if (completedScene || lastNodeDidntFit) {
if (_packetData.hasContent()) {
quint64 compressAndWriteStart = usecTimestampNow();
// if for some reason the finalized size is greater than our available size, then probably the "compressed"
@ -503,11 +512,12 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
unsigned int writtenSize = _packetData.getFinalizedSize()
+ (nodeData->getCurrentPacketIsCompressed() ? sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) : 0);
if (writtenSize > nodeData->getAvailable()) {
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
}
lastAttemptedSubTree = NULL; // reset this
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
extraPackingAttempts = 0;
quint64 compressAndWriteEnd = usecTimestampNow();
@ -594,7 +604,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
// the voxels from the current view frustum
if (nodeData->nodeBag.isEmpty()) {
if (nodeData->elementBag.isEmpty()) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes

View file

@ -882,6 +882,7 @@ void OctreeServer::run() {
// Before we do anything else, create our tree...
OctreeElement::resetPopulationStatistics();
_tree = createTree();
_tree->setIsServer(true);
// use common init to setup common timers and logging
commonInit(getMyLoggingServerTargetName(), getMyNodeType());

View file

@ -71,7 +71,7 @@ public:
virtual PacketType getMyEditNackType() const = 0;
// subclass may implement these method
virtual void beforeRun() { };
virtual void beforeRun() { }
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node) { return false; }
virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { return 0; }

View file

@ -20,7 +20,7 @@ class ParticleNodeData : public OctreeQueryNode {
public:
ParticleNodeData() :
OctreeQueryNode(),
_lastDeletedParticlesSentAt(0) { };
_lastDeletedParticlesSentAt(0) { }
virtual PacketType getMyPacketType() const { return PacketTypeParticleData; }

View file

@ -18,7 +18,7 @@
class VoxelNodeData : public OctreeQueryNode {
public:
VoxelNodeData() : OctreeQueryNode() { };
VoxelNodeData() : OctreeQueryNode() { }
virtual PacketType getMyPacketType() const { return PacketTypeVoxelData; }
};

View file

@ -393,7 +393,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
const QString ALLOWED_ROLES_CONFIG_KEY = "allowed-roles";
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
<< NodeType::MetavoxelServer;
void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {

View file

@ -32,7 +32,7 @@ var originalProperties = {
blue: 0 },
modelURL: "http://www.fungibleinsight.com/faces/beta.fst",
modelRotation: rotation,
rotation: rotation,
animationURL: "http://www.fungibleinsight.com/faces/gangnam_style_2.fbx",
animationIsPlaying: true,
};

View file

@ -21,11 +21,11 @@ var roll = 180.0;
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll)
var originalProperties = {
position: { x: 10,
y: 0,
z: 0 },
position: { x: 2.0,
y: 2.0,
z: 0.5 },
radius : 0.1,
radius : 0.25,
color: { red: 0,
green: 255,
@ -38,21 +38,21 @@ var originalProperties = {
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx",
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX",
modelRotation: rotation
rotation: rotation
};
var positionDelta = { x: 0, y: 0, z: 0 };
var positionDelta = { x: 0.002, y: 0.002, z: 0.0 };
var modelID = Models.addModel(originalProperties);
var entityID = Entities.addEntity(originalProperties);
function moveModel(deltaTime) {
function moveEntity(deltaTime) {
if (count >= moveUntil) {
// delete it...
if (count == moveUntil) {
print("calling Models.deleteModel()");
Models.deleteModel(modelID);
print("calling Entities.deleteEntity()");
Entities.deleteEntity(entityID);
}
// stop it...
@ -68,7 +68,7 @@ function moveModel(deltaTime) {
//print("count =" + count);
count++;
//print("modelID.creatorTokenID = " + modelID.creatorTokenID);
//print("entityID.creatorTokenID = " + entityID.creatorTokenID);
var newProperties = {
position: {
@ -76,18 +76,16 @@ function moveModel(deltaTime) {
y: originalProperties.position.y + (count * positionDelta.y),
z: originalProperties.position.z + (count * positionDelta.z)
},
radius : 0.25,
};
//print("modelID = " + modelID);
//print("entityID = " + entityID);
//print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z);
Models.editModel(modelID, newProperties);
Entities.editEntity(entityID, newProperties);
}
// register the call back so it fires before each data send
Script.update.connect(moveModel);
Script.update.connect(moveEntity);

View file

@ -1,5 +1,5 @@
//
// editModels.js
// editEntities.js
// examples
//
// Created by Clément Brisset on 4/24/14.
@ -950,16 +950,16 @@ var modelUploader = (function () {
error("Model upload failed: Internet request timed out!");
}
//function debugResponse() {
// print("req.errorCode = " + req.errorCode);
// print("req.readyState = " + req.readyState);
// print("req.status = " + req.status);
// print("req.statusText = " + req.statusText);
// print("req.responseType = " + req.responseType);
// print("req.responseText = " + req.responseText);
// print("req.response = " + req.response);
// print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders());
//}
function debugResponse() {
print("req.errorCode = " + req.errorCode);
print("req.readyState = " + req.readyState);
print("req.status = " + req.status);
print("req.statusText = " + req.statusText);
print("req.responseType = " + req.responseType);
print("req.responseText = " + req.responseText);
print("req.response = " + req.response);
print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders());
}
function checkUploaded() {
if (!isProcessing) { return; }
@ -1113,6 +1113,8 @@ var toolBar = (function () {
toolBar,
activeButton,
newModelButton,
newCubeButton,
newSphereButton,
browseModelsButton,
loadURLMenuItem,
loadFileMenuItem,
@ -1178,6 +1180,25 @@ var toolBar = (function () {
alpha: 0.9,
visible: false
});
newCubeButton = toolBar.addTool({
imageURL: toolIconUrl + "add-cube.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true
});
newSphereButton = toolBar.addTool({
imageURL: toolIconUrl + "add-sphere.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true
});
}
function toggleNewModelButton(active) {
@ -1196,7 +1217,8 @@ var toolBar = (function () {
position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
if (position.x > 0 && position.y > 0 && position.z > 0) {
Models.addModel({
Entities.addEntity({
type: "Model",
position: position,
radius: DEFAULT_RADIUS,
modelURL: url
@ -1260,8 +1282,10 @@ var toolBar = (function () {
if (clickedOverlay === loadFileMenuItem) {
toggleNewModelButton(false);
// TODO BUG: this is bug, if the user has never uploaded a model, this will throw an JS exception
file = Window.browse("Select your model file ...",
Settings.getValue("LastModelUploadLocation").path(),
Settings.getValue("LastModelUploadLocation").path(),
"Model files (*.fst *.fbx)");
//"Model files (*.fst *.fbx *.svo)");
if (file !== null) {
@ -1280,6 +1304,39 @@ var toolBar = (function () {
return true;
}
if (newCubeButton === toolBar.clicked(clickedOverlay)) {
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
if (position.x > 0 && position.y > 0 && position.z > 0) {
Entities.addEntity({
type: "Box",
position: position,
radius: DEFAULT_RADIUS,
color: { red: 255, green: 0, blue: 0 }
});
} else {
print("Can't create box: Box would be out of bounds.");
}
return true;
}
if (newSphereButton === toolBar.clicked(clickedOverlay)) {
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
if (position.x > 0 && position.y > 0 && position.z > 0) {
Entities.addEntity({
type: "Sphere",
position: position,
radius: DEFAULT_RADIUS,
color: { red: 255, green: 0, blue: 0 }
});
} else {
print("Can't create box: Box would be out of bounds.");
}
return true;
}
return false;
};
@ -1442,7 +1499,7 @@ var ExportMenu = function (opts) {
self.setScale(self._scale *= 2);
}
this.exportModels = function () {
this.exportEntities = function() {
var x = self._position.x;
var y = self._position.y;
var z = self._position.z;
@ -1450,7 +1507,7 @@ var ExportMenu = function (opts) {
var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo";
filename = Window.save("Select where to save", filename, "*.svo")
if (filename) {
var success = Clipboard.exportModels(filename, x, y, z, s);
var success = Clipboard.exportEntities(filename, x, y, z, s);
if (!success) {
Window.alert("Export failed: no models found in selected area.");
}
@ -1476,7 +1533,7 @@ var ExportMenu = function (opts) {
if (clickedOverlay == locationButton) {
self.showPositionPrompt();
} else if (clickedOverlay == exportButton) {
self.exportModels();
self.exportEntities();
} else if (clickedOverlay == cancelButton) {
self.close();
} else if (clickedOverlay == scaleOverlay) {
@ -1656,23 +1713,23 @@ var ModelImporter = function (opts) {
}
} else {
if (Window.confirm(("Would you like to import back to the source location?"))) {
var success = Clipboard.importModels(filename);
var success = Clipboard.importEntities(filename);
if (success) {
Clipboard.pasteModels(x, y, z, 1);
Clipboard.pasteEntities(x, y, z, 1);
} else {
Window.alert("There was an error importing the model file.");
Window.alert("There was an error importing the entity file.");
}
return;
}
}
}
var success = Clipboard.importModels(filename);
var success = Clipboard.importEntities(filename);
if (success) {
self._importing = true;
self.setImportVisible(true);
Overlays.editOverlay(importBoundaries, { size: s });
} else {
Window.alert("There was an error importing the model file.");
Window.alert("There was an error importing the entity file.");
}
}
}
@ -1682,7 +1739,7 @@ var ModelImporter = function (opts) {
if (self._importing) {
// self._importing = false;
// self.setImportVisible(false);
Clipboard.pasteModels(importPosition.x, importPosition.y, importPosition.z, 1);
Clipboard.pasteEntities(importPosition.x, importPosition.y, importPosition.z, 1);
}
}
@ -1743,7 +1800,7 @@ function controller(wichSide) {
this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously)
this.grabbing = false;
this.modelID = { isKnownID: false };
this.entityID = { isKnownID: false };
this.modelURL = "";
this.oldModelRotation;
this.oldModelPosition;
@ -1752,8 +1809,7 @@ function controller(wichSide) {
this.positionAtGrab;
this.rotationAtGrab;
this.modelPositionAtGrab;
this.modelRotationAtGrab;
this.rotationAtGrab;
this.jointsIntersectingFromStart = [];
this.laser = Overlays.addOverlay("line3d", {
@ -1786,36 +1842,34 @@ function controller(wichSide) {
anchor: "MyAvatar"
});
this.topDown = Overlays.addOverlay("line3d", {
position: { x: 0, y: 0, z: 0 },
end: { x: 0, y: 0, z: 0 },
color: { red: 0, green: 0, blue: 255 },
alpha: 1,
visible: false,
lineWidth: LASER_WIDTH,
anchor: "MyAvatar"
});
position: { x: 0, y: 0, z: 0 },
end: { x: 0, y: 0, z: 0 },
color: { red: 0, green: 0, blue: 255 },
alpha: 1,
visible: false,
lineWidth: LASER_WIDTH,
anchor: "MyAvatar"
});
this.grab = function (modelID, properties) {
this.grab = function (entityID, properties) {
if (isLocked(properties)) {
print("Model locked " + modelID.id);
print("Model locked " + entityID.id);
} else {
print("Grabbing " + modelID.id);
print("Grabbing " + entityID.id);
this.grabbing = true;
this.modelID = modelID;
this.entityID = entityID;
this.modelURL = properties.modelURL;
this.oldModelPosition = properties.position;
this.oldModelRotation = properties.modelRotation;
this.oldModelRotation = properties.rotation;
this.oldModelRadius = properties.radius;
this.positionAtGrab = this.palmPosition;
this.rotationAtGrab = this.rotation;
this.modelPositionAtGrab = properties.position;
this.modelRotationAtGrab = properties.modelRotation;
this.rotationAtGrab = properties.rotation;
this.jointsIntersectingFromStart = [];
for (var i = 0; i < jointList.length; i++) {
var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition);
@ -1850,7 +1904,7 @@ function controller(wichSide) {
if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 ||
(leftController.grabbing && rightController.grabbing &&
leftController.modelID.id == rightController.modelID.id)) {
leftController.entityID.id == rightController.entityID.id)) {
// Do nothing
} else {
print("Attaching to " + jointList[closestJointIndex]);
@ -1864,14 +1918,13 @@ function controller(wichSide) {
MyAvatar.attach(this.modelURL, jointList[closestJointIndex],
attachmentOffset, attachmentRotation, 2.0 * this.oldModelRadius,
true, false);
Models.deleteModel(this.modelID);
Entities.deleteEntity(this.entityID);
}
}
}
this.grabbing = false;
this.modelID.isKnownID = false;
this.entityID.isKnownID = false;
this.jointsIntersectingFromStart = [];
this.showLaser(true);
}
@ -1890,7 +1943,7 @@ function controller(wichSide) {
}
}
this.checkModel = function (properties) {
this.checkEntity = function (properties) {
// special case to lock the ground plane model in hq.
if (isLocked(properties)) {
return { valid: false };
@ -1960,18 +2013,18 @@ function controller(wichSide) {
this.showLaser(!this.grabbing || mode == 0);
if (this.glowedIntersectingModel.isKnownID) {
Models.editModel(this.glowedIntersectingModel, { glowLevel: 0.0 });
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 });
this.glowedIntersectingModel.isKnownID = false;
}
if (!this.grabbing) {
var intersection = Models.findRayIntersection({
origin: this.palmPosition,
direction: this.front
});
var angularSize = 2 * Math.atan(intersection.modelProperties.radius / Vec3.distance(Camera.getPosition(), intersection.modelProperties.position)) * 180 / 3.14;
if (intersection.accurate && intersection.modelID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
this.glowedIntersectingModel = intersection.modelID;
Models.editModel(this.glowedIntersectingModel, { glowLevel: 0.25 });
var intersection = Entities.findRayIntersection({
origin: this.palmPosition,
direction: this.front
});
var angularSize = 2 * Math.atan(intersection.properties.radius / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14;
if (intersection.accurate && intersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
this.glowedIntersectingModel = intersection.entityID;
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 });
}
}
}
@ -1982,16 +2035,15 @@ function controller(wichSide) {
Overlays.editOverlay(this.leftRight, { visible: show });
Overlays.editOverlay(this.topDown, { visible: show });
}
this.moveModel = function () {
this.moveEntity = function () {
if (this.grabbing) {
if (!this.modelID.isKnownID) {
print("Unknown grabbed ID " + this.modelID.id + ", isKnown: " + this.modelID.isKnownID);
this.modelID = Models.findRayIntersection({
origin: this.palmPosition,
direction: this.front
}).modelID;
print("Identified ID " + this.modelID.id + ", isKnown: " + this.modelID.isKnownID);
if (!this.entityID.isKnownID) {
print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
this.entityID = Entities.findRayIntersection({
origin: this.palmPosition,
direction: this.front
}).entityID;
print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
}
var newPosition;
var newRotation;
@ -2034,15 +2086,13 @@ function controller(wichSide) {
newRotation = Quat.multiply(this.rotation,
Quat.inverse(this.rotationAtGrab));
newRotation = Quat.multiply(newRotation,
this.modelRotationAtGrab);
this.rotationAtGrab);
break;
}
Models.editModel(this.modelID, {
position: newPosition,
modelRotation: newRotation
});
Entities.editEntity(this.entityID, {
position: newPosition,
rotation: newRotation
});
this.oldModelRotation = newRotation;
this.oldModelPosition = newPosition;
@ -2137,46 +2187,46 @@ function controller(wichSide) {
MyAvatar.detachOne(attachments[attachmentIndex].modelURL, attachments[attachmentIndex].jointName);
newProperties = {
type: "Model",
position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName),
Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)),
modelRotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName),
rotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName),
attachments[attachmentIndex].rotation),
radius: attachments[attachmentIndex].scale / 2.0,
modelURL: attachments[attachmentIndex].modelURL
};
newModel = Models.addModel(newProperties);
newModel = Entities.addEntity(newProperties);
} else {
// There is none so ...
// Checking model tree
Vec3.print("Looking at: ", this.palmPosition);
var pickRay = { origin: this.palmPosition,
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) };
var foundIntersection = Models.findRayIntersection(pickRay);
if (!foundIntersection.accurate) {
var foundIntersection = Entities.findRayIntersection(pickRay);
if(!foundIntersection.accurate) {
print("No accurate intersection");
return;
}
newModel = foundIntersection.modelID;
newModel = foundIntersection.entityID;
if (!newModel.isKnownID) {
var identify = Models.identifyModel(newModel);
var identify = Entities.identifyEntity(newModel);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + " (update loop " + newModel.id + ")");
return;
}
newModel = identify;
}
newProperties = Models.getModelProperties(newModel);
newProperties = Entities.getEntityProperties(newModel);
}
print("foundModel.modelURL=" + newProperties.modelURL);
print("foundEntity.modelURL=" + newProperties.modelURL);
if (isLocked(newProperties)) {
print("Model locked " + newProperties.id);
} else {
var check = this.checkModel(newProperties);
var check = this.checkEntity(newProperties);
if (!check.valid) {
return;
}
@ -2202,8 +2252,8 @@ function controller(wichSide) {
var leftController = new controller(LEFT);
var rightController = new controller(RIGHT);
function moveModels() {
if (leftController.grabbing && rightController.grabbing && rightController.modelID.id == leftController.modelID.id) {
function moveEntities() {
if (leftController.grabbing && rightController.grabbing && rightController.entityID.id == leftController.entityID.id) {
var newPosition = leftController.oldModelPosition;
var rotation = leftController.oldModelRotation;
var ratio = 1;
@ -2250,21 +2300,18 @@ function moveModels() {
leftController.positionAtGrab = leftController.palmPosition;
leftController.rotationAtGrab = leftController.rotation;
leftController.modelPositionAtGrab = leftController.oldModelPosition;
leftController.modelRotationAtGrab = rotation;
leftController.rotationAtGrab = rotation;
rightController.positionAtGrab = rightController.palmPosition;
rightController.rotationAtGrab = rightController.rotation;
rightController.modelPositionAtGrab = rightController.oldModelPosition;
rightController.modelRotationAtGrab = rotation;
rightController.rotationAtGrab = rotation;
break;
}
Models.editModel(leftController.modelID, {
position: newPosition,
modelRotation: rotation,
radius: leftController.oldModelRadius * ratio
});
Entities.editEntity(leftController.entityID, {
position: newPosition,
rotation: rotation,
radius: leftController.oldModelRadius * ratio
});
leftController.oldModelPosition = newPosition;
leftController.oldModelRotation = rotation;
leftController.oldModelRadius *= ratio;
@ -2274,9 +2321,8 @@ function moveModels() {
rightController.oldModelRadius *= ratio;
return;
}
leftController.moveModel();
rightController.moveModel();
leftController.moveEntity();
rightController.moveEntity();
}
var hydraConnected = false;
@ -2299,7 +2345,7 @@ function checkController(deltaTime) {
leftController.update();
rightController.update();
moveModels();
moveEntities();
} else {
if (hydraConnected) {
hydraConnected = false;
@ -2308,14 +2354,13 @@ function checkController(deltaTime) {
rightController.showLaser(false);
}
}
toolBar.move();
progressDialog.move();
}
var modelSelected = false;
var selectedModelID;
var selectedModelProperties;
var entitySelected = false;
var selectedEntityID;
var selectedEntityProperties;
var mouseLastPosition;
var orientation;
var intersection;
@ -2352,9 +2397,10 @@ function Tooltip() {
this.show = function (doShow) {
Overlays.editOverlay(this.textOverlay, { visible: doShow });
}
this.updateText = function (properties) {
var angles = Quat.safeEulerAngles(properties.modelRotation);
var text = "Model Properties:\n"
this.updateText = function(properties) {
var angles = Quat.safeEulerAngles(properties.rotation);
var text = "Entity Properties:\n"
text += "type: " + properties.type + "\n"
text += "X: " + properties.position.x.toFixed(this.decimals) + "\n"
text += "Y: " + properties.position.y.toFixed(this.decimals) + "\n"
text += "Z: " + properties.position.z.toFixed(this.decimals) + "\n"
@ -2363,17 +2409,23 @@ function Tooltip() {
text += "Roll: " + angles.z.toFixed(this.decimals) + "\n"
text += "Scale: " + 2 * properties.radius.toFixed(this.decimals) + "\n"
text += "ID: " + properties.id + "\n"
text += "Model URL: " + properties.modelURL + "\n"
text += "Animation URL: " + properties.animationURL + "\n"
text += "Animation is playing: " + properties.animationIsPlaying + "\n"
if (properties.sittingPoints.length > 0) {
text += properties.sittingPoints.length + " Sitting points: "
for (var i = 0; i < properties.sittingPoints.length; ++i) {
text += properties.sittingPoints[i].name + " "
if (properties.type == "Model") {
text += "Model URL: " + properties.modelURL + "\n"
text += "Animation URL: " + properties.animationURL + "\n"
text += "Animation is playing: " + properties.animationIsPlaying + "\n"
if (properties.sittingPoints && properties.sittingPoints.length > 0) {
text += properties.sittingPoints.length + " Sitting points: "
for (var i = 0; i < properties.sittingPoints.length; ++i) {
text += properties.sittingPoints[i].name + " "
}
} else {
text += "No sitting points" + "\n"
}
} else {
text += "No sitting points"
}
if (properties.lifetime > -1) {
text += "Lifetime: " + properties.lifetime + "\n"
}
text += "Age: " + properties.ageAsText + "\n"
Overlays.editOverlay(this.textOverlay, { text: text });
@ -2391,7 +2443,7 @@ function mousePressEvent(event) {
}
mouseLastPosition = { x: event.x, y: event.y };
modelSelected = false;
entitySelected = false;
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) {
@ -2405,23 +2457,23 @@ function mousePressEvent(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
Vec3.print("[Mouse] Looking at: ", pickRay.origin);
var foundIntersection = Models.findRayIntersection(pickRay);
var foundIntersection = Entities.findRayIntersection(pickRay);
if (!foundIntersection.accurate) {
if(!foundIntersection.accurate) {
return;
}
var foundModel = foundIntersection.modelID;
var foundEntity = foundIntersection.entityID;
if (!foundModel.isKnownID) {
var identify = Models.identifyModel(foundModel);
if (!foundEntity.isKnownID) {
var identify = Entities.identifyEntity(foundEntity);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")");
print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")");
return;
}
foundModel = identify;
foundEntity = identify;
}
var properties = Models.getModelProperties(foundModel);
var properties = Entities.getEntityProperties(foundEntity);
if (isLocked(properties)) {
print("Model locked " + properties.id);
} else {
@ -2448,10 +2500,9 @@ function mousePressEvent(event) {
var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
if (angularSize < MAX_ANGULAR_SIZE) {
modelSelected = true;
selectedModelID = foundModel;
selectedModelProperties = properties;
entitySelected = true;
selectedEntityID = foundEntity;
selectedEntityProperties = properties;
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
} else {
@ -2460,29 +2511,28 @@ function mousePressEvent(event) {
}
}
}
if (modelSelected) {
selectedModelProperties.oldRadius = selectedModelProperties.radius;
selectedModelProperties.oldPosition = {
x: selectedModelProperties.position.x,
y: selectedModelProperties.position.y,
z: selectedModelProperties.position.z,
if (entitySelected) {
selectedEntityProperties.oldRadius = selectedEntityProperties.radius;
selectedEntityProperties.oldPosition = {
x: selectedEntityProperties.position.x,
y: selectedEntityProperties.position.y,
z: selectedEntityProperties.position.z,
};
selectedModelProperties.oldRotation = {
x: selectedModelProperties.modelRotation.x,
y: selectedModelProperties.modelRotation.y,
z: selectedModelProperties.modelRotation.z,
w: selectedModelProperties.modelRotation.w,
selectedEntityProperties.oldRotation = {
x: selectedEntityProperties.rotation.x,
y: selectedEntityProperties.rotation.y,
z: selectedEntityProperties.rotation.z,
w: selectedEntityProperties.rotation.w,
};
selectedModelProperties.glowLevel = 0.0;
print("Clicked on " + selectedModelID.id + " " + modelSelected);
tooltip.updateText(selectedModelProperties);
selectedEntityProperties.glowLevel = 0.0;
print("Clicked on " + selectedEntityID.id + " " + entitySelected);
tooltip.updateText(selectedEntityProperties);
tooltip.show(true);
}
}
var glowedModelID = { id: -1, isKnownID: false };
var glowedEntityID = { id: -1, isKnownID: false };
var oldModifier = 0;
var modifier = 0;
var wasShifted = false;
@ -2492,20 +2542,19 @@ function mouseMoveEvent(event) {
}
var pickRay = Camera.computePickRay(event.x, event.y);
if (!modelSelected) {
var modelIntersection = Models.findRayIntersection(pickRay);
if (modelIntersection.accurate) {
if (glowedModelID.isKnownID && glowedModelID.id != modelIntersection.modelID.id) {
Models.editModel(glowedModelID, { glowLevel: 0.0 });
glowedModelID.id = -1;
glowedModelID.isKnownID = false;
if (!entitySelected) {
var entityIntersection = Entities.findRayIntersection(pickRay);
if (entityIntersection.accurate) {
if(glowedEntityID.isKnownID && glowedEntityID.id != entityIntersection.entityID.id) {
Entities.editEntity(glowedEntityID, { glowLevel: 0.0 });
glowedEntityID.id = -1;
glowedEntityID.isKnownID = false;
}
var angularSize = 2 * Math.atan(modelIntersection.modelProperties.radius / Vec3.distance(Camera.getPosition(), modelIntersection.modelProperties.position)) * 180 / 3.14;
if (modelIntersection.modelID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
Models.editModel(modelIntersection.modelID, { glowLevel: 0.25 });
glowedModelID = modelIntersection.modelID;
var angularSize = 2 * Math.atan(entityIntersection.properties.radius / Vec3.distance(Camera.getPosition(), entityIntersection.properties.position)) * 180 / 3.14;
if (entityIntersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 });
glowedEntityID = entityIntersection.entityID;
}
}
return;
@ -2524,22 +2573,22 @@ function mouseMoveEvent(event) {
}
pickRay = Camera.computePickRay(event.x, event.y);
if (wasShifted != event.isShifted || modifier != oldModifier) {
selectedModelProperties.oldRadius = selectedModelProperties.radius;
selectedModelProperties.oldPosition = {
x: selectedModelProperties.position.x,
y: selectedModelProperties.position.y,
z: selectedModelProperties.position.z,
selectedEntityProperties.oldRadius = selectedEntityProperties.radius;
selectedEntityProperties.oldPosition = {
x: selectedEntityProperties.position.x,
y: selectedEntityProperties.position.y,
z: selectedEntityProperties.position.z,
};
selectedModelProperties.oldRotation = {
x: selectedModelProperties.modelRotation.x,
y: selectedModelProperties.modelRotation.y,
z: selectedModelProperties.modelRotation.z,
w: selectedModelProperties.modelRotation.w,
selectedEntityProperties.oldRotation = {
x: selectedEntityProperties.rotation.x,
y: selectedEntityProperties.rotation.y,
z: selectedEntityProperties.rotation.z,
w: selectedEntityProperties.rotation.w,
};
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay,
selectedModelProperties.oldPosition,
selectedEntityProperties.oldPosition,
Quat.getFront(orientation));
mouseLastPosition = { x: event.x, y: event.y };
@ -2554,10 +2603,9 @@ function mouseMoveEvent(event) {
return;
case 1:
// Let's Scale
selectedModelProperties.radius = (selectedModelProperties.oldRadius *
selectedEntityProperties.radius = (selectedEntityProperties.oldRadius *
(1.0 + (mouseLastPosition.y - event.y) / SCALE_FACTOR));
if (selectedModelProperties.radius < 0.01) {
if (selectedEntityProperties.radius < 0.01) {
print("Scale too small ... bailling.");
return;
}
@ -2566,7 +2614,7 @@ function mouseMoveEvent(event) {
case 2:
// Let's translate
var newIntersection = rayPlaneIntersection(pickRay,
selectedModelProperties.oldPosition,
selectedEntityProperties.oldPosition,
Quat.getFront(orientation));
var vector = Vec3.subtract(newIntersection, intersection)
if (event.isShifted) {
@ -2575,16 +2623,15 @@ function mouseMoveEvent(event) {
vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i),
Vec3.multiply(Quat.getFront(orientation), j));
}
selectedModelProperties.position = Vec3.sum(selectedModelProperties.oldPosition, vector);
selectedEntityProperties.position = Vec3.sum(selectedEntityProperties.oldPosition, vector);
break;
case 3:
// Let's rotate
if (somethingChanged) {
selectedModelProperties.oldRotation.x = selectedModelProperties.modelRotation.x;
selectedModelProperties.oldRotation.y = selectedModelProperties.modelRotation.y;
selectedModelProperties.oldRotation.z = selectedModelProperties.modelRotation.z;
selectedModelProperties.oldRotation.w = selectedModelProperties.modelRotation.w;
selectedEntityProperties.oldRotation.x = selectedEntityProperties.rotation.x;
selectedEntityProperties.oldRotation.y = selectedEntityProperties.rotation.y;
selectedEntityProperties.oldRotation.z = selectedEntityProperties.rotation.z;
selectedEntityProperties.oldRotation.w = selectedEntityProperties.rotation.w;
mouseLastPosition.x = event.x;
mouseLastPosition.y = event.y;
somethingChanged = false;
@ -2598,7 +2645,7 @@ function mouseMoveEvent(event) {
var rotationAxis = (!zIsPressed && xIsPressed) ? { x: 1, y: 0, z: 0 } :
(!zIsPressed && !xIsPressed) ? { x: 0, y: 1, z: 0 } :
{ x: 0, y: 0, z: 1 };
rotationAxis = Vec3.multiplyQbyV(selectedModelProperties.modelRotation, rotationAxis);
rotationAxis = Vec3.multiplyQbyV(selectedEntityProperties.rotation, rotationAxis);
var orthogonalAxis = Vec3.cross(cameraForward, rotationAxis);
var mouseDelta = { x: event.x - mouseLastPosition
.x, y: mouseLastPosition.y - event.y, z: 0 };
@ -2611,21 +2658,21 @@ function mouseMoveEvent(event) {
}
var rotation = Quat.fromVec3Degrees({
x: (!zIsPressed && xIsPressed) ? delta : 0, // x is pressed
y: (!zIsPressed && !xIsPressed) ? delta : 0, // neither is pressed
z: (zIsPressed && !xIsPressed) ? delta : 0 // z is pressed
});
rotation = Quat.multiply(selectedModelProperties.oldRotation, rotation);
selectedModelProperties.modelRotation.x = rotation.x;
selectedModelProperties.modelRotation.y = rotation.y;
selectedModelProperties.modelRotation.z = rotation.z;
selectedModelProperties.modelRotation.w = rotation.w;
x: (!zIsPressed && xIsPressed) ? delta : 0, // x is pressed
y: (!zIsPressed && !xIsPressed) ? delta : 0, // neither is pressed
z: (zIsPressed && !xIsPressed) ? delta : 0 // z is pressed
});
rotation = Quat.multiply(selectedEntityProperties.oldRotation, rotation);
selectedEntityProperties.rotation.x = rotation.x;
selectedEntityProperties.rotation.y = rotation.y;
selectedEntityProperties.rotation.z = rotation.z;
selectedEntityProperties.rotation.w = rotation.w;
break;
}
Models.editModel(selectedModelID, selectedModelProperties);
tooltip.updateText(selectedModelProperties);
Entities.editEntity(selectedEntityID, selectedEntityProperties);
tooltip.updateText(selectedEntityProperties);
}
@ -2633,15 +2680,14 @@ function mouseReleaseEvent(event) {
if (event.isAlt || !isActive) {
return;
}
if (modelSelected) {
if (entitySelected) {
tooltip.show(false);
}
modelSelected = false;
glowedModelID.id = -1;
glowedModelID.isKnownID = false;
entitySelected = false;
glowedEntityID.id = -1;
glowedEntityID.isKnownID = false;
}
// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
@ -2711,69 +2757,118 @@ function handeMenuEvent(menuItem) {
print("menuItemEvent() in JS... menuItem=" + menuItem);
if (menuItem == "Delete") {
if (leftController.grabbing) {
print(" Delete Model.... leftController.modelID=" + leftController.modelID);
Models.deleteModel(leftController.modelID);
print(" Delete Entity.... leftController.entityID="+ leftController.entityID);
Entities.deleteEntity(leftController.entityID);
leftController.grabbing = false;
if (glowedEntityID.id == leftController.entityID.id) {
glowedEntityID = { id: -1, isKnownID: false };
}
} else if (rightController.grabbing) {
print(" Delete Model.... rightController.modelID=" + rightController.modelID);
Models.deleteModel(rightController.modelID);
print(" Delete Entity.... rightController.entityID="+ rightController.entityID);
Entities.deleteEntity(rightController.entityID);
rightController.grabbing = false;
} else if (modelSelected) {
print(" Delete Model.... selectedModelID=" + selectedModelID);
Models.deleteModel(selectedModelID);
modelSelected = false;
if (glowedEntityID.id == rightController.entityID.id) {
glowedEntityID = { id: -1, isKnownID: false };
}
} else if (entitySelected) {
print(" Delete Entity.... selectedEntityID="+ selectedEntityID);
Entities.deleteEntity(selectedEntityID);
entitySelected = false;
if (glowedEntityID.id == selectedEntityID.id) {
glowedEntityID = { id: -1, isKnownID: false };
}
} else {
print(" Delete Model.... not holding...");
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Edit Properties...") {
var editModelID = -1;
if (leftController.grabbing) {
print(" Edit Properties.... leftController.modelID=" + leftController.modelID);
editModelID = leftController.modelID;
print(" Edit Properties.... leftController.entityID="+ leftController.entityID);
editModelID = leftController.entityID;
} else if (rightController.grabbing) {
print(" Edit Properties.... rightController.modelID=" + rightController.modelID);
editModelID = rightController.modelID;
} else if (modelSelected) {
print(" Edit Properties.... selectedModelID=" + selectedModelID);
editModelID = selectedModelID;
print(" Edit Properties.... rightController.entityID="+ rightController.entityID);
editModelID = rightController.entityID;
} else if (entitySelected) {
print(" Edit Properties.... selectedEntityID="+ selectedEntityID);
editModelID = selectedEntityID;
} else {
print(" Edit Properties.... not holding...");
}
if (editModelID != -1) {
print(" Edit Properties.... about to edit properties...");
var properties = Entities.getEntityProperties(editModelID);
var array = new Array();
var decimals = 3;
array.push({ label: "Model URL:", value: selectedModelProperties.modelURL });
array.push({ label: "Animation URL:", value: selectedModelProperties.animationURL });
array.push({ label: "Animation is playing:", value: selectedModelProperties.animationIsPlaying });
array.push({ label: "X:", value: selectedModelProperties.position.x.toFixed(decimals) });
array.push({ label: "Y:", value: selectedModelProperties.position.y.toFixed(decimals) });
array.push({ label: "Z:", value: selectedModelProperties.position.z.toFixed(decimals) });
var angles = Quat.safeEulerAngles(selectedModelProperties.modelRotation);
if (properties.type == "Model") {
array.push({ label: "Model URL:", value: properties.modelURL });
array.push({ label: "Animation URL:", value: properties.animationURL });
array.push({ label: "Animation is playing:", value: properties.animationIsPlaying });
array.push({ label: "Animation FPS:", value: properties.animationFPS });
array.push({ label: "Animation Frame:", value: properties.animationFrameIndex });
}
array.push({ label: "X:", value: properties.position.x.toFixed(decimals) });
array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) });
array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) });
var angles = Quat.safeEulerAngles(properties.rotation);
array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) });
array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) });
array.push({ label: "Roll:", value: angles.z.toFixed(decimals) });
array.push({ label: "Scale:", value: 2 * selectedModelProperties.radius.toFixed(decimals) });
array.push({ button: "Cancel" });
array.push({ label: "Scale:", value: 2 * properties.radius.toFixed(decimals) });
var index = 0;
array.push({ label: "Velocity X:", value: properties.velocity.x.toFixed(decimals) });
array.push({ label: "Velocity Y:", value: properties.velocity.y.toFixed(decimals) });
array.push({ label: "Velocity Z:", value: properties.velocity.z.toFixed(decimals) });
array.push({ label: "Damping:", value: properties.damping.toFixed(decimals) });
array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) });
array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) });
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
if (properties.type == "Box") {
array.push({ label: "Red:", value: properties.color.red });
array.push({ label: "Green:", value: properties.color.green });
array.push({ label: "Blue:", value: properties.color.blue });
}
array.push({ button: "Cancel" });
if (Window.form("Edit Properties", array)) {
selectedModelProperties.modelURL = array[index++].value;
selectedModelProperties.animationURL = array[index++].value;
selectedModelProperties.animationIsPlaying = array[index++].value;
selectedModelProperties.position.x = array[index++].value;
selectedModelProperties.position.y = array[index++].value;
selectedModelProperties.position.z = array[index++].value;
var index = 0;
if (properties.type == "Model") {
properties.modelURL = array[index++].value;
properties.animationURL = array[index++].value;
properties.animationIsPlaying = array[index++].value;
properties.animationFPS = array[index++].value;
properties.animationFrameIndex = array[index++].value;
}
properties.position.x = array[index++].value;
properties.position.y = array[index++].value;
properties.position.z = array[index++].value;
angles.x = array[index++].value;
angles.y = array[index++].value;
angles.z = array[index++].value;
selectedModelProperties.modelRotation = Quat.fromVec3Degrees(angles);
selectedModelProperties.radius = array[index++].value / 2;
print(selectedModelProperties.radius);
properties.rotation = Quat.fromVec3Degrees(angles);
properties.radius = array[index++].value / 2;
Models.editModel(selectedModelID, selectedModelProperties);
properties.velocity.x = array[index++].value;
properties.velocity.y = array[index++].value;
properties.velocity.z = array[index++].value;
properties.damping = array[index++].value;
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
properties.lifetime = array[index++].value; // give ourselves that many more seconds
if (properties.type == "Box") {
properties.color.red = array[index++].value;
properties.color.green = array[index++].value;
properties.color.blue = array[index++].value;
}
Entities.editEntity(editModelID, properties);
}
modelSelected = false;
}
} else if (menuItem == "Paste Models") {
@ -2810,10 +2905,10 @@ Controller.keyPressEvent.connect(function (event) {
}
// resets model orientation when holding with mouse
if (event.text == "r" && modelSelected) {
selectedModelProperties.modelRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 });
Models.editModel(selectedModelID, selectedModelProperties);
tooltip.updateText(selectedModelProperties);
if (event.text == "r" && entitySelected) {
selectedEntityProperties.rotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 });
Entities.editEntity(selectedEntityID, selectedEntityProperties);
tooltip.updateText(selectedEntityProperties);
somethingChanged = true;
}
});

View file

@ -1,347 +0,0 @@
//
// placeModelsWithHands.js
// examples
//
// Created by Brad Hefta-Gaub on 1/20/14.
// Copyright 2014 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
//
// maybe we should make these constants...
var LEFT_PALM = 0;
var LEFT_TIP = 1;
var LEFT_BUTTON_FWD = 5;
var LEFT_BUTTON_3 = 3;
var RIGHT_PALM = 2;
var RIGHT_TIP = 3;
var RIGHT_BUTTON_FWD = 11;
var RIGHT_BUTTON_3 = 9;
var leftModelAlreadyInHand = false;
var rightModelAlreadyInHand = false;
var leftRecentlyDeleted = false;
var rightRecentlyDeleted = false;
var leftHandModel;
var rightHandModel;
//var throwSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
//var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
var targetRadius = 0.5;
var radiusDefault = 0.25;
var modelRadius = radiusDefault;
var radiusMinimum = 0.05;
var radiusMaximum = 0.5;
var modelURLs = [
"http://www.fungibleinsight.com/faces/beta.fst",
"https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/topHat.fst",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx",
];
var animationURLs = [
"http://www.fungibleinsight.com/faces/gangnam_style_2.fbx",
"",
"",
"",
"",
"",
"",
"",
"",
"",
];
var currentModelURL = 1;
var numModels = modelURLs.length;
function getNewVoxelPosition() {
var camera = Camera.getPosition();
var forwardVector = Quat.getFront(MyAvatar.orientation);
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, 2.0));
return newPosition;
}
function keyPressEvent(event) {
debugPrint("event.text=" + event.text);
if (event.text == "ESC") {
if (leftRecentlyDeleted) {
leftRecentlyDeleted = false;
leftModelAlreadyInHand = false;
}
if (rightRecentlyDeleted) {
rightRecentlyDeleted = false;
rightModelAlreadyInHand = false;
}
} else if (event.text == "m") {
var URL = Window.prompt("Model URL", "Enter URL, e.g. http://foo.com/model.fbx");
var modelPosition = getNewVoxelPosition();
var properties = { position: { x: modelPosition.x,
y: modelPosition.y,
z: modelPosition.z },
radius: modelRadius,
modelURL: URL
};
newModel = Models.addModel(properties);
} else if (event.text == "DELETE" || event.text == "BACKSPACE") {
if (leftModelAlreadyInHand) {
print("want to delete leftHandModel=" + leftHandModel);
Models.deleteModel(leftHandModel);
leftHandModel = "";
//leftModelAlreadyInHand = false;
leftRecentlyDeleted = true;
}
if (rightModelAlreadyInHand) {
print("want to delete rightHandModel=" + rightHandModel);
Models.deleteModel(rightHandModel);
rightHandModel = "";
//rightModelAlreadyInHand = false;
rightRecentlyDeleted = true;
}
} else {
var nVal = parseInt(event.text);
if ((nVal >= 0) && (nVal < numModels)) {
currentModelURL = nVal;
print("Model = " + currentModelURL);
}
}
}
var wantDebugging = false;
function debugPrint(message) {
if (wantDebugging) {
print(message);
}
}
function getModelHoldPosition(whichSide) {
var normal;
var tipPosition;
if (whichSide == LEFT_PALM) {
normal = Controller.getSpatialControlNormal(LEFT_PALM);
tipPosition = Controller.getSpatialControlPosition(LEFT_TIP);
} else {
normal = Controller.getSpatialControlNormal(RIGHT_PALM);
tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP);
}
var MODEL_FORWARD_OFFSET = 0.08; // put the model a bit forward of fingers
position = { x: MODEL_FORWARD_OFFSET * normal.x,
y: MODEL_FORWARD_OFFSET * normal.y,
z: MODEL_FORWARD_OFFSET * normal.z };
position.x += tipPosition.x;
position.y += tipPosition.y;
position.z += tipPosition.z;
return position;
}
function checkControllerSide(whichSide) {
var BUTTON_FWD;
var BUTTON_3;
var palmPosition;
var palmRotation;
var modelAlreadyInHand;
var handMessage;
if (whichSide == LEFT_PALM) {
BUTTON_FWD = LEFT_BUTTON_FWD;
BUTTON_3 = LEFT_BUTTON_3;
palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
palmRotation = Controller.getSpatialControlRawRotation(LEFT_PALM);
modelAlreadyInHand = leftModelAlreadyInHand;
handMessage = "LEFT";
} else {
BUTTON_FWD = RIGHT_BUTTON_FWD;
BUTTON_3 = RIGHT_BUTTON_3;
palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
palmRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM);
modelAlreadyInHand = rightModelAlreadyInHand;
handMessage = "RIGHT";
}
//print("checkControllerSide..." + handMessage);
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3));
// If I don't currently have a model in my hand, then try to grab closest one
if (!modelAlreadyInHand && grabButtonPressed) {
var closestModel = Models.findClosestModel(palmPosition, targetRadius);
if (closestModel.isKnownID) {
debugPrint(handMessage + " HAND- CAUGHT SOMETHING!!");
if (whichSide == LEFT_PALM) {
leftModelAlreadyInHand = true;
leftHandModel = closestModel;
} else {
rightModelAlreadyInHand = true;
rightHandModel = closestModel;
}
var modelPosition = getModelHoldPosition(whichSide);
var properties = { position: { x: modelPosition.x,
y: modelPosition.y,
z: modelPosition.z },
radius: modelRadius,
modelRotation: palmRotation,
};
debugPrint(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
Models.editModel(closestModel, properties);
/*
var options = new AudioInjectionOptions();
options.position = modelPosition;
options.volume = 1.0;
Audio.playSound(catchSound, options);
*/
return; // exit early
}
}
// If '3' is pressed, and not holding a model, make a new one
if (Controller.isButtonPressed(BUTTON_3) && !modelAlreadyInHand) {
var modelPosition = getModelHoldPosition(whichSide);
var properties = { position: { x: modelPosition.x,
y: modelPosition.y,
z: modelPosition.z },
radius: modelRadius,
modelRotation: palmRotation,
modelURL: modelURLs[currentModelURL]
};
if (animationURLs[currentModelURL] !== "") {
properties.animationURL = animationURLs[currentModelURL];
properties.animationIsPlaying = true;
}
debugPrint("modelRadius=" +modelRadius);
//newModel = Models.addModel(properties);
print("just added model... newModel=" + newModel.creatorTokenID);
print("properties.animationURL=" + properties.animationURL);
if (whichSide == LEFT_PALM) {
leftModelAlreadyInHand = true;
leftHandModel = newModel;
} else {
rightModelAlreadyInHand = true;
rightHandModel = newModel;
}
// Play a new model sound
/*
var options = new AudioInjectionOptions();
options.position = modelPosition;
options.volume = 1.0;
Audio.playSound(catchSound, options);
*/
return; // exit early
}
if (modelAlreadyInHand) {
if (whichSide == LEFT_PALM) {
handModel = leftHandModel;
whichTip = LEFT_TIP;
} else {
handModel = rightHandModel;
whichTip = RIGHT_TIP;
}
// If holding the model keep it in the palm
if (grabButtonPressed) {
debugPrint(">>>>> " + handMessage + "-MODEL IN HAND, grabbing, hold and move");
var modelPosition = getModelHoldPosition(whichSide);
var properties = { position: { x: modelPosition.x,
y: modelPosition.y,
z: modelPosition.z },
radius: modelRadius,
modelRotation: palmRotation,
};
debugPrint(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
Models.editModel(handModel, properties);
} else {
debugPrint(">>>>> " + handMessage + "-MODEL IN HAND, not grabbing, RELEASE!!!");
if (whichSide == LEFT_PALM) {
leftModelAlreadyInHand = false;
leftHandModel = false;
} else {
rightModelAlreadyInHand = false;
rightHandModel = false;
}
/*
var options = new AudioInjectionOptions();
options.position = modelPosition;
options.volume = 1.0;
Audio.playSound(throwSound, options);
*/
}
}
}
function checkController(deltaTime) {
var numberOfButtons = Controller.getNumberOfButtons();
var numberOfTriggers = Controller.getNumberOfTriggers();
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
// this is expected for hydras
if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) {
debugPrint("no hydra connected?");
return; // bail if no hydra
}
checkControllerSide(LEFT_PALM);
checkControllerSide(RIGHT_PALM);
if (rightModelAlreadyInHand) {
var rightTriggerValue = Controller.getTriggerValue(1);
if (rightTriggerValue > 0.0) {
var possibleRadius = ((1.0 - rightTriggerValue) * (radiusMaximum - radiusMinimum)) + radiusMinimum;
modelRadius = possibleRadius;
} else {
modelRadius = radiusDefault;
}
}
if (leftModelAlreadyInHand) {
var leftTriggerValue = Controller.getTriggerValue(0);
if (leftTriggerValue > 0.0) {
var possibleRadius = ((1.0 - leftTriggerValue) * (radiusMaximum - radiusMinimum)) + radiusMinimum;
modelRadius = possibleRadius;
} else {
modelRadius = radiusDefault;
}
}
}
// register the call back so it fires before each data send
Script.update.connect(checkController);
Controller.keyPressEvent.connect(keyPressEvent);

View file

@ -167,11 +167,11 @@ function standUp() {
var models = new Object();
function SeatIndicator(modelProperties, seatIndex) {
this.position = Vec3.sum(modelProperties.position,
Vec3.multiply(Vec3.multiplyQbyV(modelProperties.modelRotation,
Vec3.multiply(Vec3.multiplyQbyV(modelProperties.rotation,
modelProperties.sittingPoints[seatIndex].position),
modelProperties.radius));
this.orientation = Quat.multiply(modelProperties.modelRotation,
this.orientation = Quat.multiply(modelProperties.rotation,
modelProperties.sittingPoints[seatIndex].rotation);
this.scale = MyAvatar.scale / 12;
@ -284,7 +284,7 @@ function update(deltaTime){
avatarOldPosition = MyAvatar.position;
var SEARCH_RADIUS = 50;
var foundModels = Models.findModels(MyAvatar.position, SEARCH_RADIUS);
var foundModels = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS);
// Let's remove indicator that got out of radius
for (model in models) {
if (Vec3.distance(models[model].properties.position, MyAvatar.position) > SEARCH_RADIUS) {
@ -296,7 +296,7 @@ function update(deltaTime){
for (var i = 0; i < foundModels.length; ++i) {
var model = foundModels[i];
if (typeof(models[model.id]) == "undefined") {
model.properties = Models.getModelProperties(model);
model.properties = Entities.getEntityProperties(model);
if (Vec3.distance(model.properties.position, MyAvatar.position) < SEARCH_RADIUS) {
addIndicators(model);
}
@ -318,7 +318,7 @@ function addIndicators(modelID) {
models[modelID.id] = modelID;
} else {
Models.editModel(modelID, { glowLevel: 0.0 });
Entities.editEntity(modelID, { glowLevel: 0.0 });
}
}

View file

@ -41,7 +41,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe
# grab the implementation and header files from src dirs
file(GLOB INTERFACE_SRCS src/*.cpp src/*.h)
foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles models)
foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities)
file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h)
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}")
endforeach(SUBDIR)
@ -101,7 +101,7 @@ endif()
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
# link required hifi libraries
link_hifi_libraries(shared octree voxels fbx metavoxels networking particles models avatars audio animation script-engine)
link_hifi_libraries(shared octree voxels fbx metavoxels networking particles entities avatars audio animation script-engine)
# find any optional and required libraries
find_package(ZLIB REQUIRED)

View file

@ -52,9 +52,9 @@
#include <AccountManager.h>
#include <AudioInjector.h>
#include <EntityScriptingInterface.h>
#include <LocalVoxelsList.h>
#include <Logging.h>
#include <ModelsScriptingInterface.h>
#include <NetworkAccessManager.h>
#include <OctalCode.h>
#include <OctreeSceneStats.h>
@ -141,8 +141,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_voxelImporter(),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_modelClipboardRenderer(),
_modelClipboard(),
_entityClipboardRenderer(),
_entityClipboard(),
_wantToKillLocalVoxels(false),
_viewFrustum(),
_lastQueriedViewFrustum(),
@ -303,13 +303,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// tell the NodeList instance who to tell the domain server we care about
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
<< NodeType::MetavoxelServer);
// connect to the packet sent signal of the _voxelEditSender and the _particleEditSender
connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent);
connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent);
connect(&_modelEditSender, &ModelEditPacketSender::packetSent, this, &Application::packetSent);
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
// move the silentNodeTimer to the _nodeThread
QTimer* silentNodeTimer = new QTimer();
@ -352,7 +352,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// Tell our voxel edit sender about our known jurisdictions
_voxelEditSender.setVoxelServerJurisdictions(&_voxelServerJurisdictions);
_particleEditSender.setServerJurisdictions(&_particleServerJurisdictions);
_modelEditSender.setServerJurisdictions(&_modelServerJurisdictions);
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
Particle::setVoxelEditPacketSender(&_voxelEditSender);
Particle::setParticleEditPacketSender(&_particleEditSender);
@ -364,7 +364,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// probably not the right long term solution. But for now, we're going to do this to
// allow you to move a particle around in your hand
_particleEditSender.setPacketsPerSecond(3000); // super high!!
_modelEditSender.setPacketsPerSecond(3000); // super high!!
_entityEditSender.setPacketsPerSecond(3000); // super high!!
// Set the sixense filtering
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
@ -454,7 +454,7 @@ Application::~Application() {
_voxelHideShowThread.terminate();
_voxelEditSender.terminate();
_particleEditSender.terminate();
_modelEditSender.terminate();
_entityEditSender.terminate();
VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown
@ -549,7 +549,7 @@ void Application::initializeGL() {
_voxelEditSender.initialize(_enableProcessVoxelsThread);
_voxelHideShowThread.initialize(_enableProcessVoxelsThread);
_particleEditSender.initialize(_enableProcessVoxelsThread);
_modelEditSender.initialize(_enableProcessVoxelsThread);
_entityEditSender.initialize(_enableProcessVoxelsThread);
if (_enableProcessVoxelsThread) {
qDebug("Voxel parsing thread created.");
@ -801,7 +801,7 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
break;
case NodeType::VoxelServer:
case NodeType::ParticleServer:
case NodeType::ModelServer:
case NodeType::EntityServer:
channel = BandwidthMeter::VOXELS;
break;
default:
@ -1339,7 +1339,7 @@ void Application::dropEvent(QDropEvent *event) {
void Application::sendPingPackets() {
QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
controlledBroadcastToNodes(pingPacket, NodeSet()
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
<< NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::MetavoxelServer);
}
@ -1523,22 +1523,21 @@ struct SendVoxelsOperationArgs {
const unsigned char* newBaseOctCode;
};
bool Application::exportModels(const QString& filename, float x, float y, float z, float scale) {
QVector<ModelItem*> models;
_models.getTree()->findModelsInCube(AACube(glm::vec3(x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), models);
if (models.size() > 0) {
bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) {
QVector<EntityItem*> entities;
_entities.getTree()->findEntities(AACube(glm::vec3(x / (float)TREE_SCALE,
y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), entities);
if (entities.size() > 0) {
glm::vec3 root(x, y, z);
ModelTree exportTree;
EntityTree exportTree;
for (int i = 0; i < models.size(); i++) {
ModelItemProperties properties;
ModelItemID id = models.at(i)->getModelItemID();
id.isKnownID = false;
properties.copyFromNewModelItem(*models.at(i));
for (int i = 0; i < entities.size(); i++) {
EntityItemProperties properties = entities.at(i)->getProperties();
EntityItemID id = entities.at(i)->getEntityItemID();
properties.setPosition(properties.getPosition() - root);
exportTree.addModel(id, properties);
exportTree.addEntity(id, properties);
}
exportTree.writeToSVOFile(filename.toLocal8Bit().constData());
} else {
qDebug() << "No models were selected";
@ -1631,17 +1630,17 @@ void Application::importVoxels() {
emit importDone();
}
bool Application::importModels(const QString& filename) {
_modelClipboard.eraseAllOctreeElements();
bool success = _modelClipboard.readFromSVOFile(filename.toLocal8Bit().constData());
bool Application::importEntities(const QString& filename) {
_entityClipboard.eraseAllOctreeElements();
bool success = _entityClipboard.readFromSVOFile(filename.toLocal8Bit().constData());
if (success) {
_modelClipboard.reaverageOctreeElements();
_entityClipboard.reaverageOctreeElements();
}
return success;
}
void Application::pasteModels(float x, float y, float z) {
_modelClipboard.sendModels(&_modelEditSender, x, y, z);
void Application::pasteEntities(float x, float y, float z) {
_entityClipboard.sendEntities(&_entityEditSender, _entities.getTree(), x, y, z);
}
void Application::cutVoxels(const VoxelDetail& sourceVoxel) {
@ -1797,12 +1796,12 @@ void Application::init() {
_particles.init();
_particles.setViewFrustum(getViewFrustum());
_models.init();
_models.setViewFrustum(getViewFrustum());
_entities.init();
_entities.setViewFrustum(getViewFrustum());
_modelClipboardRenderer.init();
_modelClipboardRenderer.setViewFrustum(getViewFrustum());
_modelClipboardRenderer.setTree(&_modelClipboard);
_entityClipboardRenderer.init();
_entityClipboardRenderer.setViewFrustum(getViewFrustum());
_entityClipboardRenderer.setTree(&_entityClipboard);
_metavoxels.init();
@ -2032,7 +2031,7 @@ void Application::updateThreads(float deltaTime) {
_voxelHideShowThread.threadRoutine();
_voxelEditSender.threadRoutine();
_particleEditSender.threadRoutine();
_modelEditSender.threadRoutine();
_entityEditSender.threadRoutine();
}
}
@ -2165,8 +2164,8 @@ void Application::update(float deltaTime) {
}
{
PerformanceTimer perfTimer("models");
_models.update(); // update the models...
PerformanceTimer perfTimer("entities");
_entities.update(); // update the models...
}
{
@ -2210,9 +2209,16 @@ void Application::update(float deltaTime) {
// if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it
if (queryIsDue || viewIsDifferentEnough) {
_lastQueriedTime = now;
queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions);
queryOctree(NodeType::ModelServer, PacketTypeModelQuery, _modelServerJurisdictions);
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Particles)) {
queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
queryOctree(NodeType::EntityServer, PacketTypeEntityQuery, _entityServerJurisdictions);
}
_lastQueriedViewFrustum = _viewFrustum;
}
}
@ -2268,7 +2274,7 @@ int Application::sendNackPackets() {
if (node->getActiveSocket() &&
( node->getType() == NodeType::VoxelServer
|| node->getType() == NodeType::ParticleServer
|| node->getType() == NodeType::ModelServer)
|| node->getType() == NodeType::EntityServer)
) {
QUuid nodeUUID = node->getUUID();
@ -2335,13 +2341,7 @@ int Application::sendNackPackets() {
void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) {
// if voxels are disabled, then don't send this at all...
if (!Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
return;
}
//qDebug() << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView();
bool wantExtraDebugging = getLogger()->extraDebugging();
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
@ -2659,9 +2659,20 @@ void Application::updateShadowMap() {
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.1f, 4.0f); // magic numbers courtesy http://www.eecs.berkeley.edu/~ravir/6160/papers/shadowmaps.ppt
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
_particles.render(OctreeRenderer::SHADOW_RENDER_MODE);
_models.render(OctreeRenderer::SHADOW_RENDER_MODE);
{
PerformanceTimer perfTimer("avatarManager");
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
}
{
PerformanceTimer perfTimer("particles");
_particles.render(OctreeRenderer::SHADOW_RENDER_MODE);
}
{
PerformanceTimer perfTimer("entities");
_entities.render(OctreeRenderer::SHADOW_RENDER_MODE);
}
glDisable(GL_POLYGON_OFFSET_FILL);
@ -2858,10 +2869,10 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
// render models...
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
PerformanceTimer perfTimer("models");
PerformanceTimer perfTimer("entities");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... models...");
_models.render();
"Application::displaySide() ... entities...");
_entities.render();
}
// render the ambient occlusion effect if enabled
@ -3385,7 +3396,7 @@ void Application::domainChanged(const QString& domainHostname) {
_particles.clear();
// reset the model renderer
_models.clear();
_entities.clear();
// reset the voxels renderer
_voxels.killLocalVoxels();
@ -3424,7 +3435,7 @@ void Application::nodeKilled(SharedNodePointer node) {
_voxelEditSender.nodeKilled(node);
_particleEditSender.nodeKilled(node);
_modelEditSender.nodeKilled(node);
_entityEditSender.nodeKilled(node);
if (node->getType() == NodeType::AudioMixer) {
QMetaObject::invokeMethod(&_audio, "audioMixerKilled");
@ -3504,16 +3515,16 @@ void Application::nodeKilled(SharedNodePointer node) {
}
_octreeSceneStatsLock.unlock();
} else if (node->getType() == NodeType::ModelServer) {
} else if (node->getType() == NodeType::EntityServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
_modelServerJurisdictions.lockForRead();
if (_modelServerJurisdictions.find(nodeUUID) != _modelServerJurisdictions.end()) {
unsigned char* rootCode = _modelServerJurisdictions[nodeUUID].getRootOctalCode();
_entityServerJurisdictions.lockForRead();
if (_entityServerJurisdictions.find(nodeUUID) != _entityServerJurisdictions.end()) {
unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode();
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
_modelServerJurisdictions.unlock();
_entityServerJurisdictions.unlock();
qDebug("model server going away...... v[%f, %f, %f, %f]",
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
@ -3530,10 +3541,10 @@ void Application::nodeKilled(SharedNodePointer node) {
}
// If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
_modelServerJurisdictions.lockForWrite();
_modelServerJurisdictions.erase(_modelServerJurisdictions.find(nodeUUID));
_entityServerJurisdictions.lockForWrite();
_entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID));
}
_modelServerJurisdictions.unlock();
_entityServerJurisdictions.unlock();
// also clean up scene stats for that server
_octreeSceneStatsLock.lockForWrite();
@ -3599,8 +3610,8 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin
jurisdiction = &_particleServerJurisdictions;
serverType = "Particle";
} else {
jurisdiction = &_modelServerJurisdictions;
serverType = "Model";
jurisdiction = &_entityServerJurisdictions;
serverType = "Entity";
}
jurisdiction->lockForRead();
@ -3709,8 +3720,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
scriptEngine->getModelsScriptingInterface()->setPacketSender(&_modelEditSender);
scriptEngine->getModelsScriptingInterface()->setModelTree(_models.getTree());
scriptEngine->getEntityScriptingInterface()->setPacketSender(&_entityEditSender);
scriptEngine->getEntityScriptingInterface()->setEntityTree(_entities.getTree());
// model has some custom types
Model::registerMetaTypes(scriptEngine);

View file

@ -30,7 +30,7 @@
#include <QUndoStack>
#include <QSystemTrayIcon>
#include <ModelEditPacketSender.h>
#include <EntityEditPacketSender.h>
#include <NetworkPacket.h>
#include <NodeList.h>
#include <PacketHeaders.h>
@ -65,7 +65,7 @@
#include "devices/Visage.h"
#include "devices/CaraFaceTracker.h"
#include "devices/DdeFaceTracker.h"
#include "models/ModelTreeRenderer.h"
#include "entities/EntityTreeRenderer.h"
#include "particles/ParticleTreeRenderer.h"
#include "renderer/AmbientOcclusionEffect.h"
#include "renderer/GeometryCache.h"
@ -200,12 +200,12 @@ public:
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
ParticleTreeRenderer* getParticles() { return &_particles; }
MetavoxelSystem* getMetavoxels() { return &_metavoxels; }
ModelTreeRenderer* getModels() { return &_models; }
EntityTreeRenderer* getEntities() { return &_entities; }
bool getImportSucceded() { return _importSucceded; }
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
VoxelTree* getClipboard() { return &_clipboard; }
ModelTree* getModelClipboard() { return &_modelClipboard; }
ModelTreeRenderer* getModelClipboardRenderer() { return &_modelClipboardRenderer; }
EntityTree* getEntityClipboard() { return &_entityClipboard; }
EntityTreeRenderer* getEntityClipboardRenderer() { return &_entityClipboardRenderer; }
Environment* getEnvironment() { return &_environment; }
bool isMousePressed() const { return _mousePressed; }
bool isMouseHidden() const { return _mouseHidden; }
@ -291,7 +291,7 @@ public:
glm::vec2 getViewportDimensions() const{ return glm::vec2(_glWidget->width(),_glWidget->height()); }
NodeToJurisdictionMap& getVoxelServerJurisdictions() { return _voxelServerJurisdictions; }
NodeToJurisdictionMap& getParticleServerJurisdictions() { return _particleServerJurisdictions; }
NodeToJurisdictionMap& getModelServerJurisdictions() { return _modelServerJurisdictions; }
NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; }
void pasteVoxelsToOctalCode(const unsigned char* octalCodeDestination);
void skipVersion(QString latestVersion);
@ -321,9 +321,9 @@ public slots:
void nodeKilled(SharedNodePointer node);
void packetSent(quint64 length);
void pasteModels(float x, float y, float z);
bool exportModels(const QString& filename, float x, float y, float z, float scale);
bool importModels(const QString& filename);
void pasteEntities(float x, float y, float z);
bool exportEntities(const QString& filename, float x, float y, float z, float scale);
bool importEntities(const QString& filename);
void importVoxels(); // doesn't include source voxel because it goes to clipboard
void cutVoxels(const VoxelDetail& sourceVoxel);
@ -474,9 +474,9 @@ private:
ParticleTreeRenderer _particles;
ParticleCollisionSystem _particleCollisionSystem;
ModelTreeRenderer _models;
ModelTreeRenderer _modelClipboardRenderer;
ModelTree _modelClipboard;
EntityTreeRenderer _entities;
EntityTreeRenderer _entityClipboardRenderer;
EntityTree _entityClipboard;
QByteArray _voxelsFilename;
bool _wantToKillLocalVoxels;
@ -565,7 +565,7 @@ private:
VoxelHideShowThread _voxelHideShowThread;
VoxelEditPacketSender _voxelEditSender;
ParticleEditPacketSender _particleEditSender;
ModelEditPacketSender _modelEditSender;
EntityEditPacketSender _entityEditSender;
int _packetsPerSecond;
int _bytesPerSecond;
@ -578,7 +578,7 @@ private:
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
NodeToJurisdictionMap _modelServerJurisdictions;
NodeToJurisdictionMap _entityServerJurisdictions;
NodeToOctreeSceneStats _octreeServerSceneStats;
QReadWriteLock _octreeSceneStatsLock;

View file

@ -61,22 +61,21 @@ void DatagramProcessor::processDatagrams() {
Particle::handleAddParticleResponse(incomingPacket);
application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket);
break;
case PacketTypeModelAddResponse:
case PacketTypeEntityAddResponse:
// this will keep creatorTokenIDs to IDs mapped correctly
ModelItem::handleAddModelResponse(incomingPacket);
application->getModels()->getTree()->handleAddModelResponse(incomingPacket);
EntityItemID::handleAddEntityResponse(incomingPacket);
application->getEntities()->getTree()->handleAddEntityResponse(incomingPacket);
break;
case PacketTypeParticleData:
case PacketTypeParticleErase:
case PacketTypeModelData:
case PacketTypeModelErase:
case PacketTypeEntityData:
case PacketTypeEntityErase:
case PacketTypeVoxelData:
case PacketTypeVoxelErase:
case PacketTypeOctreeStats:
case PacketTypeEnvironmentData: {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::networkReceive()... _octreeProcessor.queueReceivedPacket()");
bool wantExtraDebugging = application->getLogger()->extraDebugging();
if (wantExtraDebugging && packetTypeForPacket(incomingPacket) == PacketTypeVoxelData) {
int numBytesPacketHeader = numBytesForPacketHeader(incomingPacket);
@ -89,7 +88,7 @@ void DatagramProcessor::processDatagrams() {
OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow();
int flightTime = arrivedAt - sentAt;
qDebug("got PacketType_VOXEL_DATA, sequence:%d flightTime:%d", sequence, flightTime);
qDebug("got an Octree data or erase message, sequence:%d flightTime:%d", sequence, flightTime);
}
SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(incomingPacket);
@ -159,9 +158,9 @@ void DatagramProcessor::processDatagrams() {
application->_particleEditSender.processNackPacket(incomingPacket);
}
break;
case PacketTypeModelEditNack:
case PacketTypeEntityEditNack:
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) {
application->_modelEditSender.processNackPacket(incomingPacket);
application->_entityEditSender.processNackPacket(incomingPacket);
}
break;
default:

View file

@ -108,7 +108,7 @@ void Avatar::simulate(float deltaTime) {
// update the avatar's position according to its referential
if (_referential) {
if (_referential->hasExtraData()) {
ModelTree* tree = Application::getInstance()->getModels()->getTree();
EntityTree* tree = Application::getInstance()->getEntities()->getTree();
switch (_referential->type()) {
case Referential::MODEL:
_referential = new ModelReferential(_referential,

View file

@ -11,40 +11,41 @@
#include <AvatarData.h>
#include "ModelTree.h"
#include <EntityTree.h>
#include "../renderer/Model.h"
#include "ModelReferential.h"
ModelReferential::ModelReferential(Referential* referential, ModelTree* tree, AvatarData* avatar) :
ModelReferential::ModelReferential(Referential* referential, EntityTree* tree, AvatarData* avatar) :
Referential(MODEL, avatar),
_tree(tree) {
_translation = referential->getTranslation();
_rotation = referential->getRotation();
_scale = referential->getScale();
unpackExtraData(reinterpret_cast<unsigned char*>(referential->getExtraData().data()),
referential->getExtraData().size());
if (!isValid()) {
qDebug() << "ModelReferential::copyConstructor(): Not Valid";
return;
}
const ModelItem* item = _tree->findModelByID(_modelID);
if (item != NULL) {
_refScale = item->getRadius();
_refRotation = item->getModelRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
update();
}
_tree(tree)
{
_translation = referential->getTranslation();
_rotation = referential->getRotation();
_scale = referential->getScale();
unpackExtraData(reinterpret_cast<unsigned char*>(referential->getExtraData().data()),
referential->getExtraData().size());
if (!isValid()) {
qDebug() << "ModelReferential::copyConstructor(): Not Valid";
return;
}
const EntityItem* item = _tree->findEntityByID(_entityID);
if (item != NULL) {
_refScale = item->getRadius();
_refRotation = item->getRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
update();
}
}
ModelReferential::ModelReferential(uint32_t modelID, ModelTree* tree, AvatarData* avatar) :
ModelReferential::ModelReferential(const QUuid& entityID, EntityTree* tree, AvatarData* avatar) :
Referential(MODEL, avatar),
_modelID(modelID),
_entityID(entityID),
_tree(tree)
{
const ModelItem* item = _tree->findModelByID(_modelID);
const EntityItem* item = _tree->findEntityByID(_entityID);
if (!isValid() || item == NULL) {
qDebug() << "ModelReferential::constructor(): Not Valid";
_isValid = false;
@ -52,7 +53,7 @@ ModelReferential::ModelReferential(uint32_t modelID, ModelTree* tree, AvatarData
}
_refScale = item->getRadius();
_refRotation = item->getModelRotation();
_refRotation = item->getRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
glm::quat refInvRot = glm::inverse(_refRotation);
@ -62,7 +63,7 @@ ModelReferential::ModelReferential(uint32_t modelID, ModelTree* tree, AvatarData
}
void ModelReferential::update() {
const ModelItem* item = _tree->findModelByID(_modelID);
const EntityItem* item = _tree->findEntityByID(_entityID);
if (!isValid() || item == NULL || _avatar == NULL) {
return;
}
@ -73,8 +74,8 @@ void ModelReferential::update() {
_avatar->setTargetScale(_refScale * _scale, true);
somethingChanged = true;
}
if (item->getModelRotation() != _refRotation) {
_refRotation = item->getModelRotation();
if (item->getRotation() != _refRotation) {
_refRotation = item->getRotation();
_avatar->setOrientation(_refRotation * _rotation, true);
somethingChanged = true;
}
@ -85,16 +86,18 @@ void ModelReferential::update() {
}
int ModelReferential::packExtraData(unsigned char* destinationBuffer) const {
memcpy(destinationBuffer, &_modelID, sizeof(_modelID));
return sizeof(_modelID);
QByteArray encodedEntityID = _entityID.toRfc4122();
memcpy(destinationBuffer, encodedEntityID.constData(), encodedEntityID.size());
return encodedEntityID.size();
}
int ModelReferential::unpackExtraData(const unsigned char *sourceBuffer, int size) {
memcpy(&_modelID, sourceBuffer, sizeof(_modelID));
return sizeof(_modelID);
QByteArray encodedEntityID((const char*)sourceBuffer, NUM_BYTES_RFC4122_UUID);
_entityID = QUuid::fromRfc4122(encodedEntityID);
return NUM_BYTES_RFC4122_UUID;
}
JointReferential::JointReferential(Referential* referential, ModelTree* tree, AvatarData* avatar) :
JointReferential::JointReferential(Referential* referential, EntityTree* tree, AvatarData* avatar) :
ModelReferential(referential, tree, avatar)
{
_type = JOINT;
@ -103,7 +106,7 @@ JointReferential::JointReferential(Referential* referential, ModelTree* tree, Av
return;
}
const ModelItem* item = _tree->findModelByID(_modelID);
const EntityItem* item = _tree->findEntityByID(_entityID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= model->getJointStateCount()) {
_refScale = item->getRadius();
@ -113,12 +116,12 @@ JointReferential::JointReferential(Referential* referential, ModelTree* tree, Av
update();
}
JointReferential::JointReferential(uint32_t jointIndex, uint32_t modelID, ModelTree* tree, AvatarData* avatar) :
ModelReferential(modelID, tree, avatar),
JointReferential::JointReferential(uint32_t jointIndex, const QUuid& entityID, EntityTree* tree, AvatarData* avatar) :
ModelReferential(entityID, tree, avatar),
_jointIndex(jointIndex)
{
_type = JOINT;
const ModelItem* item = _tree->findModelByID(_modelID);
const EntityItem* item = _tree->findEntityByID(_entityID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= model->getJointStateCount()) {
qDebug() << "JointReferential::constructor(): Not Valid";
@ -137,7 +140,7 @@ JointReferential::JointReferential(uint32_t jointIndex, uint32_t modelID, ModelT
}
void JointReferential::update() {
const ModelItem* item = _tree->findModelByID(_modelID);
const EntityItem* item = _tree->findEntityByID(_entityID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= model->getJointStateCount()) {
return;
@ -149,7 +152,7 @@ void JointReferential::update() {
_avatar->setTargetScale(_refScale * _scale, true);
somethingChanged = true;
}
if (item->getModelRotation() != _refRotation) {
if (item->getRotation() != _refRotation) {
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
_avatar->setOrientation(_refRotation * _rotation, true);
somethingChanged = true;
@ -160,10 +163,10 @@ void JointReferential::update() {
}
}
const Model* JointReferential::getModel(const ModelItem* item) {
ModelItemFBXService* fbxService = _tree->getFBXService();
const Model* JointReferential::getModel(const EntityItem* item) {
EntityItemFBXService* fbxService = _tree->getFBXService();
if (item != NULL && fbxService != NULL) {
return fbxService->getModelForModelItem(*item);
return fbxService->getModelForEntityItem(item);
}
return NULL;
}

View file

@ -14,31 +14,31 @@
#include <Referential.h>
class ModelTree;
class EntityTree;
class Model;
class ModelReferential : public Referential {
public:
ModelReferential(Referential* ref, ModelTree* tree, AvatarData* avatar);
ModelReferential(uint32_t modelID, ModelTree* tree, AvatarData* avatar);
ModelReferential(Referential* ref, EntityTree* tree, AvatarData* avatar);
ModelReferential(const QUuid& entityID, EntityTree* tree, AvatarData* avatar);
virtual void update();
protected:
virtual int packExtraData(unsigned char* destinationBuffer) const;
virtual int unpackExtraData(const unsigned char* sourceBuffer, int size);
uint32_t _modelID;
ModelTree* _tree;
QUuid _entityID;
EntityTree* _tree;
};
class JointReferential : public ModelReferential {
public:
JointReferential(Referential* ref, ModelTree* tree, AvatarData* avatar);
JointReferential(uint32_t jointIndex, uint32_t modelID, ModelTree* tree, AvatarData* avatar);
JointReferential(Referential* ref, EntityTree* tree, AvatarData* avatar);
JointReferential(uint32_t jointIndex, const QUuid& entityID, EntityTree* tree, AvatarData* avatar);
virtual void update();
protected:
const Model* getModel(const ModelItem* item);
const Model* getModel(const EntityItem* item);
virtual int packExtraData(unsigned char* destinationBuffer) const;
virtual int unpackExtraData(const unsigned char* sourceBuffer, int size);

View file

@ -484,8 +484,8 @@ void MyAvatar::clearReferential() {
changeReferential(NULL);
}
bool MyAvatar::setModelReferential(int id) {
ModelTree* tree = Application::getInstance()->getModels()->getTree();
bool MyAvatar::setModelReferential(const QUuid& id) {
EntityTree* tree = Application::getInstance()->getEntities()->getTree();
changeReferential(new ModelReferential(id, tree, this));
if (_referential->isValid()) {
return true;
@ -495,8 +495,8 @@ bool MyAvatar::setModelReferential(int id) {
}
}
bool MyAvatar::setJointReferential(int id, int jointIndex) {
ModelTree* tree = Application::getInstance()->getModels()->getTree();
bool MyAvatar::setJointReferential(const QUuid& id, int jointIndex) {
EntityTree* tree = Application::getInstance()->getEntities()->getTree();
changeReferential(new JointReferential(jointIndex, id, tree, this));
if (!_referential->isValid()) {
return true;

View file

@ -168,8 +168,8 @@ public slots:
glm::vec3 getRightPalmPosition();
void clearReferential();
bool setModelReferential(int id);
bool setJointReferential(int id, int jointIndex);
bool setModelReferential(const QUuid& id);
bool setJointReferential(const QUuid& id, int jointIndex);
bool isRecording();
qint64 recorderElapsed();

View file

@ -0,0 +1,321 @@
//
// EntityTreeRenderer.cpp
// interface/src
//
// Created by Brad Hefta-Gaub on 12/6/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 <glm/gtx/quaternion.hpp>
#include <FBXReader.h>
#include "InterfaceConfig.h"
#include <BoxEntityItem.h>
#include <ModelEntityItem.h>
#include <PerfStat.h>
#include "Menu.h"
#include "EntityTreeRenderer.h"
#include "RenderableBoxEntityItem.h"
#include "RenderableModelEntityItem.h"
#include "RenderableSphereEntityItem.h"
QThread* EntityTreeRenderer::getMainThread() {
return Application::getInstance()->getEntities()->thread();
}
EntityTreeRenderer::EntityTreeRenderer() :
OctreeRenderer() {
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
}
EntityTreeRenderer::~EntityTreeRenderer() {
}
void EntityTreeRenderer::clear() {
OctreeRenderer::clear();
}
void EntityTreeRenderer::init() {
OctreeRenderer::init();
static_cast<EntityTree*>(_tree)->setFBXService(this);
}
void EntityTreeRenderer::setTree(Octree* newTree) {
OctreeRenderer::setTree(newTree);
static_cast<EntityTree*>(_tree)->setFBXService(this);
}
void EntityTreeRenderer::update() {
if (_tree) {
EntityTree* tree = static_cast<EntityTree*>(_tree);
tree->update();
}
}
void EntityTreeRenderer::render(RenderMode renderMode) {
OctreeRenderer::render(renderMode);
deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup
}
const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(const EntityItem* entityItem) {
const FBXGeometry* result = NULL;
if (entityItem->getType() == EntityTypes::Model) {
const RenderableModelEntityItem* constModelEntityItem = dynamic_cast<const RenderableModelEntityItem*>(entityItem);
RenderableModelEntityItem* modelEntityItem = const_cast<RenderableModelEntityItem*>(constModelEntityItem);
assert(modelEntityItem); // we need this!!!
Model* model = modelEntityItem->getModel(this);
if (model) {
result = &model->getGeometry()->getFBXGeometry();
}
}
return result;
}
const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityItem) {
const Model* result = NULL;
if (entityItem->getType() == EntityTypes::Model) {
const RenderableModelEntityItem* constModelEntityItem = dynamic_cast<const RenderableModelEntityItem*>(entityItem);
RenderableModelEntityItem* modelEntityItem = const_cast<RenderableModelEntityItem*>(constModelEntityItem);
assert(modelEntityItem); // we need this!!!
result = modelEntityItem->getModel(this);
}
return result;
}
void renderElementProxy(EntityTreeElement* entityTreeElement) {
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float)TREE_SCALE;
float elementSize = entityTreeElement->getScale() * (float)TREE_SCALE;
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
glutWireCube(elementSize);
glPopMatrix();
bool displayElementChildProxies = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementChildProxies);
if (displayElementChildProxies) {
// draw the children
float halfSize = elementSize / 2.0f;
float quarterSize = elementSize / 4.0f;
glColor3f(1.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 1.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.5f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.5f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
}
}
float EntityTreeRenderer::distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const {
glm::vec3 temp = viewFrustum.getPosition() - center;
float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp));
return distanceToVoxelCenter;
}
// TODO: This could be optimized to be a table, or something that doesn't require recalculation on every
// render call for every entity
// TODO: This is essentially the same logic used to render voxels, but since models are more detailed then voxels
// I've added a voxelToModelRatio that adjusts how much closer to a model you have to be to see it.
bool EntityTreeRenderer::shouldRenderEntity(float largestDimension, float distanceToCamera) const {
const float voxelToModelRatio = 4.0f; // must be this many times closer to a model than a voxel to see it.
float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
float scale = (float)TREE_SCALE;
float visibleDistanceAtScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, voxelSizeScale) / voxelToModelRatio;
while (scale > largestDimension) {
scale /= 2.0f;
visibleDistanceAtScale /= 2.0f;
}
if (scale < largestDimension) {
visibleDistanceAtScale *= 2.0f;
}
return (distanceToCamera <= visibleDistanceAtScale);
}
void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
args->_elementsTouched++;
// actually render it here...
// we need to iterate the actual entityItems of the element
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
QList<EntityItem*>& entityItems = entityTreeElement->getEntities();
uint16_t numberOfEntities = entityItems.size();
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayElementProxy = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementProxy);
if (!isShadowMode && displayElementProxy && numberOfEntities > 0) {
renderElementProxy(entityTreeElement);
}
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItem* entityItem = entityItems[i];
// render entityItem
AACube entityCube = entityItem->getAACube();
entityCube.scale(TREE_SCALE);
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...
float distance = distanceToCamera(entityCube.calcCenter(), *args->_viewFrustum);
if (shouldRenderEntity(entityCube.getLargestDimension(), distance) &&
args->_viewFrustum->cubeInFrustum(entityCube) != ViewFrustum::OUTSIDE) {
Glower* glower = NULL;
if (entityItem->getGlowLevel() > 0.0f) {
glower = new Glower(entityItem->getGlowLevel());
}
entityItem->render(args);
if (glower) {
delete glower;
}
} else {
args->_itemsOutOfView++;
}
}
}
float EntityTreeRenderer::getSizeScale() const {
return Menu::getInstance()->getVoxelSizeScale();
}
int EntityTreeRenderer::getBoundaryLevelAdjust() const {
return Menu::getInstance()->getBoundaryLevelAdjust();
}
void EntityTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
static_cast<EntityTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
}
Model* EntityTreeRenderer::allocateModel(const QString& url) {
Model* model = NULL;
// Make sure we only create and delete models on the thread that owns the EntityTreeRenderer
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(Model*, model),
Q_ARG(const QString&, url));
return model;
}
model = new Model();
model->init();
model->setURL(QUrl(url));
return model;
}
Model* EntityTreeRenderer::updateModel(Model* original, const QString& newUrl) {
Model* model = NULL;
// The caller shouldn't call us if the URL doesn't need to change. But if they
// do, we just return their original back to them.
if (!original || (QUrl(newUrl) == original->getURL())) {
return original;
}
// Before we do any creating or deleting, make sure we're on our renderer thread
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateModel", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(Model*, model),
Q_ARG(Model*, original),
Q_ARG(const QString&, newUrl));
return model;
}
// at this point we know we need to replace the model, and we know we're on the
// correct thread, so we can do all our work.
if (original) {
delete original; // delete the old model...
}
// create the model and correctly initialize it with the new url
model = new Model();
model->init();
model->setURL(QUrl(newUrl));
return model;
}
void EntityTreeRenderer::releaseModel(Model* model) {
// If we're not on the renderer's thread, then remember this model to be deleted later
if (QThread::currentThread() != thread()) {
_releasedModels << model;
} else { // otherwise just delete it right away
delete model;
}
}
void EntityTreeRenderer::deleteReleasedModels() {
if (_releasedModels.size() > 0) {
foreach(Model* model, _releasedModels) {
delete model;
}
_releasedModels.clear();
}
}

View file

@ -0,0 +1,85 @@
//
// EntityTreeRenderer.h
// interface/src
//
// Created by Brad Hefta-Gaub on 12/6/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
//
#ifndef hifi_EntityTreeRenderer_h
#define hifi_EntityTreeRenderer_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <EntityTree.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ViewFrustum.h>
#include "renderer/Model.h"
// Generic client side Octree renderer class.
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
Q_OBJECT
public:
EntityTreeRenderer();
virtual ~EntityTreeRenderer();
virtual Octree* createTree() { return new EntityTree(true); }
virtual char getMyNodeType() const { return NodeType::EntityServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; }
virtual PacketType getExpectedPacketType() const { return PacketTypeEntityData; }
virtual void renderElement(OctreeElement* element, RenderArgs* args);
virtual float getSizeScale() const;
virtual int getBoundaryLevelAdjust() const;
virtual void setTree(Octree* newTree);
void update();
EntityTree* getTree() { return static_cast<EntityTree*>(_tree); }
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE);
virtual const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem);
virtual const Model* getModelForEntityItem(const EntityItem* entityItem);
/// clears the tree
virtual void clear();
//Q_INVOKABLE Model* getModel(const ModelEntityItem* modelEntityItem);
// renderers for various types of entities
void renderEntityTypeBox(EntityItem* entity, RenderArgs* args);
void renderEntityTypeModel(EntityItem* entity, RenderArgs* args);
static QThread* getMainThread();
/// if a renderable entity item needs a model, we will allocate it for them
Q_INVOKABLE Model* allocateModel(const QString& url);
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
Q_INVOKABLE Model* updateModel(Model* original, const QString& newUrl);
/// if a renderable entity item is done with a model, it should return it to us
void releaseModel(Model* model);
void deleteReleasedModels();
private:
QList<Model*> _releasedModels;
float distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const;
bool shouldRenderEntity(float largestDimension, float distanceToCamera) const;
};
#endif // hifi_EntityTreeRenderer_h

View file

@ -0,0 +1,101 @@
//
// RenderableBoxEntityItem.cpp
// interface/src
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 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 <glm/gtx/quaternion.hpp>
#include <FBXReader.h>
#include "InterfaceConfig.h"
#include <BoxEntityItem.h>
#include <ModelEntityItem.h>
#include <PerfStat.h>
#include "Menu.h"
#include "EntityTreeRenderer.h"
#include "RenderableBoxEntityItem.h"
EntityItem* RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new RenderableBoxEntityItem(entityID, properties);
}
void RenderableBoxEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
assert(getType() == EntityTypes::Box);
glm::vec3 position = getPosition() * (float)TREE_SCALE;
float size = getSize() * (float)TREE_SCALE;
glm::quat rotation = getRotation();
const bool useGlutCube = false;
if (useGlutCube) {
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glutSolidCube(size);
glPopMatrix();
} else {
static GLfloat vertices[] = { 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0,v1,v2,v3 (front)
1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0,v3,v4,v5 (right)
1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0,v5,v6,v1 (top)
-1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1, 1, // v1,v6,v7,v2 (left)
-1,-1,-1, 1,-1,-1, 1,-1, 1, -1,-1, 1, // v7,v4,v3,v2 (bottom)
1,-1,-1, -1,-1,-1, -1, 1,-1, 1, 1,-1 }; // v4,v7,v6,v5 (back)
// normal array
static GLfloat normals[] = { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0,v1,v2,v3 (front)
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0,v3,v4,v5 (right)
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0,v5,v6,v1 (top)
-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1,v6,v7,v2 (left)
0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, // v7,v4,v3,v2 (bottom)
0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1 }; // v4,v7,v6,v5 (back)
// index array of vertex array for glDrawElements() & glDrawRangeElement()
static GLubyte indices[] = { 0, 1, 2, 2, 3, 0, // front
4, 5, 6, 6, 7, 4, // right
8, 9,10, 10,11, 8, // top
12,13,14, 14,15,12, // left
16,17,18, 18,19,16, // bottom
20,21,22, 22,23,20 }; // back
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glNormalPointer(GL_FLOAT, 0, normals);
glVertexPointer(3, GL_FLOAT, 0, vertices);
//glEnable(GL_BLEND);
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
// we need to do half the size because the geometry in the VBOs are from -1,-1,-1 to 1,1,1
float halfSize = size/2.0f;
glScalef(halfSize, halfSize, halfSize);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices);
glPopMatrix();
glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays
glDisableClientState(GL_NORMAL_ARRAY);
}
};

View file

@ -0,0 +1,40 @@
//
// RenderableBoxEntityItem.h
// interface/src/entities
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 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
//
#ifndef hifi_RenderableBoxEntityItem_h
#define hifi_RenderableBoxEntityItem_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <EntityTree.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ViewFrustum.h>
#include <BoxEntityItem.h>
class RenderableBoxEntityItem : public BoxEntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
RenderableBoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
BoxEntityItem(entityItemID, properties)
{ }
virtual void render(RenderArgs* args);
};
#endif // hifi_RenderableBoxEntityItem_h

View file

@ -0,0 +1,240 @@
//
// RenderableModelEntityItem.cpp
// interface/src
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 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 <glm/gtx/quaternion.hpp>
#include <FBXReader.h>
#include "InterfaceConfig.h"
#include <BoxEntityItem.h>
#include <ModelEntityItem.h>
#include <PerfStat.h>
#include "Menu.h"
#include "EntityTreeRenderer.h"
#include "RenderableModelEntityItem.h"
EntityItem* RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new RenderableModelEntityItem(entityID, properties);
}
RenderableModelEntityItem::~RenderableModelEntityItem() {
assert(_myRenderer || !_model); // if we have a model, we need to know our renderer
if (_myRenderer && _model) {
_myRenderer->releaseModel(_model);
_model = NULL;
}
}
bool RenderableModelEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
QString oldModelURL = getModelURL();
bool somethingChanged = ModelEntityItem::setProperties(properties, forceCopy);
if (somethingChanged && oldModelURL != getModelURL()) {
_needsModelReload = true;
}
return somethingChanged;
}
int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
QString oldModelURL = getModelURL();
int bytesRead = ModelEntityItem::readEntitySubclassDataFromBuffer(data, bytesLeftToRead,
args, propertyFlags, overwriteLocalData);
if (oldModelURL != getModelURL()) {
_needsModelReload = true;
}
return bytesRead;
}
void RenderableModelEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableModelEntityItem::render");
assert(getType() == EntityTypes::Model);
bool drawAsModel = hasModel();
glm::vec3 position = getPosition() * (float)TREE_SCALE;
float radius = getRadius() * (float)TREE_SCALE;
float size = getSize() * (float)TREE_SCALE;
if (drawAsModel) {
glPushMatrix();
{
const float alpha = 1.0f;
if (!_model || _needsModelReload) {
// TODO: this getModel() appears to be about 3% of model render time. We should optimize
PerformanceTimer perfTimer("getModel");
EntityTreeRenderer* renderer = static_cast<EntityTreeRenderer*>(args->_renderer);
getModel(renderer);
}
if (_model) {
// handle animations..
if (hasAnimation()) {
if (!jointsMapped()) {
QStringList modelJointNames = _model->getJointNames();
mapJoints(modelJointNames);
}
if (jointsMapped()) {
QVector<glm::quat> frameData = getAnimationFrame();
for (int i = 0; i < frameData.size(); i++) {
_model->setJointState(i, true, frameData[i]);
}
}
}
glm::quat rotation = getRotation();
if (needsSimulation() && _model->isActive()) {
_model->setScaleToFit(true, radius * 2.0f);
_model->setSnapModelToCenter(true);
_model->setRotation(rotation);
_model->setTranslation(position);
// make sure to simulate so everything gets set up correctly for rendering
{
PerformanceTimer perfTimer("_model->simulate");
_model->simulate(0.0f);
}
_needsInitialSimulation = false;
}
// TODO: should we allow entityItems to have alpha on their models?
Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE
? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
if (_model->isActive()) {
// TODO: this is the majority of model render time. And rendering of a cube model vs the basic Box render
// is significantly more expensive. Is there a way to call this that doesn't cost us as much?
PerformanceTimer perfTimer("model->render");
_model->render(alpha, modelRenderMode);
} else {
// if we couldn't get a model, then just draw a cube
glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutWireCube(size);
glPopMatrix();
}
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
if (!isShadowMode && displayModelBounds) {
PerformanceTimer perfTimer("displayModelBounds");
glm::vec3 unRotatedMinimum = _model->getUnscaledMeshExtents().minimum;
glm::vec3 unRotatedMaximum = _model->getUnscaledMeshExtents().maximum;
glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum;
float width = unRotatedExtents.x;
float height = unRotatedExtents.y;
float depth = unRotatedExtents.z;
Extents rotatedExtents = _model->getUnscaledMeshExtents();
calculateRotatedExtents(rotatedExtents, rotation);
glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;
const glm::vec3& modelScale = _model->getScale();
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
// draw the orignal bounding cube
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
glutWireCube(size);
// draw the rotated bounding cube
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix();
glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z);
glutWireCube(1.0);
glPopMatrix();
// draw the model relative bounding box
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
glColor3f(0.0f, 1.0f, 0.0f);
glutWireCube(1.0);
glPopMatrix();
}
} else {
// if we couldn't get a model, then just draw a cube
glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutWireCube(size);
glPopMatrix();
}
}
glPopMatrix();
} else {
glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutWireCube(size);
glPopMatrix();
}
}
Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
Model* result = NULL;
// make sure our renderer is setup
if (!_myRenderer) {
_myRenderer = renderer;
}
assert(_myRenderer == renderer); // you should only ever render on one renderer
_needsModelReload = false; // this is the reload
// if we have a URL, then we will want to end up returning a model...
if (!getModelURL().isEmpty()) {
// if we have a previously allocated model, but it's URL doesn't match
// then we need to let our renderer update our model for us.
if (_model && QUrl(getModelURL()) != _model->getURL()) {
result = _model = _myRenderer->updateModel(_model, getModelURL());
_needsInitialSimulation = true;
} else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one
result = _model = _myRenderer->allocateModel(getModelURL());
_needsInitialSimulation = true;
} else { // we already have the model we want...
result = _model;
}
} else { // if our desired URL is empty, we may need to delete our existing model
if (_model) {
_myRenderer->releaseModel(_model);
result = _model = NULL;
_needsInitialSimulation = true;
}
}
return result;
}
bool RenderableModelEntityItem::needsSimulation() const {
SimulationState simulationState = getSimulationState();
return _needsInitialSimulation || simulationState == Moving || simulationState == Changing;
}

View file

@ -0,0 +1,62 @@
//
// RenderableModelEntityItem.h
// interface/src/entities
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 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
//
#ifndef hifi_RenderableModelEntityItem_h
#define hifi_RenderableModelEntityItem_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <EntityTree.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ViewFrustum.h>
#include "renderer/Model.h"
#include <ModelEntityItem.h>
#include <BoxEntityItem.h>
class RenderableModelEntityItem : public ModelEntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
RenderableModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
ModelEntityItem(entityItemID, properties),
_model(NULL),
_needsInitialSimulation(true),
_needsModelReload(true),
_myRenderer(NULL) { }
virtual ~RenderableModelEntityItem();
virtual bool setProperties(const EntityItemProperties& properties, bool forceCopy);
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
virtual void somethingChangedNotification() { _needsInitialSimulation = true; }
virtual void render(RenderArgs* args);
Model* getModel(EntityTreeRenderer* renderer);
private:
bool needsSimulation() const;
Model* _model;
bool _needsInitialSimulation;
bool _needsModelReload;
EntityTreeRenderer* _myRenderer;
};
#endif // hifi_RenderableModelEntityItem_h

View file

@ -0,0 +1,42 @@
//
// RenderableSphereEntityItem.cpp
// interface/src
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 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 <glm/gtx/quaternion.hpp>
#include <FBXReader.h>
#include "InterfaceConfig.h"
#include <PerfStat.h>
#include <SphereEntityItem.h>
#include "Menu.h"
#include "EntityTreeRenderer.h"
#include "RenderableSphereEntityItem.h"
EntityItem* RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new RenderableSphereEntityItem(entityID, properties);
}
void RenderableSphereEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableSphereEntityItem::render");
assert(getType() == EntityTypes::Sphere);
glm::vec3 position = getPosition() * (float)TREE_SCALE;
float radius = getRadius() * (float)TREE_SCALE;
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
};

View file

@ -0,0 +1,40 @@
//
// RenderableSphereEntityItem.h
// interface/src/entities
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 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
//
#ifndef hifi_RenderableSphereEntityItem_h
#define hifi_RenderableSphereEntityItem_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <EntityTree.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ViewFrustum.h>
#include <SphereEntityItem.h>
class RenderableSphereEntityItem : public SphereEntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
RenderableSphereEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
SphereEntityItem(entityItemID, properties)
{ }
virtual void render(RenderArgs* args);
};
#endif // hifi_RenderableSphereEntityItem_h

View file

@ -1,373 +0,0 @@
//
// ModelTreeRenderer.cpp
// interface/src
//
// Created by Brad Hefta-Gaub on 12/6/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 <glm/gtx/quaternion.hpp>
#include <FBXReader.h>
#include "InterfaceConfig.h"
#include "Menu.h"
#include "ModelTreeRenderer.h"
ModelTreeRenderer::ModelTreeRenderer() :
OctreeRenderer() {
}
ModelTreeRenderer::~ModelTreeRenderer() {
clearModelsCache();
}
void ModelTreeRenderer::clear() {
OctreeRenderer::clear();
clearModelsCache();
}
void ModelTreeRenderer::clearModelsCache() {
qDebug() << "ModelTreeRenderer::clearModelsCache()...";
// delete the models in _knownModelsItemModels
foreach(Model* model, _knownModelsItemModels) {
delete model;
}
_knownModelsItemModels.clear();
foreach(Model* model, _unknownModelsItemModels) {
delete model;
}
_unknownModelsItemModels.clear();
}
void ModelTreeRenderer::init() {
OctreeRenderer::init();
static_cast<ModelTree*>(_tree)->setFBXService(this);
}
void ModelTreeRenderer::setTree(Octree* newTree) {
OctreeRenderer::setTree(newTree);
static_cast<ModelTree*>(_tree)->setFBXService(this);
}
void ModelTreeRenderer::update() {
if (_tree) {
ModelTree* tree = static_cast<ModelTree*>(_tree);
tree->update();
}
}
void ModelTreeRenderer::render(RenderMode renderMode) {
OctreeRenderer::render(renderMode);
}
const FBXGeometry* ModelTreeRenderer::getGeometryForModel(const ModelItem& modelItem) {
const FBXGeometry* result = NULL;
Model* model = getModel(modelItem);
if (model) {
result = &model->getGeometry()->getFBXGeometry();
}
return result;
}
const Model* ModelTreeRenderer::getModelForModelItem(const ModelItem& modelItem) {
return getModel(modelItem);
}
Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) {
Model* model = NULL;
if (modelItem.isKnownID()) {
if (_knownModelsItemModels.find(modelItem.getID()) != _knownModelsItemModels.end()) {
model = _knownModelsItemModels[modelItem.getID()];
if (QUrl(modelItem.getModelURL()) != model->getURL()) {
delete model; // delete the old model...
model = NULL;
_knownModelsItemModels.remove(modelItem.getID());
}
}
// if we don't have a model...
if (!model) {
// Make sure we only create new models on the thread that owns the ModelTreeRenderer
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "getModel", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(Model*, model), Q_ARG(const ModelItem&, modelItem));
return model;
}
model = new Model();
model->init();
model->setURL(QUrl(modelItem.getModelURL()));
_knownModelsItemModels[modelItem.getID()] = model;
}
} else {
if (_unknownModelsItemModels.find(modelItem.getCreatorTokenID()) != _unknownModelsItemModels.end()) {
model = _unknownModelsItemModels[modelItem.getCreatorTokenID()];
if (QUrl(modelItem.getModelURL()) != model->getURL()) {
delete model; // delete the old model...
model = NULL;
_unknownModelsItemModels.remove(modelItem.getID());
}
}
if (!model) {
// Make sure we only create new models on the thread that owns the ModelTreeRenderer
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "getModel", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(Model*, model), Q_ARG(const ModelItem&, modelItem));
return model;
}
model = new Model();
model->init();
model->setURL(QUrl(modelItem.getModelURL()));
_unknownModelsItemModels[modelItem.getCreatorTokenID()] = model;
}
}
return model;
}
void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
args->_elementsTouched++;
// actually render it here...
// we need to iterate the actual modelItems of the element
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
QList<ModelItem>& modelItems = modelTreeElement->getModels();
uint16_t numberOfModels = modelItems.size();
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
bool displayElementProxy = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementProxy);
bool displayElementChildProxies = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementChildProxies);
if (!isShadowMode && displayElementProxy && numberOfModels > 0) {
glm::vec3 elementCenter = modelTreeElement->getAACube().calcCenter() * (float)TREE_SCALE;
float elementSize = modelTreeElement->getScale() * (float)TREE_SCALE;
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
glutWireCube(elementSize);
glPopMatrix();
if (displayElementChildProxies) {
// draw the children
float halfSize = elementSize / 2.0f;
float quarterSize = elementSize / 4.0f;
glColor3f(1.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 1.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(1.0f, 1.0f, 1.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.5f);
glPushMatrix();
glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.5f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize);
glutWireCube(halfSize);
glPopMatrix();
glColor3f(0.0f, 0.5f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize);
glutWireCube(halfSize);
glPopMatrix();
}
}
for (uint16_t i = 0; i < numberOfModels; i++) {
ModelItem& modelItem = modelItems[i];
// render modelItem aspoints
AACube modelCube = modelItem.getAACube();
modelCube.scale(TREE_SCALE);
if (args->_viewFrustum->cubeInFrustum(modelCube) != ViewFrustum::OUTSIDE) {
glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE;
float radius = modelItem.getRadius() * (float)TREE_SCALE;
float size = modelItem.getSize() * (float)TREE_SCALE;
bool drawAsModel = modelItem.hasModel();
args->_itemsRendered++;
if (drawAsModel) {
glPushMatrix();
{
const float alpha = 1.0f;
Model* model = getModel(modelItem);
if (model) {
model->setScaleToFit(true, radius * 2.0f);
model->setSnapModelToCenter(true);
// set the rotation
glm::quat rotation = modelItem.getModelRotation();
model->setRotation(rotation);
// set the position
model->setTranslation(position);
// handle animations..
if (modelItem.hasAnimation()) {
if (!modelItem.jointsMapped()) {
QStringList modelJointNames = model->getJointNames();
modelItem.mapJoints(modelJointNames);
}
QVector<glm::quat> frameData = modelItem.getAnimationFrame();
for (int i = 0; i < frameData.size(); i++) {
model->setJointState(i, true, frameData[i]);
}
}
// make sure to simulate so everything gets set up correctly for rendering
model->simulate(0.0f);
// TODO: should we allow modelItems to have alpha on their models?
Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE
? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
if (modelItem.getGlowLevel() > 0.0f) {
Glower glower(modelItem.getGlowLevel());
if (model->isActive()) {
model->render(alpha, modelRenderMode);
} else {
// if we couldn't get a model, then just draw a sphere
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
}
} else {
if (model->isActive()) {
model->render(alpha, modelRenderMode);
} else {
// if we couldn't get a model, then just draw a sphere
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
}
}
if (!isShadowMode && displayModelBounds) {
glm::vec3 unRotatedMinimum = model->getUnscaledMeshExtents().minimum;
glm::vec3 unRotatedMaximum = model->getUnscaledMeshExtents().maximum;
glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum;
float width = unRotatedExtents.x;
float height = unRotatedExtents.y;
float depth = unRotatedExtents.z;
Extents rotatedExtents = model->getUnscaledMeshExtents();
calculateRotatedExtents(rotatedExtents, rotation);
glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;
const glm::vec3& modelScale = model->getScale();
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
// draw the orignal bounding cube
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
glutWireCube(size);
// draw the rotated bounding cube
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix();
glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z);
glutWireCube(1.0);
glPopMatrix();
// draw the model relative bounding box
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
glColor3f(0.0f, 1.0f, 0.0f);
glutWireCube(1.0);
glPopMatrix();
}
} else {
// if we couldn't get a model, then just draw a sphere
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
}
}
glPopMatrix();
} else {
//glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glPopMatrix();
}
} else {
args->_itemsOutOfView++;
}
}
}
float ModelTreeRenderer::getSizeScale() const {
return Menu::getInstance()->getVoxelSizeScale();
}
int ModelTreeRenderer::getBoundaryLevelAdjust() const {
return Menu::getInstance()->getBoundaryLevelAdjust();
}
void ModelTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
static_cast<ModelTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
}

View file

@ -1,65 +0,0 @@
//
// ModelTreeRenderer.h
// interface/src
//
// Created by Brad Hefta-Gaub on 12/6/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
//
#ifndef hifi_ModelTreeRenderer_h
#define hifi_ModelTreeRenderer_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <ModelTree.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ViewFrustum.h>
#include "renderer/Model.h"
// Generic client side Octree renderer class.
class ModelTreeRenderer : public OctreeRenderer, public ModelItemFBXService {
Q_OBJECT
public:
ModelTreeRenderer();
virtual ~ModelTreeRenderer();
virtual Octree* createTree() { return new ModelTree(true); }
virtual char getMyNodeType() const { return NodeType::ModelServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; }
virtual void renderElement(OctreeElement* element, RenderArgs* args);
virtual float getSizeScale() const;
virtual int getBoundaryLevelAdjust() const;
virtual void setTree(Octree* newTree);
void update();
ModelTree* getTree() { return (ModelTree*)_tree; }
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE);
virtual const FBXGeometry* getGeometryForModel(const ModelItem& modelItem);
virtual const Model* getModelForModelItem(const ModelItem& modelItem);
/// clears the tree
virtual void clear();
protected:
void clearModelsCache();
Q_INVOKABLE Model* getModel(const ModelItem& modelItem);
QMap<uint32_t, Model*> _knownModelsItemModels;
QMap<uint32_t, Model*> _unknownModelsItemModels;
};
#endif // hifi_ModelTreeRenderer_h

View file

@ -1252,8 +1252,6 @@ float Model::getLimbLength(int jointIndex) const {
return length;
}
const int BALL_SUBDIVISIONS = 10;
void Model::renderJointCollisionShapes(float alpha) {
// implement this when we have shapes for regular models
}

View file

@ -133,14 +133,14 @@ void ClipboardScriptingInterface::nudgeVoxel(float x, float y, float z, float s,
}
bool ClipboardScriptingInterface::exportModels(const QString& filename, float x, float y, float z, float s) {
return Application::getInstance()->exportModels(filename, x, y, z, s);
bool ClipboardScriptingInterface::exportEntities(const QString& filename, float x, float y, float z, float s) {
return Application::getInstance()->exportEntities(filename, x, y, z, s);
}
bool ClipboardScriptingInterface::importModels(const QString& filename) {
return Application::getInstance()->importModels(filename);
bool ClipboardScriptingInterface::importEntities(const QString& filename) {
return Application::getInstance()->importEntities(filename);
}
void ClipboardScriptingInterface::pasteModels(float x, float y, float z, float s) {
Application::getInstance()->pasteModels(x, y, z);
void ClipboardScriptingInterface::pasteEntities(float x, float y, float z, float s) {
Application::getInstance()->pasteEntities(x, y, z);
}

View file

@ -46,9 +46,9 @@ public slots:
void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec);
bool importModels(const QString& filename);
bool exportModels(const QString& filename, float x, float y, float z, float s);
void pasteModels(float x, float y, float z, float s);
bool importEntities(const QString& filename);
bool exportEntities(const QString& filename, float x, float y, float z, float s);
void pasteEntities(float x, float y, float z, float s);
};
#endif // hifi_ClipboardScriptingInterface_h

View file

@ -5,7 +5,7 @@
// Created by Ryan Huffman on 05/14/14.
// Copyright 2014 High Fidelity, Inc.
//
// This class draws a border around the different Voxel, Model, and Particle nodes on the current domain,
// This class draws a border around the different Voxel, Entity, and Particle nodes on the current domain,
// and a semi-transparent cube around the currently mouse-overed node.
//
// Distributed under the Apache License, Version 2.0.
@ -20,20 +20,20 @@
NodeBounds::NodeBounds(QObject* parent) :
QObject(parent),
_showVoxelNodes(false),
_showModelNodes(false),
_showEntityNodes(false),
_showParticleNodes(false),
_overlayText() {
}
void NodeBounds::draw() {
if (!(_showVoxelNodes || _showModelNodes || _showParticleNodes)) {
if (!(_showVoxelNodes || _showEntityNodes || _showParticleNodes)) {
_overlayText[0] = '\0';
return;
}
NodeToJurisdictionMap& voxelServerJurisdictions = Application::getInstance()->getVoxelServerJurisdictions();
NodeToJurisdictionMap& modelServerJurisdictions = Application::getInstance()->getModelServerJurisdictions();
NodeToJurisdictionMap& entityServerJurisdictions = Application::getInstance()->getEntityServerJurisdictions();
NodeToJurisdictionMap& particleServerJurisdictions = Application::getInstance()->getParticleServerJurisdictions();
NodeToJurisdictionMap* serverJurisdictions;
@ -61,8 +61,8 @@ void NodeBounds::draw() {
if (nodeType == NodeType::VoxelServer && _showVoxelNodes) {
serverJurisdictions = &voxelServerJurisdictions;
} else if (nodeType == NodeType::ModelServer && _showModelNodes) {
serverJurisdictions = &modelServerJurisdictions;
} else if (nodeType == NodeType::EntityServer && _showEntityNodes) {
serverJurisdictions = &entityServerJurisdictions;
} else if (nodeType == NodeType::ParticleServer && _showParticleNodes) {
serverJurisdictions = &particleServerJurisdictions;
} else {
@ -89,7 +89,7 @@ void NodeBounds::draw() {
+ ((serverBounds.getVertex(TOP_LEFT_FAR) - serverBounds.getVertex(BOTTOM_RIGHT_NEAR)) / 2.0f);
const float VOXEL_NODE_SCALE = 1.00f;
const float MODEL_NODE_SCALE = 0.99f;
const float ENTITY_NODE_SCALE = 0.99f;
const float PARTICLE_NODE_SCALE = 0.98f;
float scaleFactor = rootDetails.s * TREE_SCALE;
@ -101,8 +101,8 @@ void NodeBounds::draw() {
// Scale different node types slightly differently because it's common for them to overlap.
if (nodeType == NodeType::VoxelServer) {
scaleFactor *= VOXEL_NODE_SCALE;
} else if (nodeType == NodeType::ModelServer) {
scaleFactor *= MODEL_NODE_SCALE;
} else if (nodeType == NodeType::EntityServer) {
scaleFactor *= ENTITY_NODE_SCALE;
} else {
scaleFactor *= PARTICLE_NODE_SCALE;
}
@ -216,7 +216,7 @@ void NodeBounds::drawNodeBorder(const glm::vec3& center, float scale, float red,
void NodeBounds::getColorForNodeType(NodeType_t nodeType, float& red, float& green, float& blue) {
red = nodeType == NodeType::VoxelServer ? 1.0 : 0.0;
green = nodeType == NodeType::ParticleServer ? 1.0 : 0.0;
blue = nodeType == NodeType::ModelServer ? 1.0 : 0.0;
blue = nodeType == NodeType::EntityServer ? 1.0 : 0.0;
}
void NodeBounds::drawOverlay() {

View file

@ -24,7 +24,7 @@ public:
NodeBounds(QObject* parent = NULL);
bool getShowVoxelNodes() { return _showVoxelNodes; }
bool getShowModelNodes() { return _showModelNodes; }
bool getShowEntityNodes() { return _showEntityNodes; }
bool getShowParticleNodes() { return _showParticleNodes; }
void draw();
@ -32,7 +32,7 @@ public:
public slots:
void setShowVoxelNodes(bool value) { _showVoxelNodes = value; }
void setShowModelNodes(bool value) { _showModelNodes = value; }
void setShowEntityNodes(bool value) { _showEntityNodes = value; }
void setShowParticleNodes(bool value) { _showParticleNodes = value; }
protected:
@ -41,7 +41,7 @@ protected:
private:
bool _showVoxelNodes;
bool _showModelNodes;
bool _showEntityNodes;
bool _showParticleNodes;
char _overlayText[MAX_OVERLAY_TEXT_LENGTH + 1];

View file

@ -232,8 +232,8 @@ void OctreeStatsDialog::showAllOctreeServers() {
showOctreeServersOfType(serverCount, NodeType::ParticleServer, "Particle",
Application::getInstance()->getParticleServerJurisdictions());
showOctreeServersOfType(serverCount, NodeType::ModelServer, "Model",
Application::getInstance()->getModelServerJurisdictions());
showOctreeServersOfType(serverCount, NodeType::EntityServer, "Entity",
Application::getInstance()->getEntityServerJurisdictions());
if (_voxelServerLabelsCount > serverCount) {
for (int i = serverCount; i < _voxelServerLabelsCount; i++) {

View file

@ -13,16 +13,16 @@
#include "LocalModelsOverlay.h"
LocalModelsOverlay::LocalModelsOverlay(ModelTreeRenderer* modelTreeRenderer) :
LocalModelsOverlay::LocalModelsOverlay(EntityTreeRenderer* entityTreeRenderer) :
Volume3DOverlay(),
_modelTreeRenderer(modelTreeRenderer) {
_entityTreeRenderer(entityTreeRenderer) {
}
LocalModelsOverlay::~LocalModelsOverlay() {
}
void LocalModelsOverlay::update(float deltatime) {
_modelTreeRenderer->update();
_entityTreeRenderer->update();
}
void LocalModelsOverlay::render() {
@ -31,9 +31,7 @@ void LocalModelsOverlay::render() {
Application* app = Application::getInstance();
glm::vec3 oldTranslation = app->getViewMatrixTranslation();
app->setViewMatrixTranslation(oldTranslation + _position);
_modelTreeRenderer->render();
_entityTreeRenderer->render();
Application::getInstance()->setViewMatrixTranslation(oldTranslation);
} glPopMatrix();
}

View file

@ -12,21 +12,21 @@
#ifndef hifi_LocalModelsOverlay_h
#define hifi_LocalModelsOverlay_h
#include "models/ModelTreeRenderer.h"
#include "entities/EntityTreeRenderer.h"
#include "Volume3DOverlay.h"
class LocalModelsOverlay : public Volume3DOverlay {
Q_OBJECT
public:
LocalModelsOverlay(ModelTreeRenderer* modelTreeRenderer);
LocalModelsOverlay(EntityTreeRenderer* entityTreeRenderer);
~LocalModelsOverlay();
virtual void update(float deltatime);
virtual void render();
private:
ModelTreeRenderer *_modelTreeRenderer;
EntityTreeRenderer* _entityTreeRenderer;
};
#endif // hifi_LocalModelsOverlay_h

View file

@ -160,7 +160,7 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
created = true;
is3D = true;
} else if (type == "localmodels") {
thisOverlay = new LocalModelsOverlay(Application::getInstance()->getModelClipboardRenderer());
thisOverlay = new LocalModelsOverlay(Application::getInstance()->getEntityClipboardRenderer());
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;

View file

@ -80,39 +80,46 @@ void OctreePacketProcessor::processPacket(const SharedNodePointer& sendingNode,
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket);
app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket);
if (sendingNode) {
if (sendingNode) {
switch(voxelPacketType) {
case PacketTypeParticleErase: {
switch(voxelPacketType) {
case PacketTypeParticleErase: {
if (Menu::getInstance()->isOptionChecked(MenuOption::Particles)) {
app->_particles.processEraseMessage(mutablePacket, sendingNode);
} break;
}
} break;
case PacketTypeParticleData: {
case PacketTypeParticleData: {
if (Menu::getInstance()->isOptionChecked(MenuOption::Particles)) {
app->_particles.processDatagram(mutablePacket, sendingNode);
} break;
}
} break;
case PacketTypeModelErase: {
app->_models.processEraseMessage(mutablePacket, sendingNode);
} break;
case PacketTypeEntityErase: {
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
app->_entities.processEraseMessage(mutablePacket, sendingNode);
}
} break;
case PacketTypeModelData: {
app->_models.processDatagram(mutablePacket, sendingNode);
} break;
case PacketTypeEntityData: {
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
app->_entities.processDatagram(mutablePacket, sendingNode);
}
} break;
case PacketTypeEnvironmentData: {
app->_environment.parseData(*sendingNode->getActiveSocket(), mutablePacket);
} break;
case PacketTypeEnvironmentData: {
app->_environment.parseData(*sendingNode->getActiveSocket(), mutablePacket);
} break;
default : {
default : {
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
app->_voxels.setDataSourceUUID(sendingNode->getUUID());
app->_voxels.parseData(mutablePacket);
app->_voxels.setDataSourceUUID(QUuid());
} break;
}
}
} break;
}
}
}

View file

@ -86,7 +86,6 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels, VoxelTree* tree)
VoxelTreeElement::addDeleteHook(this);
VoxelTreeElement::addUpdateHook(this);
_abandonedVBOSlots = 0;
_falseColorizeBySource = false;
_dataSourceUUID = QUuid();
_voxelServerCount = 0;
@ -229,7 +228,6 @@ void VoxelSystem::clearFreeBufferIndexes() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "clearFreeBufferIndexes()");
_voxelsInWriteArrays = 0; // reset our VBO
_abandonedVBOSlots = 0;
// clear out freeIndexes
{
@ -668,10 +666,6 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
_voxelsUpdated = newTreeToArrays(_tree->getRoot());
_tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean
if (_writeRenderFullVBO) {
_abandonedVBOSlots = 0; // reset the count of our abandoned slots, why is this here and not earlier????
}
_writeRenderFullVBO = false;
} else {
_voxelsUpdated = 0;
@ -792,7 +786,6 @@ bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, v
}
// TODO: does cleanupRemovedVoxels() ever get called?
// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots???
void VoxelSystem::recreateVoxelGeometryInView() {
@ -859,37 +852,6 @@ void VoxelSystem::checkForCulling() {
quint64 endViewCulling = usecTimestampNow();
_lastViewCullingElapsed = (endViewCulling - start) / 1000;
}
// Once we call cleanupRemovedVoxels() we do need to rebuild our VBOs (if anything was actually removed). So,
// we should consider putting this someplace else... as this might be able to occur less frequently, and save us on
// VBO reubuilding. Possibly we should do this only if our actual VBO usage crosses some lower boundary.
cleanupRemovedVoxels();
}
void VoxelSystem::cleanupRemovedVoxels() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "cleanupRemovedVoxels()");
// This handles cleanup of voxels that were culled as part of our regular out of view culling operation
if (!_removedVoxels.isEmpty()) {
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
qDebug() << "cleanupRemovedVoxels().. _removedVoxels=" << _removedVoxels.count();
}
while (!_removedVoxels.isEmpty()) {
delete _removedVoxels.extract();
}
_writeRenderFullVBO = true; // if we remove voxels, we must update our full VBOs
}
// we also might have VBO slots that have been abandoned, if too many of our VBO slots
// are abandonded we want to rerender our full VBOs
const float TOO_MANY_ABANDONED_RATIO = 0.5f;
if (!_usePrimitiveRenderer && !_writeRenderFullVBO &&
(_abandonedVBOSlots > (_voxelsInWriteArrays * TOO_MANY_ABANDONED_RATIO))) {
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
qDebug() << "cleanupRemovedVoxels().. _abandonedVBOSlots ["
<< _abandonedVBOSlots << "] > TOO_MANY_ABANDONED_RATIO";
}
_writeRenderFullVBO = true;
}
}
void VoxelSystem::copyWrittenDataToReadArraysFullVBOs() {
@ -1213,9 +1175,6 @@ void VoxelSystem::init() {
_initialMemoryUsageGPU = getFreeMemoryGPU();
initVoxelMemory();
// our own _removedVoxels doesn't need to be notified of voxel deletes
VoxelTreeElement::removeDeleteHook(&_removedVoxels);
}
void VoxelSystem::changeTree(VoxelTree* newTree) {

View file

@ -134,7 +134,6 @@ private:
bool _initialized;
int _callsToTreesToArrays;
OctreeElementBag _removedVoxels;
// Operation functions for tree recursion methods
static int _nodeCount;
@ -177,7 +176,6 @@ private:
unsigned long _voxelsUpdated;
unsigned long _voxelsInReadArrays;
unsigned long _voxelsInWriteArrays;
unsigned long _abandonedVBOSlots;
bool _writeRenderFullVBO;
bool _readRenderFullVBO;
@ -222,7 +220,6 @@ private:
void setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndices[]);
int newTreeToArrays(VoxelTreeElement* currentNode);
void cleanupRemovedVoxels();
void copyWrittenDataToReadArrays(bool fullVBOs);

View file

@ -51,14 +51,12 @@ int Referential::packReferential(unsigned char* destinationBuffer) const {
char size = packExtraData(destinationBuffer);
*sizePosition = size; // write extra data size in saved spot here
destinationBuffer += size;
return destinationBuffer - startPosition;
}
int Referential::unpackReferential(const unsigned char* sourceBuffer) {
const unsigned char* startPosition = sourceBuffer;
sourceBuffer += unpack(sourceBuffer);
char expectedSize = *sourceBuffer++;
char bytesRead = unpackExtraData(sourceBuffer, expectedSize);
_isValid = (bytesRead == expectedSize);
@ -67,7 +65,6 @@ int Referential::unpackReferential(const unsigned char* sourceBuffer) {
qDebug() << "[ERROR] Referential extra data overflow";
}
sourceBuffer += expectedSize;
return sourceBuffer - startPosition;
}

View file

@ -1,4 +1,4 @@
set(TARGET_NAME models)
set(TARGET_NAME entities)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Script)
@ -8,4 +8,4 @@ include_glm()
link_hifi_libraries(shared octree fbx networking animation)
# call macro to link our dependencies and bubble them up via a property on our target
link_shared_dependencies()
link_shared_dependencies()

View file

@ -0,0 +1,92 @@
//
// AddEntityOperator.cpp
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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 "EntityItem.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "AddEntityOperator.h"
AddEntityOperator::AddEntityOperator(EntityTree* tree,
EntityItem* newEntity) :
_tree(tree),
_newEntity(newEntity),
_foundNew(false),
_changeTime(usecTimestampNow()),
_newEntityBox()
{
// caller must have verified existence of newEntity
assert(_newEntity);
_newEntityBox = _newEntity->getAACube().clamp(0.0f, 1.0f);
}
bool AddEntityOperator::preRecursion(OctreeElement* element) {
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
// In Pre-recursion, we're generally deciding whether or not we want to recurse this
// path of the tree. For this operation, we want to recurse the branch of the tree if
// and of the following are true:
// * We have not yet found the location for the new entity, and this branch contains the bounds of the new entity
bool keepSearching = false; // assume we don't need to search any more
// If we haven't yet found the new entity, and this subTreeContains our new
// entity, then we need to keep searching.
if (!_foundNew && element->getAACube().contains(_newEntityBox)) {
// If this element is the best fit for the new entity properties, then add/or update it
if (entityTreeElement->bestFitBounds(_newEntityBox)) {
entityTreeElement->addEntityItem(_newEntity);
_tree->setContainingElement(_newEntity->getEntityItemID(), entityTreeElement);
_foundNew = true;
keepSearching = false;
} else {
keepSearching = true;
}
}
return keepSearching; // if we haven't yet found it, keep looking
}
bool AddEntityOperator::postRecursion(OctreeElement* element) {
// Post-recursion is the unwinding process. For this operation, while we
// unwind we want to mark the path as being dirty if we changed it below.
// We might have two paths, one for the old entity and one for the new entity.
bool keepSearching = !_foundNew;
// As we unwind, if we're in either of these two paths, we mark our element
// as dirty.
if ((_foundNew && element->getAACube().contains(_newEntityBox))) {
element->markWithChangedTime();
}
return keepSearching; // if we haven't yet found it, keep looking
}
OctreeElement* AddEntityOperator::possiblyCreateChildAt(OctreeElement* element, int childIndex) {
// If we're getting called, it's because there was no child element at this index while recursing.
// We only care if this happens while still searching for the new entity location.
// Check to see if
if (!_foundNew) {
float childElementScale = element->getAACube().getScale() / 2.0f; // all of our children will be half our scale
// if the scale of our desired cube is smaller than our children, then consider making a child
if (_newEntityBox.getLargestDimension() <= childElementScale) {
int indexOfChildContainingNewEntity = element->getMyChildContaining(_newEntityBox);
if (childIndex == indexOfChildContainingNewEntity) {
return element->addChildAtIndex(childIndex);
}
}
}
return NULL;
}

View file

@ -0,0 +1,32 @@
//
// AddEntityOperator.h
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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
//
#ifndef hifi_AddEntityOperator_h
#define hifi_AddEntityOperator_h
class AddEntityOperator : public RecurseOctreeOperator {
public:
AddEntityOperator(EntityTree* tree, EntityItem* newEntity);
virtual bool preRecursion(OctreeElement* element);
virtual bool postRecursion(OctreeElement* element);
virtual OctreeElement* possiblyCreateChildAt(OctreeElement* element, int childIndex);
private:
EntityTree* _tree;
EntityItem* _newEntity;
bool _foundNew;
quint64 _changeTime;
AABox _newEntityBox;
};
#endif // hifi_AddEntityOperator_h

View file

@ -0,0 +1,104 @@
//
// BoxEntityItem.cpp
// libraries/entities/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 <QDebug>
#include <ByteCountCoding.h>
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "BoxEntityItem.h"
EntityItem* BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new BoxEntityItem(entityID, properties);
}
BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
{
_type = EntityTypes::Box;
setProperties(properties, true);
}
EntityItemProperties BoxEntityItem::getProperties() const {
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
properties._color = getXColor();
properties._colorChanged = false;
properties._glowLevel = getGlowLevel();
properties._glowLevelChanged = false;
return properties;
}
bool BoxEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
bool somethingChanged = false;
somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class
if (properties._colorChanged || forceCopy) {
setColor(properties._color);
somethingChanged = true;
}
if (properties._glowLevelChanged || forceCopy) {
setGlowLevel(properties._glowLevel);
somethingChanged = true;
}
if (somethingChanged) {
bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - getLastEdited();
qDebug() << "BoxEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
}
setLastEdited(properties._lastEdited);
}
return somethingChanged;
}
int BoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
return bytesRead;
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
EntityPropertyFlags BoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;
return requestedProperties;
}
void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
}

View file

@ -0,0 +1,58 @@
//
// BoxEntityItem.h
// libraries/entities/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
//
#ifndef hifi_BoxEntityItem_h
#define hifi_BoxEntityItem_h
#include "EntityItem.h"
class BoxEntityItem : public EntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
BoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
ALLOW_INSTANTIATION // This class can be instantiated
// methods for getting/setting all properties of an entity
virtual EntityItemProperties getProperties() const;
virtual bool setProperties(const EntityItemProperties& properties, bool forceCopy = false);
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const;
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
protected:
rgbColor _color;
};
#endif // hifi_BoxEntityItem_h

View file

@ -0,0 +1,132 @@
//
// DeleteEntityOperator.cpp
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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 "EntityItem.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "DeleteEntityOperator.h"
DeleteEntityOperator::DeleteEntityOperator(EntityTree* tree, const EntityItemID& searchEntityID) :
_tree(tree),
_changeTime(usecTimestampNow()),
_foundCount(0),
_lookingCount(0)
{
addEntityIDToDeleteList(searchEntityID);
}
DeleteEntityOperator::~DeleteEntityOperator() {
}
DeleteEntityOperator::DeleteEntityOperator(EntityTree* tree) :
_tree(tree),
_changeTime(usecTimestampNow()),
_foundCount(0),
_lookingCount(0)
{
}
void DeleteEntityOperator::addEntityIDToDeleteList(const EntityItemID& searchEntityID) {
// check our tree, to determine if this entity is known
EntityToDeleteDetails details;
details.containingElement = _tree->getContainingElement(searchEntityID);
if (details.containingElement) {
details.entity = details.containingElement->getEntityWithEntityItemID(searchEntityID);
if (!details.entity) {
//assert(false);
qDebug() << "that's UNEXPECTED, we got a _containingElement, but couldn't find the oldEntity!";
} else {
details.cube = details.containingElement->getAACube();
_entitiesToDelete << details;
_lookingCount++;
_tree->trackDeletedEntity(searchEntityID);
// before deleting any entity make sure to remove it from our Mortal, Changing, and Moving lists
_tree->removeEntityFromSimulationLists(searchEntityID);
}
}
}
// does this entity tree element contain the old entity
bool DeleteEntityOperator::subTreeContainsSomeEntitiesToDelete(OctreeElement* element) {
bool containsEntity = false;
// If we don't have an old entity, then we don't contain the entity, otherwise
// check the bounds
if (_entitiesToDelete.size() > 0) {
AACube elementCube = element->getAACube();
foreach(const EntityToDeleteDetails& details, _entitiesToDelete) {
if (elementCube.contains(details.cube)) {
containsEntity = true;
break; // if it contains at least one, we're good to go
}
}
}
return containsEntity;
}
bool DeleteEntityOperator::preRecursion(OctreeElement* element) {
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
// In Pre-recursion, we're generally deciding whether or not we want to recurse this
// path of the tree. For this operation, we want to recurse the branch of the tree if
// and of the following are true:
// * We have not yet found the old entity, and this branch contains our old entity
// * We have not yet found the new entity, and this branch contains our new entity
//
// Note: it's often the case that the branch in question contains both the old entity
// and the new entity.
bool keepSearching = false; // assume we don't need to search any more
// If we haven't yet found all the entities, and this sub tree contains at least one of our
// entities, then we need to keep searching.
if ((_foundCount < _lookingCount) && subTreeContainsSomeEntitiesToDelete(element)) {
// check against each of our search entities
foreach(const EntityToDeleteDetails& details, _entitiesToDelete) {
// If this is the element we're looking for, then ask it to remove the old entity
// and we can stop searching.
if (entityTreeElement == details.containingElement) {
EntityItemID entityItemID = details.entity->getEntityItemID();
EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID); // find the actual entity
entityTreeElement->removeEntityItem(theEntity); // remove it from the element
_tree->setContainingElement(entityItemID, NULL); // update or id to element lookup
delete theEntity; // now actually delete the entity!
_foundCount++;
}
}
// if we haven't found all of our search for entities, then keep looking
keepSearching = (_foundCount < _lookingCount);
}
return keepSearching; // if we haven't yet found it, keep looking
}
bool DeleteEntityOperator::postRecursion(OctreeElement* element) {
// Post-recursion is the unwinding process. For this operation, while we
// unwind we want to mark the path as being dirty if we changed it below.
// We might have two paths, one for the old entity and one for the new entity.
bool keepSearching = (_foundCount < _lookingCount);
// As we unwind, if we're in either of these two paths, we mark our element
// as dirty.
if ((subTreeContainsSomeEntitiesToDelete(element))) {
element->markWithChangedTime();
}
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
entityTreeElement->pruneChildren(); // take this opportunity to prune any empty leaves
return keepSearching; // if we haven't yet found it, keep looking
}

View file

@ -0,0 +1,48 @@
//
// DeleteEntityOperator.h
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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
//
#ifndef hifi_DeleteEntityOperator_h
#define hifi_DeleteEntityOperator_h
class EntityToDeleteDetails {
public:
const EntityItem* entity;
AACube cube;
EntityTreeElement* containingElement;
};
inline uint qHash(const EntityToDeleteDetails& a, uint seed) {
return qHash(a.entity->getEntityItemID(), seed);
}
inline bool operator==(const EntityToDeleteDetails& a, const EntityToDeleteDetails& b) {
return a.entity->getEntityItemID() == b.entity->getEntityItemID();
}
class DeleteEntityOperator : public RecurseOctreeOperator {
public:
DeleteEntityOperator(EntityTree* tree);
DeleteEntityOperator(EntityTree* tree, const EntityItemID& searchEntityID);
~DeleteEntityOperator();
void addEntityIDToDeleteList(const EntityItemID& searchEntityID);
virtual bool preRecursion(OctreeElement* element);
virtual bool postRecursion(OctreeElement* element);
private:
EntityTree* _tree;
QSet<EntityToDeleteDetails> _entitiesToDelete;
quint64 _changeTime;
int _foundCount;
int _lookingCount;
bool subTreeContainsSomeEntitiesToDelete(OctreeElement* element);
};
#endif // hifi_DeleteEntityOperator_h

View file

@ -0,0 +1,53 @@
//
// EntityEditPacketSender.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 8/12/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 <assert.h>
#include <PerfStat.h>
#include <OctalCode.h>
#include <PacketHeaders.h>
#include "EntityEditPacketSender.h"
#include "EntityItem.h"
void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type,
unsigned char* editBuffer, size_t length, int clockSkew) {
if (type == PacketTypeEntityAddOrEdit) {
EntityItem::adjustEditPacketForClockSkew(editBuffer, length, clockSkew);
}
}
void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemID modelID,
const EntityItemProperties& properties) {
if (!_shouldSend) {
return; // bail early
}
// use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize
static unsigned char bufferOut[MAX_PACKET_SIZE];
int sizeOut = 0;
if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(type, bufferOut, sizeOut);
}
}
void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityItemID) {
if (!_shouldSend) {
return; // bail early
}
// use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize
static unsigned char bufferOut[MAX_PACKET_SIZE];
size_t sizeOut = 0;
if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(PacketTypeEntityErase, bufferOut, sizeOut);
}
}

View file

@ -0,0 +1,35 @@
//
// EntityEditPacketSender.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 8/12/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
//
#ifndef hifi_EntityEditPacketSender_h
#define hifi_EntityEditPacketSender_h
#include <OctreeEditPacketSender.h>
#include "EntityItem.h"
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
class EntityEditPacketSender : public OctreeEditPacketSender {
Q_OBJECT
public:
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
/// NOTE: EntityItemProperties assumes that all distances are in meter units
void queueEditEntityMessage(PacketType type, EntityItemID modelID, const EntityItemProperties& properties);
void queueEraseEntityMessage(const EntityItemID& entityItemID);
// My server type is the model server
virtual char getMyNodeType() const { return NodeType::EntityServer; }
virtual void adjustEditPacketForClockSkew(PacketType type, unsigned char* editBuffer, size_t length, int clockSkew);
};
#endif // hifi_EntityEditPacketSender_h

View file

@ -0,0 +1,677 @@
//
// EntityItem.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 <ByteCountCoding.h>
#include <GLMHelpers.h>
#include <Octree.h>
#include <RegisteredMetaTypes.h>
#include <SharedUtil.h> // usecTimestampNow()
#include <VoxelsScriptingInterface.h>
#include <VoxelDetail.h>
#include "EntityScriptingInterface.h"
#include "EntityItem.h"
#include "EntityTree.h"
const float EntityItem::IMMORTAL = -1.0f; /// special lifetime which means the entity lives for ever. default lifetime
const float EntityItem::DEFAULT_GLOW_LEVEL = 0.0f;
const float EntityItem::DEFAULT_MASS = 1.0f;
const float EntityItem::DEFAULT_LIFETIME = EntityItem::IMMORTAL;
const float EntityItem::DEFAULT_DAMPING = 0.99f;
const glm::vec3 EntityItem::NO_VELOCITY = glm::vec3(0, 0, 0);
const float EntityItem::EPSILON_VELOCITY_LENGTH = (1.0f / 10000.0f) / (float)TREE_SCALE; // really small
const glm::vec3 EntityItem::DEFAULT_VELOCITY = EntityItem::NO_VELOCITY;
const glm::vec3 EntityItem::NO_GRAVITY = glm::vec3(0, 0, 0);
const glm::vec3 EntityItem::REGULAR_GRAVITY = glm::vec3(0, (-9.8f / TREE_SCALE), 0);
const glm::vec3 EntityItem::DEFAULT_GRAVITY = EntityItem::NO_GRAVITY;
const QString EntityItem::DEFAULT_SCRIPT = QString("");
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_id = entityItemID.id;
_creatorTokenID = entityItemID.creatorTokenID;
// init values with defaults before calling setProperties
//uint64_t now = usecTimestampNow();
_lastEdited = 0;
_lastEditedFromRemote = 0;
_lastEditedFromRemoteInRemoteTime = 0;
_lastUpdated = 0;
_created = 0; // TODO: when do we actually want to make this "now"
_position = glm::vec3(0,0,0);
_radius = 0;
_rotation = ENTITY_DEFAULT_ROTATION;
_glowLevel = DEFAULT_GLOW_LEVEL;
_mass = DEFAULT_MASS;
_velocity = DEFAULT_VELOCITY;
_gravity = DEFAULT_GRAVITY;
_damping = DEFAULT_DAMPING;
_lifetime = DEFAULT_LIFETIME;
}
EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) {
_type = EntityTypes::Unknown;
_lastEdited = 0;
_lastEditedFromRemote = 0;
_lastEditedFromRemoteInRemoteTime = 0;
_lastUpdated = 0;
_created = properties.getCreated();
initFromEntityItemID(entityItemID);
setProperties(properties, true); // force copy
}
EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties;
requestedProperties += PROP_POSITION;
requestedProperties += PROP_RADIUS;
requestedProperties += PROP_ROTATION;
requestedProperties += PROP_MASS;
requestedProperties += PROP_VELOCITY;
requestedProperties += PROP_GRAVITY;
requestedProperties += PROP_DAMPING;
requestedProperties += PROP_LIFETIME;
requestedProperties += PROP_SCRIPT;
return requestedProperties;
}
OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData) const {
// 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...
OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best
// encode our ID as a byte count coded byte stream
QByteArray encodedID = getID().toRfc4122();
// encode our type as a byte count coded byte stream
ByteCountCoded<quint32> typeCoder = getType();
QByteArray encodedType = typeCoder;
quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited();
ByteCountCoded<quint64> updateDeltaCoder = updateDelta;
QByteArray encodedUpdateDelta = updateDeltaCoder;
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
EntityPropertyFlags requestedProperties = getEntityProperties(params);
EntityPropertyFlags propertiesDidntFit = requestedProperties;
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
// then our entityTreeElementExtraEncodeData should include data about which properties we need to append.
if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) {
requestedProperties = entityTreeElementExtraEncodeData->entities.value(getEntityItemID());
}
LevelDetails entityLevel = packetData->startLevel();
quint64 lastEdited = getLastEdited();
const bool wantDebug = false;
if (wantDebug) {
float editedAgo = getEditedAgo();
QString agoAsString = formatSecondsElapsed(editedAgo);
qDebug() << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited
<< " ago=" << editedAgo << "seconds - " << agoAsString;
}
bool successIDFits = false;
bool successTypeFits = false;
bool successCreatedFits = false;
bool successLastEditedFits = false;
bool successLastUpdatedFits = false;
bool successPropertyFlagsFits = false;
int propertyFlagsOffset = 0;
int oldPropertyFlagsLength = 0;
QByteArray encodedPropertyFlags;
int propertyCount = 0;
successIDFits = packetData->appendValue(encodedID);
if (successIDFits) {
successTypeFits = packetData->appendValue(encodedType);
}
if (successTypeFits) {
successCreatedFits = packetData->appendValue(_created);
}
if (successCreatedFits) {
successLastEditedFits = packetData->appendValue(lastEdited);
}
if (successLastEditedFits) {
successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta);
}
if (successLastUpdatedFits) {
propertyFlagsOffset = packetData->getUncompressedByteOffset();
encodedPropertyFlags = propertyFlags;
oldPropertyFlagsLength = encodedPropertyFlags.length();
successPropertyFlagsFits = packetData->appendValue(encodedPropertyFlags);
}
bool headerFits = successIDFits && successTypeFits && successCreatedFits && successLastEditedFits
&& successLastUpdatedFits && successPropertyFlagsFits;
int startOfEntityItemData = packetData->getUncompressedByteOffset();
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,
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, getPosition());
APPEND_ENTITY_PROPERTY(PROP_RADIUS, appendValue, getRadius());
APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation());
APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, getMass());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity());
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, getGravity());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, getDamping());
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, appendValue, getLifetime());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, getScript());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
propertyFlags,
propertiesDidntFit,
propertyCount,
appendState);
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
encodedPropertyFlags = propertyFlags;
int newPropertyFlagsLength = encodedPropertyFlags.length();
packetData->updatePriorBytes(propertyFlagsOffset,
(const unsigned char*)encodedPropertyFlags.constData(), encodedPropertyFlags.length());
// if the size of the PropertyFlags shrunk, we need to shift everything down to front of packet.
if (newPropertyFlagsLength < oldPropertyFlagsLength) {
int oldSize = packetData->getUncompressedSize();
const unsigned char* modelItemData = packetData->getUncompressedData(propertyFlagsOffset + oldPropertyFlagsLength);
int modelItemDataLength = endOfEntityItemData - startOfEntityItemData;
int newEntityItemDataStart = propertyFlagsOffset + newPropertyFlagsLength;
packetData->updatePriorBytes(newEntityItemDataStart, modelItemData, modelItemDataLength);
int newSize = oldSize - (oldPropertyFlagsLength - newPropertyFlagsLength);
packetData->setUncompressedSize(newSize);
} else {
assert(newPropertyFlagsLength == oldPropertyFlagsLength); // should not have grown
}
packetData->endLevel(entityLevel);
} else {
packetData->discardLevel(entityLevel);
appendState = OctreeElement::NONE; // if we got here, then we didn't include the item
}
// If any part of the model items didn't fit, then the element is considered partial
if (appendState != OctreeElement::COMPLETED) {
// add this item into our list for the next appendElementData() pass
entityTreeElementExtraEncodeData->entities.insert(getEntityItemID(), propertiesDidntFit);
}
return appendState;
}
// TODO: My goal is to get rid of this concept completely. The old code (and some of the current code) used this
// result to calculate if a packet being sent to it was potentially bad or corrupt. I've adjusted this to now
// only consider the minimum header bytes as being required. But it would be preferable to completely eliminate
// this logic from the callers.
int EntityItem::expectedBytes() {
// Header bytes
// 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...
const int MINIMUM_HEADER_BYTES = 27;
return MINIMUM_HEADER_BYTES;
}
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
bool wantDebug = false;
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
// NOTE: This shouldn't happen. The only versions of the bit stream that didn't support split mtu buffers should
// be handled by the model subclass and shouldn't call this routine.
qDebug() << "EntityItem::readEntityDataFromBuffer()... "
"ERROR CASE...args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU";
return 0;
}
// Header bytes
// 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...
const int MINIMUM_HEADER_BYTES = 27;
int bytesRead = 0;
if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) {
int originalLength = bytesLeftToRead;
QByteArray originalDataBuffer((const char*)data, originalLength);
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
const unsigned char* dataAt = data;
// id
QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size
_id = QUuid::fromRfc4122(encodedID);
_creatorTokenID = UNKNOWN_ENTITY_TOKEN; // if we know the id, then we don't care about the creator token
_newlyCreated = false;
dataAt += encodedID.size();
bytesRead += encodedID.size();
// type
QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint32> typeCoder = encodedType;
encodedType = typeCoder; // determine true length
dataAt += encodedType.size();
bytesRead += encodedType.size();
quint32 type = typeCoder;
_type = (EntityTypes::EntityType)type;
bool overwriteLocalData = true; // assume the new content overwrites our local data
// _created
quint64 createdFromBuffer = 0;
memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer));
dataAt += sizeof(createdFromBuffer);
bytesRead += sizeof(createdFromBuffer);
createdFromBuffer -= clockSkew;
_created = createdFromBuffer; // TODO: do we ever want to discard this???
if (wantDebug) {
quint64 lastEdited = getLastEdited();
float editedAgo = getEditedAgo();
QString agoAsString = formatSecondsElapsed(editedAgo);
QString ageAsString = formatSecondsElapsed(getAge());
qDebug() << "Loading entity " << getEntityItemID() << " from buffer...";
qDebug() << " _created =" << _created;
qDebug() << " age=" << getAge() << "seconds - " << ageAsString;
qDebug() << " lastEdited =" << lastEdited;
qDebug() << " ago=" << editedAgo << "seconds - " << agoAsString;
}
quint64 now = usecTimestampNow();
quint64 lastEditedFromBuffer = 0;
quint64 lastEditedFromBufferAdjusted = 0;
// TODO: we could make this encoded as a delta from _created
// _lastEdited
memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer));
dataAt += sizeof(lastEditedFromBuffer);
bytesRead += sizeof(lastEditedFromBuffer);
lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew;
bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);
if (wantDebug) {
qDebug() << "data from server **************** ";
qDebug() << " entityItemID=" << getEntityItemID();
qDebug() << " now=" << now;
qDebug() << " getLastEdited()=" << getLastEdited();
qDebug() << " lastEditedFromBuffer=" << lastEditedFromBuffer << " (BEFORE clockskew adjust)";
qDebug() << " clockSkew=" << clockSkew;
qDebug() << " lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted << " (AFTER clockskew adjust)";
qDebug() << " _lastEditedFromRemote=" << _lastEditedFromRemote
<< " (our local time the last server edit we accepted)";
qDebug() << " _lastEditedFromRemoteInRemoteTime=" << _lastEditedFromRemoteInRemoteTime
<< " (remote time the last server edit we accepted)";
qDebug() << " fromSameServerEdit=" << fromSameServerEdit;
}
bool ignoreServerPacket = false; // assume we're use this server packet
// If this packet is from the same server edit as the last packet we accepted from the server
// we probably want to use it.
if (fromSameServerEdit) {
// If this is from the same sever packet, then check against any local changes since we got
// the most recent packet from this server time
if (_lastEdited > _lastEditedFromRemote) {
ignoreServerPacket = true;
}
} else {
// If this isn't from the same sever packet, then honor our skew adjusted times...
// If we've changed our local tree more recently than the new data from this packet
// then we will not be changing our values, instead we just read and skip the data
if (_lastEdited > lastEditedFromBufferAdjusted) {
ignoreServerPacket = true;
}
}
if (ignoreServerPacket) {
overwriteLocalData = false;
if (wantDebug) {
qDebug() << "IGNORING old data from server!!! ****************";
}
} else {
if (wantDebug) {
qDebug() << "USING NEW data from server!!! ****************";
}
_lastEdited = lastEditedFromBufferAdjusted;
_lastEditedFromRemote = now;
_lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer;
somethingChangedNotification(); // notify derived classes that something has changed
}
// last updated is stored as ByteCountCoded delta from lastEdited
QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta;
quint64 updateDelta = updateDeltaCoder;
if (overwriteLocalData) {
_lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that for _lastEdited
if (wantDebug) {
qDebug() << "_lastUpdated=" << _lastUpdated;
qDebug() << "_lastEdited=" << _lastEdited;
qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted;
}
}
encodedUpdateDelta = updateDeltaCoder; // determine true length
dataAt += encodedUpdateDelta.size();
bytesRead += encodedUpdateDelta.size();
// Property Flags
QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
dataAt += propertyFlags.getEncodedLength();
bytesRead += propertyFlags.getEncodedLength();
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, _position);
READ_ENTITY_PROPERTY(PROP_RADIUS, float, _radius);
READ_ENTITY_PROPERTY_QUAT(PROP_ROTATION, _rotation);
READ_ENTITY_PROPERTY(PROP_MASS, float, _mass);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, _velocity);
READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, _gravity);
READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, _lifetime);
READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript);
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
}
return bytesRead;
}
void EntityItem::debugDump() const {
qDebug() << "EntityItem id:" << getEntityItemID();
qDebug(" edited ago:%f", getEditedAgo());
qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z);
qDebug(" radius:%f", getRadius());
}
// adjust any internal timestamps to fix clock skew for this server
void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, size_t length, int clockSkew) {
unsigned char* dataAt = editPacketBuffer;
int octets = numberOfThreeBitSectionsInCode(dataAt);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
dataAt += lengthOfOctcode;
// lastEdited
quint64 lastEditedInLocalTime;
memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime));
quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew;
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
const bool wantDebug = false;
if (wantDebug) {
qDebug("EntityItem::adjustEditPacketForClockSkew()...");
qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime;
qDebug() << " clockSkew: " << clockSkew;
qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime;
}
}
bool EntityItem::isRestingOnSurface() const {
return _position.y <= _radius
&& _velocity.y >= -EPSILON && _velocity.y <= EPSILON
&& _gravity.y < 0.0f;
}
void EntityItem::update(const quint64& updateTime) {
bool wantDebug = false;
float timeElapsed = (float)(updateTime - _lastUpdated) / (float)(USECS_PER_SECOND);
if (wantDebug) {
qDebug() << "********** EntityItem::update()";
qDebug() << " updateTime=" << updateTime;
qDebug() << " _lastUpdated=" << _lastUpdated;
qDebug() << " timeElapsed=" << timeElapsed;
}
_lastUpdated = updateTime;
if (wantDebug) {
qDebug() << "********** EntityItem::update() .... SETTING _lastUpdated=" << _lastUpdated;
}
if (hasVelocity() || hasGravity()) {
glm::vec3 position = getPosition();
glm::vec3 velocity = getVelocity();
if (wantDebug) {
qDebug() << "EntityItem::update()....";
qDebug() << " timeElapsed:" << timeElapsed;
qDebug() << " old AACube:" << getAACube();
qDebug() << " old position:" << position;
qDebug() << " old velocity:" << velocity;
}
position += velocity * timeElapsed;
// handle bounces off the ground... We bounce at the height of our radius...
if (position.y <= _radius) {
velocity = velocity * glm::vec3(1,-1,1);
// if we've slowed considerably, then just stop moving
if (glm::length(velocity) <= EPSILON_VELOCITY_LENGTH) {
velocity = NO_VELOCITY;
}
position.y = _radius;
}
// handle gravity....
if (hasGravity() && !isRestingOnSurface()) {
velocity += getGravity() * timeElapsed;
}
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
// "ground" plane of the domain, but for now it
if (hasGravity() && isRestingOnSurface()) {
velocity.y = 0.0f;
position.y = _radius;
}
// handle damping
glm::vec3 dampingResistance = velocity * getDamping();
if (wantDebug) {
qDebug() << " getDamping():" << getDamping();
qDebug() << " dampingResistance:" << dampingResistance;
qDebug() << " dampingResistance * timeElapsed:" << dampingResistance * timeElapsed;
}
velocity -= dampingResistance * timeElapsed;
if (wantDebug) {
qDebug() << " velocity AFTER dampingResistance:" << velocity;
}
// round velocity to zero if it's close enough...
if (glm::length(velocity) <= EPSILON_VELOCITY_LENGTH) {
velocity = NO_VELOCITY;
}
if (wantDebug) {
qDebug() << " new position:" << position;
qDebug() << " new velocity:" << velocity;
}
setPosition(position);
setVelocity(velocity);
if (wantDebug) {
qDebug() << " new AACube:" << getAACube();
}
}
}
EntityItem::SimulationState EntityItem::getSimulationState() const {
if (hasVelocity() || (hasGravity() && !isRestingOnSurface())) {
return EntityItem::Moving;
}
if (isMortal()) {
return EntityItem::Mortal;
}
return EntityItem::Static;
}
bool EntityItem::lifetimeHasExpired() const {
return isMortal() && (getAge() > getLifetime());
}
void EntityItem::copyChangedProperties(const EntityItem& other) {
*this = other;
}
EntityItemProperties EntityItem::getProperties() const {
EntityItemProperties properties;
properties._id = getID();
properties._idSet = true;
properties._created = _created;
properties._type = getType();
properties._position = getPosition() * (float) TREE_SCALE;
properties._radius = getRadius() * (float) TREE_SCALE;
properties._rotation = getRotation();
properties._mass = getMass();
properties._velocity = getVelocity() * (float) TREE_SCALE;
properties._gravity = getGravity() * (float) TREE_SCALE;
properties._damping = getDamping();
properties._lifetime = getLifetime();
properties._script = getScript();
properties._positionChanged = false;
properties._radiusChanged = false;
properties._rotationChanged = false;
properties._massChanged = false;
properties._velocityChanged = false;
properties._gravityChanged = false;
properties._dampingChanged = false;
properties._lifetimeChanged = false;
properties._scriptChanged = false;
properties._defaultSettings = false;
return properties;
}
bool EntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
bool somethingChanged = false;
// handle the setting of created timestamps for the basic new entity case
if (forceCopy) {
if (properties.getCreated() == UNKNOWN_CREATED_TIME) {
_created = usecTimestampNow();
} else if (properties.getCreated() != USE_EXISTING_CREATED_TIME) {
_created = properties.getCreated();
}
}
if (properties._positionChanged || forceCopy) {
// clamp positions to the domain to prevent someone from moving an entity out of the domain
setPosition(glm::clamp(properties._position / (float) TREE_SCALE, 0.0f, 1.0f));
somethingChanged = true;
}
if (properties._radiusChanged || forceCopy) {
setRadius(properties._radius / (float) TREE_SCALE);
somethingChanged = true;
}
if (properties._rotationChanged || forceCopy) {
setRotation(properties._rotation);
somethingChanged = true;
}
if (properties._massChanged || forceCopy) {
setMass(properties._mass);
somethingChanged = true;
}
if (properties._velocityChanged || forceCopy) {
setVelocity(properties._velocity / (float) TREE_SCALE);
somethingChanged = true;
}
if (properties._massChanged || forceCopy) {
setMass(properties._mass);
somethingChanged = true;
}
if (properties._gravityChanged || forceCopy) {
setGravity(properties._gravity / (float) TREE_SCALE);
somethingChanged = true;
}
if (properties._dampingChanged || forceCopy) {
setDamping(properties._damping);
somethingChanged = true;
}
if (properties._lifetimeChanged || forceCopy) {
setLifetime(properties._lifetime);
somethingChanged = true;
}
if (properties._scriptChanged || forceCopy) {
setScript(properties._script);
somethingChanged = true;
}
if (somethingChanged) {
somethingChangedNotification(); // notify derived classes that something has changed
bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - getLastEdited();
qDebug() << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
}
setLastEdited(properties._lastEdited);
}
return somethingChanged;
}

View file

@ -0,0 +1,212 @@
//
// EntityItem.h
// libraries/entities/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
//
#ifndef hifi_EntityItem_h
#define hifi_EntityItem_h
#include <stdint.h>
#include <glm/glm.hpp>
#include <AnimationCache.h> // for Animation, AnimationCache, and AnimationPointer classes
#include <Octree.h> // for EncodeBitstreamParams class
#include <OctreeElement.h> // for OctreeElement::AppendState
#include <OctreePacketData.h>
#include "EntityItemID.h"
#include "EntityItemProperties.h"
#include "EntityTypes.h"
class EntityTreeElementExtraEncodeData;
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { };
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
/// one directly, instead you must only construct one of it's derived classes with additional features.
class EntityItem {
public:
DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly
EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
virtual ~EntityItem() { }
// ID and EntityItemID related methods
QUuid getID() const { return _id; }
void setID(const QUuid& id) { _id = id; }
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
bool isNewlyCreated() const { return _newlyCreated; }
bool isKnownID() const { return getID() != UNKNOWN_ENTITY_ID; }
EntityItemID getEntityItemID() const { return EntityItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_ENTITY_ID); }
// methods for getting/setting all properties of an entity
virtual EntityItemProperties getProperties() const;
/// returns true is something changed
virtual bool setProperties(const EntityItemProperties& properties, bool forceCopy = false);
/// override this in your derived class if you'd like to be informed when something about the state of the entity
/// has changed. This will be called with properties change or when new data is loaded from a stream
virtual void somethingChangedNotification() { }
quint64 getLastUpdated() const { return _lastUpdated; } /// Last simulated time of this entity universal usecs
/// Last edited time of this entity universal usecs
quint64 getLastEdited() const { return _lastEdited; }
void setLastEdited(quint64 lastEdited) { _lastEdited = _lastUpdated = lastEdited; }
float getEditedAgo() const /// Elapsed seconds since this entity was last edited
{ return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; }
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData) const;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const { /* do nothing*/ };
static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args);
virtual int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData)
{ return 0; }
virtual void render(RenderArgs* args) { } // by default entity items don't know how to render
static int expectedBytes();
static bool encodeEntityEditMessageDetails(PacketType command, EntityItemID id, const EntityItemProperties& details,
unsigned char* bufferOut, int sizeIn, int& sizeOut);
static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, size_t length, int clockSkew);
virtual void update(const quint64& now);
typedef enum SimulationState_t {
Static,
Mortal,
Changing,
Moving
} SimulationState;
virtual SimulationState getSimulationState() const;
virtual void debugDump() const;
// similar to assignment/copy, but it handles keeping lifetime accurate
void copyChangedProperties(const EntityItem& other);
// attributes applicable to all entity types
EntityTypes::EntityType getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0)
void setPosition(const glm::vec3& value) { _position = value; } /// set position in domain scale units (0.0 - 1.0)
float getRadius() const { return _radius; } /// get radius in domain scale units (0.0 - 1.0)
void setRadius(float value) { _radius = value; } /// set radius in domain scale units (0.0 - 1.0)
const glm::quat& getRotation() const { return _rotation; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
static const float DEFAULT_GLOW_LEVEL;
float getGlowLevel() const { return _glowLevel; }
void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; }
static const float DEFAULT_MASS;
float getMass() const { return _mass; }
void setMass(float value) { _mass = value; }
static const glm::vec3 DEFAULT_VELOCITY;
static const glm::vec3 NO_VELOCITY;
static const float EPSILON_VELOCITY_LENGTH;
const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second
void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in domain scale units (0.0-1.0) per second
bool hasVelocity() const { return _velocity != NO_VELOCITY; }
static const glm::vec3 DEFAULT_GRAVITY;
static const glm::vec3 REGULAR_GRAVITY;
static const glm::vec3 NO_GRAVITY;
const glm::vec3& getGravity() const { return _gravity; } /// gravity in domain scale units (0.0-1.0) per second squared
void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in domain scale units (0.0-1.0) per second squared
bool hasGravity() const { return _gravity != NO_GRAVITY; }
// TODO: this should eventually be updated to support resting on collisions with other surfaces
bool isRestingOnSurface() const;
static const float DEFAULT_DAMPING;
float getDamping() const { return _damping; }
void setDamping(float value) { _damping = value; }
// lifetime related properties.
static const float IMMORTAL; /// special lifetime which means the entity lives for ever. default lifetime
static const float DEFAULT_LIFETIME;
float getLifetime() const { return _lifetime; } /// get the lifetime in seconds for the entity
void setLifetime(float value) { _lifetime = value; } /// set the lifetime in seconds for the entity
/// is this entity immortal, in that it has no lifetime set, and will exist until manually deleted
bool isImmortal() const { return _lifetime == IMMORTAL; }
/// is this entity mortal, in that it has a lifetime set, and will automatically be deleted when that lifetime expires
bool isMortal() const { return _lifetime != IMMORTAL; }
/// age of this entity in seconds
float getAge() const { return (float)(usecTimestampNow() - _created) / (float)USECS_PER_SECOND; }
bool lifetimeHasExpired() const;
// position, size, and bounds related helpers
float getSize() const { return _radius * 2.0f; } /// get maximum dimension in domain scale units (0.0 - 1.0)
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
AACube getAACube() const { return AACube(getMinimumPoint(), getSize()); } /// AACube in domain scale units (0.0 - 1.0)
static const QString DEFAULT_SCRIPT;
const QString& getScript() const { return _script; }
void setScript(const QString& value) { _script = value; }
protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
EntityTypes::EntityType _type;
QUuid _id;
uint32_t _creatorTokenID;
bool _newlyCreated;
quint64 _lastUpdated;
quint64 _lastEdited; // this is the last official local or remote edit time
quint64 _lastEditedFromRemote; // this is the last time we received and edit from the server
quint64 _lastEditedFromRemoteInRemoteTime; // time in server time space the last time we received and edit from the server
quint64 _created;
glm::vec3 _position;
float _radius;
glm::quat _rotation;
float _glowLevel;
float _mass;
glm::vec3 _velocity;
glm::vec3 _gravity;
float _damping;
float _lifetime;
QString _script;
};
#endif // hifi_EntityItem_h

View file

@ -0,0 +1,139 @@
//
// EntityItemID.cpp
// libraries/entities/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 <QDebug>
#include <PacketHeaders.h>
#include "EntityItemID.h"
// for locally created models
QHash<uint32_t, QUuid> EntityItemID::_tokenIDsToIDs;
uint32_t EntityItemID::_nextCreatorTokenID = 0;
EntityItemID::EntityItemID() :
id(NEW_ENTITY),
creatorTokenID(UNKNOWN_ENTITY_TOKEN),
isKnownID(false)
{
};
EntityItemID::EntityItemID(const EntityItemID& other) :
id(other.id),
creatorTokenID(other.creatorTokenID),
isKnownID(other.isKnownID)
{
}
EntityItemID::EntityItemID(const QUuid& id, uint32_t creatorTokenID, bool isKnownID) :
id(id),
creatorTokenID(creatorTokenID),
isKnownID(isKnownID)
{
};
EntityItemID::EntityItemID(const QUuid& id) :
id(id),
creatorTokenID(UNKNOWN_ENTITY_TOKEN),
isKnownID(true)
{
};
EntityItemID EntityItemID::getIDfromCreatorTokenID(uint32_t creatorTokenID) {
if (_tokenIDsToIDs.find(creatorTokenID) != _tokenIDsToIDs.end()) {
return EntityItemID(_tokenIDsToIDs[creatorTokenID], creatorTokenID, true);
}
return EntityItemID(UNKNOWN_ENTITY_ID);
}
uint32_t EntityItemID::getNextCreatorTokenID() {
uint32_t creatorTokenID = _nextCreatorTokenID;
_nextCreatorTokenID++;
return creatorTokenID;
}
EntityItemID EntityItemID::assignActualIDForToken() const {
EntityItemID newlyAssignedEntityID;
newlyAssignedEntityID.creatorTokenID = creatorTokenID;
newlyAssignedEntityID.isKnownID = true;
newlyAssignedEntityID.id = QUuid::createUuid();
return newlyAssignedEntityID;
}
EntityItemID EntityItemID::convertToKnownIDVersion() const {
EntityItemID knownIDVersionEntityID;
knownIDVersionEntityID.creatorTokenID = UNKNOWN_ENTITY_TOKEN;
knownIDVersionEntityID.isKnownID = true;
knownIDVersionEntityID.id = id;
return knownIDVersionEntityID;
}
EntityItemID EntityItemID::convertToCreatorTokenVersion() const {
EntityItemID knownIDVersionEntityID;
knownIDVersionEntityID.creatorTokenID = creatorTokenID;
knownIDVersionEntityID.isKnownID = false;
knownIDVersionEntityID.id = UNKNOWN_ENTITY_ID;
return knownIDVersionEntityID;
}
EntityItemID EntityItemID::readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead) {
EntityItemID result;
if (bytesLeftToRead >= NUM_BYTES_RFC4122_UUID) {
// id
QByteArray encodedID((const char*)data, NUM_BYTES_RFC4122_UUID);
result.id = QUuid::fromRfc4122(encodedID);
result.isKnownID = true;
result.creatorTokenID = UNKNOWN_ENTITY_TOKEN;
}
return result;
}
void EntityItemID::handleAddEntityResponse(const QByteArray& packet) {
const unsigned char* dataAt = reinterpret_cast<const unsigned char*>(packet.data());
int numBytesPacketHeader = numBytesForPacketHeader(packet);
int bytesRead = numBytesPacketHeader;
dataAt += numBytesPacketHeader;
uint32_t creatorTokenID;
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
dataAt += sizeof(creatorTokenID);
bytesRead += sizeof(creatorTokenID);
QUuid entityID = QUuid::fromRfc4122(packet.mid(bytesRead, NUM_BYTES_RFC4122_UUID));
dataAt += NUM_BYTES_RFC4122_UUID;
// add our token to id mapping
_tokenIDsToIDs[creatorTokenID] = entityID;
}
QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& id) {
QScriptValue obj = engine->newObject();
obj.setProperty("id", id.id.toString());
obj.setProperty("creatorTokenID", id.creatorTokenID);
obj.setProperty("isKnownID", id.isKnownID);
return obj;
}
void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& id) {
id.id = QUuid(object.property("id").toVariant().toString());
id.creatorTokenID = object.property("creatorTokenID").toVariant().toUInt();
id.isKnownID = object.property("isKnownID").toVariant().toBool();
}

View file

@ -0,0 +1,100 @@
//
// EntityItemID.h
// libraries/entities/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
//
#ifndef hifi_EntityItemID_h
#define hifi_EntityItemID_h
#include <stdint.h>
#include <QObject>
#include <QHash>
#include <QScriptEngine>
#include <QUuid>
const uint32_t UNKNOWN_ENTITY_TOKEN = 0xFFFFFFFF;
//const uint32_t NEW_ENTITY = 0xFFFFFFFF;
//const uint32_t UNKNOWN_ENTITY_ID = 0xFFFFFFFF;
const QUuid NEW_ENTITY;
const QUuid UNKNOWN_ENTITY_ID;
/// Abstract ID for editing model items. Used in EntityItem JS API - When models are created in the JS api, they are given a
/// local creatorTokenID, the actual id for the model is not known until the server responds to the creator with the
/// correct mapping. This class works with the scripting API an allows the developer to edit models they created.
class EntityItemID {
public:
EntityItemID();
EntityItemID(const EntityItemID& other);
EntityItemID(const QUuid& id, uint32_t creatorTokenID, bool isKnownID);
EntityItemID(const QUuid& id);
//uint32_t id;
QUuid id;
uint32_t creatorTokenID;
bool isKnownID;
// these methods will reduce the ID down to half the IDs data to allow for comparisons and searches of known values
EntityItemID convertToKnownIDVersion() const;
EntityItemID convertToCreatorTokenVersion() const;
// these methods allow you to create models, and later edit them.
//static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID);
static EntityItemID getIDfromCreatorTokenID(uint32_t creatorTokenID);
static uint32_t getNextCreatorTokenID();
static void handleAddEntityResponse(const QByteArray& packet);
static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead);
private:
friend class EntityTree;
EntityItemID assignActualIDForToken() const; // only called by EntityTree
static uint32_t _nextCreatorTokenID; /// used by the static interfaces for creator token ids
static QHash<uint32_t, QUuid> _tokenIDsToIDs;
};
inline bool operator<(const EntityItemID& a, const EntityItemID& b) {
return (a.id == b.id) ? (a.creatorTokenID < b.creatorTokenID) : (a.id < b.id);
}
inline bool operator==(const EntityItemID& a, const EntityItemID& b) {
if (a.id == UNKNOWN_ENTITY_ID || b.id == UNKNOWN_ENTITY_ID) {
return a.creatorTokenID == b.creatorTokenID;
}
return a.id == b.id;
}
inline uint qHash(const EntityItemID& a, uint seed) {
QUuid temp;
if (a.id == UNKNOWN_ENTITY_ID) {
temp = QUuid(a.creatorTokenID,0,0,0,0,0,0,0,0,0,0);
} else {
temp = a.id;
}
return qHash(temp, seed);
}
inline QDebug operator<<(QDebug debug, const EntityItemID& id) {
debug << "[ id:" << id.id << ", creatorTokenID:" << id.creatorTokenID << ", isKnownID:" << id.isKnownID << "]";
return debug;
}
Q_DECLARE_METATYPE(EntityItemID);
Q_DECLARE_METATYPE(QVector<EntityItemID>);
QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& properties);
void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties);
#endif // hifi_EntityItemID_h

View file

@ -0,0 +1,786 @@
//
// EntityItemProperties.cpp
// libraries/entities/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 <QDebug>
#include <QObject>
#include <ByteCountCoding.h>
#include <GLMHelpers.h>
#include <RegisteredMetaTypes.h>
#include "EntityItem.h"
#include "EntityItemProperties.h"
EntityItemProperties::EntityItemProperties() :
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
_lastEdited(0),
_created(UNKNOWN_CREATED_TIME),
_type(EntityTypes::Unknown),
_position(0),
_radius(ENTITY_DEFAULT_RADIUS),
_rotation(ENTITY_DEFAULT_ROTATION),
_mass(EntityItem::DEFAULT_MASS),
_velocity(EntityItem::DEFAULT_VELOCITY),
_gravity(EntityItem::DEFAULT_GRAVITY),
_damping(EntityItem::DEFAULT_DAMPING),
_lifetime(EntityItem::DEFAULT_LIFETIME),
_script(EntityItem::DEFAULT_SCRIPT),
_positionChanged(false),
_radiusChanged(false),
_rotationChanged(false),
_massChanged(false),
_velocityChanged(false),
_gravityChanged(false),
_dampingChanged(false),
_lifetimeChanged(false),
_scriptChanged(false),
_color(),
_modelURL(""),
_animationURL(""),
_animationIsPlaying(false),
_animationFrameIndex(0.0),
_animationFPS(ENTITY_DEFAULT_ANIMATION_FPS),
_glowLevel(0.0f),
_colorChanged(false),
_modelURLChanged(false),
_animationURLChanged(false),
_animationIsPlayingChanged(false),
_animationFrameIndexChanged(false),
_animationFPSChanged(false),
_glowLevelChanged(false),
_defaultSettings(true)
{
}
void EntityItemProperties::debugDump() const {
qDebug() << "EntityItemProperties...";
qDebug() << " _type=" << EntityTypes::getEntityTypeName(_type);
qDebug() << " _id=" << _id;
qDebug() << " _idSet=" << _idSet;
qDebug() << " _position=" << _position.x << "," << _position.y << "," << _position.z;
qDebug() << " _radius=" << _radius;
qDebug() << " _modelURL=" << _modelURL;
qDebug() << " changed properties...";
EntityPropertyFlags props = getChangedProperties();
props.debugDumpBits();
}
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
EntityPropertyFlags changedProperties;
if (_radiusChanged) {
changedProperties += PROP_RADIUS;
}
if (_positionChanged) {
changedProperties += PROP_POSITION;
}
if (_rotationChanged) {
changedProperties += PROP_ROTATION;
}
if (_massChanged) {
changedProperties += PROP_MASS;
}
if (_velocityChanged) {
changedProperties += PROP_VELOCITY;
}
if (_gravityChanged) {
changedProperties += PROP_GRAVITY;
}
if (_dampingChanged) {
changedProperties += PROP_DAMPING;
}
if (_lifetimeChanged) {
changedProperties += PROP_LIFETIME;
}
if (_scriptChanged) {
changedProperties += PROP_SCRIPT;
}
if (_colorChanged) {
changedProperties += PROP_COLOR;
}
if (_modelURLChanged) {
changedProperties += PROP_MODEL_URL;
}
if (_animationURLChanged) {
changedProperties += PROP_ANIMATION_URL;
}
if (_animationIsPlayingChanged) {
changedProperties += PROP_ANIMATION_PLAYING;
}
if (_animationFrameIndexChanged) {
changedProperties += PROP_ANIMATION_FRAME_INDEX;
}
if (_animationFPSChanged) {
changedProperties += PROP_ANIMATION_FPS;
}
return changedProperties;
}
QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) const {
QScriptValue properties = engine->newObject();
if (_idSet) {
properties.setProperty("id", _id.toString());
bool isKnownID = (_id != UNKNOWN_ENTITY_ID);
properties.setProperty("isKnownID", isKnownID);
}
properties.setProperty("type", EntityTypes::getEntityTypeName(_type));
QScriptValue position = vec3toScriptValue(engine, _position);
properties.setProperty("position", position);
properties.setProperty("radius", _radius);
QScriptValue rotation = quatToScriptValue(engine, _rotation);
properties.setProperty("rotation", rotation);
properties.setProperty("mass", _mass);
QScriptValue velocity = vec3toScriptValue(engine, _velocity);
properties.setProperty("velocity", velocity);
QScriptValue gravity = vec3toScriptValue(engine, _gravity);
properties.setProperty("gravity", gravity);
properties.setProperty("damping", _damping);
properties.setProperty("lifetime", _lifetime);
properties.setProperty("age", getAge()); // gettable, but not settable
properties.setProperty("ageAsText", formatSecondsElapsed(getAge())); // gettable, but not settable
properties.setProperty("script", _script);
QScriptValue color = xColorToScriptValue(engine, _color);
properties.setProperty("color", color);
properties.setProperty("modelURL", _modelURL);
properties.setProperty("animationURL", _animationURL);
properties.setProperty("animationIsPlaying", _animationIsPlaying);
properties.setProperty("animationFrameIndex", _animationFrameIndex);
properties.setProperty("animationFPS", _animationFPS);
properties.setProperty("glowLevel", _glowLevel);
// Sitting properties support
QScriptValue sittingPoints = engine->newObject();
for (int i = 0; i < _sittingPoints.size(); ++i) {
QScriptValue sittingPoint = engine->newObject();
sittingPoint.setProperty("name", _sittingPoints[i].name);
sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints[i].position));
sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints[i].rotation));
sittingPoints.setProperty(i, sittingPoint);
}
sittingPoints.setProperty("length", _sittingPoints.size());
properties.setProperty("sittingPoints", sittingPoints);
return properties;
}
void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
QScriptValue typeScriptValue = object.property("type");
if (typeScriptValue.isValid()) {
QString typeName;
typeName = typeScriptValue.toVariant().toString();
_type = EntityTypes::getEntityTypeFromName(typeName);
}
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) {
setPosition(newPosition); // gives us automatic clamping
}
}
}
QScriptValue radius = object.property("radius");
if (radius.isValid()) {
float newRadius;
newRadius = radius.toVariant().toFloat();
if (_defaultSettings || newRadius != _radius) {
_radius = newRadius;
_radiusChanged = true;
}
}
QScriptValue rotation = object.property("rotation");
if (rotation.isValid()) {
QScriptValue x = rotation.property("x");
QScriptValue y = rotation.property("y");
QScriptValue z = rotation.property("z");
QScriptValue w = rotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
if (_defaultSettings || newRotation != _rotation) {
_rotation = newRotation;
_rotationChanged = true;
}
}
}
QScriptValue mass = object.property("mass");
if (mass.isValid()) {
float newValue;
newValue = mass.toVariant().toFloat();
if (_defaultSettings || newValue != _mass) {
_mass = newValue;
_massChanged = true;
}
}
QScriptValue velocity = object.property("velocity");
if (velocity.isValid()) {
QScriptValue x = velocity.property("x");
QScriptValue y = velocity.property("y");
QScriptValue z = velocity.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newValue;
newValue.x = x.toVariant().toFloat();
newValue.y = y.toVariant().toFloat();
newValue.z = z.toVariant().toFloat();
if (_defaultSettings || newValue != _velocity) {
_velocity = newValue;
_velocityChanged = true;
}
}
}
QScriptValue gravity = object.property("gravity");
if (gravity.isValid()) {
QScriptValue x = gravity.property("x");
QScriptValue y = gravity.property("y");
QScriptValue z = gravity.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newValue;
newValue.x = x.toVariant().toFloat();
newValue.y = y.toVariant().toFloat();
newValue.z = z.toVariant().toFloat();
if (_defaultSettings || newValue != _gravity) {
_gravity = newValue;
_gravityChanged = true;
}
}
}
QScriptValue damping = object.property("damping");
if (damping.isValid()) {
float newValue;
newValue = damping.toVariant().toFloat();
if (_defaultSettings || newValue != _damping) {
_damping = newValue;
_dampingChanged = true;
}
}
QScriptValue lifetime = object.property("lifetime");
if (lifetime.isValid()) {
float newValue;
newValue = lifetime.toVariant().toFloat();
if (_defaultSettings || newValue != _lifetime) {
_lifetime = newValue;
_lifetimeChanged = true;
}
}
QScriptValue script = object.property("script");
if (script.isValid()) {
QString newValue;
newValue = script.toVariant().toString();
if (_defaultSettings || newValue != _script) {
_script = newValue;
_scriptChanged = 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 modelURL = object.property("modelURL");
if (modelURL.isValid()) {
QString newModelURL;
newModelURL = modelURL.toVariant().toString();
if (_defaultSettings || newModelURL != _modelURL) {
_modelURL = newModelURL;
_modelURLChanged = 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();
}
QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties) {
return properties.copyToScriptValue(engine);
}
void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties) {
properties.copyFromScriptValue(object);
}
// TODO: Implement support for edit packets that can span an MTU sized buffer. We need to implement a mechanism for the
// encodeEntityEditPacket() method to communicate the the caller which properties couldn't fit in the buffer. Similar
// to how we handle this in the Octree streaming case.
//
// TODO: Right now, all possible properties for all subclasses are handled here. Ideally we'd prefer
// to handle this in a more generic way. Allowing subclasses of EntityItem to register their properties
//
// TODO: There's a lot of repeated patterns in the code below to handle each property. It would be nice if the property
// registration mechanism allowed us to collapse these repeated sections of code into a single implementation that
// utilized the registration table to shorten up and simplify this code.
//
// TODO: Implement support for paged properties, spanning MTU, and custom properties
//
// TODO: Implement support for script and visible properties.
//
bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
unsigned char* bufferOut, int sizeIn, int& sizeOut) {
OctreePacketData ourDataPacket(false, sizeIn); // create a packetData object to add out packet details too.
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
bool success = true; // assume the best
OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best
sizeOut = 0;
// TODO: We need to review how jurisdictions should be handled for entities. (The old Models and Particles code
// didn't do anything special for jurisdictions, so we're keeping that same behavior here.)
//
// Always include the root octcode. This is only because the OctreeEditPacketSender will check these octcodes
// to determine which server to send the changes to in the case of multiple jurisdictions. The root will be sent
// to all servers.
glm::vec3 rootPosition(0);
float rootScale = 0.5f;
unsigned char* octcode = pointToOctalCode(rootPosition.x, rootPosition.y, rootPosition.z, rootScale);
success = packetData->startSubTree(octcode);
delete[] octcode;
// assuming we have rome to fit our octalCode, proceed...
if (success) {
// Now add our edit content details...
bool isNewEntityItem = (id.id == NEW_ENTITY);
// id
// encode our ID as a byte count coded byte stream
QByteArray encodedID = id.id.toRfc4122(); // NUM_BYTES_RFC4122_UUID
// encode our ID as a byte count coded byte stream
ByteCountCoded<quint32> tokenCoder;
QByteArray encodedToken;
// special case for handling "new" modelItems
if (isNewEntityItem) {
// encode our creator token as a byte count coded byte stream
tokenCoder = id.creatorTokenID;
encodedToken = tokenCoder;
}
// encode our type as a byte count coded byte stream
ByteCountCoded<quint32> typeCoder = (quint32)properties.getType();
QByteArray encodedType = typeCoder;
quint64 updateDelta = 0; // this is an edit so by definition, it's update is in sync
ByteCountCoded<quint64> updateDeltaCoder = updateDelta;
QByteArray encodedUpdateDelta = updateDeltaCoder;
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
EntityPropertyFlags requestedProperties = properties.getChangedProperties();
EntityPropertyFlags propertiesDidntFit = requestedProperties;
// TODO: we need to handle the multi-pass form of this, similar to how we handle entity data
//
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
// then our modelTreeElementExtraEncodeData should include data about which properties we need to append.
//if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) {
// requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID());
//}
LevelDetails entityLevel = packetData->startLevel();
// Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this
// timestamp for clock skew
quint64 lastEdited = properties.getLastEdited();
bool successLastEditedFits = packetData->appendValue(lastEdited);
bool successIDFits = packetData->appendValue(encodedID);
if (isNewEntityItem && successIDFits) {
successIDFits = packetData->appendValue(encodedToken);
}
bool successTypeFits = packetData->appendValue(encodedType);
// NOTE: We intentionally do not send "created" times in edit messages. This is because:
// 1) if the edit is to an existing entity, the created time can not be changed
// 2) if the edit is to a new entity, the created time is the last edited time
// TODO: Should we get rid of this in this in edit packets, since this has to always be 0?
bool successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta);
int propertyFlagsOffset = packetData->getUncompressedByteOffset();
QByteArray encodedPropertyFlags = propertyFlags;
int oldPropertyFlagsLength = encodedPropertyFlags.length();
bool successPropertyFlagsFits = packetData->appendValue(encodedPropertyFlags);
int propertyCount = 0;
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
&& successLastUpdatedFits && successPropertyFlagsFits;
int startOfEntityItemData = packetData->getUncompressedByteOffset();
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,
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, properties.getPosition());
APPEND_ENTITY_PROPERTY(PROP_RADIUS, appendValue, properties.getRadius());
APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, properties.getRotation());
APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, properties.getMass());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, properties.getVelocity());
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, properties.getGravity());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, properties.getDamping());
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, appendValue, properties.getLifetime());
//APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, properties.getScript()); // not supported by edit messages
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, properties.getModelURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, properties.getAnimationURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, properties.getAnimationFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, properties.getAnimationFrameIndex());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, properties.getAnimationIsPlaying());
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
encodedPropertyFlags = propertyFlags;
int newPropertyFlagsLength = encodedPropertyFlags.length();
packetData->updatePriorBytes(propertyFlagsOffset,
(const unsigned char*)encodedPropertyFlags.constData(), encodedPropertyFlags.length());
// if the size of the PropertyFlags shrunk, we need to shift everything down to front of packet.
if (newPropertyFlagsLength < oldPropertyFlagsLength) {
int oldSize = packetData->getUncompressedSize();
const unsigned char* modelItemData = packetData->getUncompressedData(propertyFlagsOffset + oldPropertyFlagsLength);
int modelItemDataLength = endOfEntityItemData - startOfEntityItemData;
int newEntityItemDataStart = propertyFlagsOffset + newPropertyFlagsLength;
packetData->updatePriorBytes(newEntityItemDataStart, modelItemData, modelItemDataLength);
int newSize = oldSize - (oldPropertyFlagsLength - newPropertyFlagsLength);
packetData->setUncompressedSize(newSize);
} else {
assert(newPropertyFlagsLength == oldPropertyFlagsLength); // should not have grown
}
packetData->endLevel(entityLevel);
} else {
packetData->discardLevel(entityLevel);
appendState = OctreeElement::NONE; // if we got here, then we didn't include the item
}
// If any part of the model items didn't fit, then the element is considered partial
if (appendState != OctreeElement::COMPLETED) {
// TODO: handle mechanism for handling partial fitting data!
// add this item into our list for the next appendElementData() pass
//modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit);
// for now, if it's not complete, it's not successful
success = false;
}
}
if (success) {
packetData->endSubTree();
const unsigned char* finalizedData = packetData->getFinalizedData();
int finalizedSize = packetData->getFinalizedSize();
memcpy(bufferOut, finalizedData, finalizedSize);
sizeOut = finalizedSize;
} else {
packetData->discardSubTree();
sizeOut = 0;
}
return success;
}
// TODO:
// how to handle lastEdited?
// how to handle lastUpdated?
// consider handling case where no properties are included... we should just ignore this packet...
//
// TODO: Right now, all possible properties for all subclasses are handled here. Ideally we'd prefer
// to handle this in a more generic way. Allowing subclasses of EntityItem to register their properties
//
// TODO: There's a lot of repeated patterns in the code below to handle each property. It would be nice if the property
// registration mechanism allowed us to collapse these repeated sections of code into a single implementation that
// utilized the registration table to shorten up and simplify this code.
//
// TODO: Implement support for paged properties, spanning MTU, and custom properties
//
// TODO: Implement support for script and visible properties.
//
bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes,
EntityItemID& entityID, EntityItemProperties& properties) {
bool valid = false;
const unsigned char* dataAt = data;
processedBytes = 0;
// the first part of the data is an octcode, this is a required element of the edit packet format, but we don't
// actually use it, we do need to skip it and read to the actual data we care about.
int octets = numberOfThreeBitSectionsInCode(data);
int bytesToReadOfOctcode = bytesRequiredForCodeLength(octets);
// we don't actually do anything with this octcode...
dataAt += bytesToReadOfOctcode;
processedBytes += bytesToReadOfOctcode;
// Edit packets have a last edited time stamp immediately following the octcode.
// NOTE: the edit times have been set by the editor to match out clock, so we don't need to adjust
// these times for clock skew at this point.
quint64 lastEdited;
memcpy(&lastEdited, dataAt, sizeof(lastEdited));
dataAt += sizeof(lastEdited);
processedBytes += sizeof(lastEdited);
properties.setLastEdited(lastEdited);
// NOTE: We intentionally do not send "created" times in edit messages. This is because:
// 1) if the edit is to an existing entity, the created time can not be changed
// 2) if the edit is to a new entity, the created time is the last edited time
// encoded id
QByteArray encodedID((const char*)dataAt, NUM_BYTES_RFC4122_UUID); // maximum possible size
QUuid editID = QUuid::fromRfc4122(encodedID);
dataAt += encodedID.size();
processedBytes += encodedID.size();
bool isNewEntityItem = (editID == NEW_ENTITY);
if (isNewEntityItem) {
// If this is a NEW_ENTITY, 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
QByteArray encodedToken((const char*)dataAt, (bytesToRead - processedBytes));
ByteCountCoded<quint32> tokenCoder = encodedToken;
quint32 creatorTokenID = tokenCoder;
encodedToken = tokenCoder; // determine true bytesToRead
dataAt += encodedToken.size();
processedBytes += encodedToken.size();
//newEntityItem.setCreatorTokenID(creatorTokenID);
//newEntityItem._newlyCreated = true;
entityID.id = NEW_ENTITY;
entityID.creatorTokenID = creatorTokenID;
entityID.isKnownID = false;
valid = true;
// created time is lastEdited time
properties.setCreated(lastEdited);
} else {
entityID.id = editID;
entityID.creatorTokenID = UNKNOWN_ENTITY_TOKEN;
entityID.isKnownID = true;
valid = true;
// created time is lastEdited time
properties.setCreated(USE_EXISTING_CREATED_TIME);
}
// Entity Type...
QByteArray encodedType((const char*)dataAt, (bytesToRead - processedBytes));
ByteCountCoded<quint32> typeCoder = encodedType;
quint32 entityTypeCode = typeCoder;
properties.setType((EntityTypes::EntityType)entityTypeCode);
encodedType = typeCoder; // determine true bytesToRead
dataAt += encodedType.size();
processedBytes += encodedType.size();
// Update Delta - when was this item updated relative to last edit... this really should be 0
// TODO: Should we get rid of this in this in edit packets, since this has to always be 0?
// TODO: do properties need to handle lastupdated???
// last updated is stored as ByteCountCoded delta from lastEdited
QByteArray encodedUpdateDelta((const char*)dataAt, (bytesToRead - processedBytes));
ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta;
encodedUpdateDelta = updateDeltaCoder; // determine true bytesToRead
dataAt += encodedUpdateDelta.size();
processedBytes += encodedUpdateDelta.size();
// TODO: Do we need this lastUpdated?? We don't seem to use it.
//quint64 updateDelta = updateDeltaCoder;
//quint64 lastUpdated = lastEdited + updateDelta; // don't adjust for clock skew since we already did that for lastEdited
// Property Flags...
QByteArray encodedPropertyFlags((const char*)dataAt, (bytesToRead - processedBytes));
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
dataAt += propertyFlags.getEncodedLength();
processedBytes += propertyFlags.getEncodedLength();
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS, float, setRadius);
READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(PROP_ROTATION, setRotation);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MASS, float, setMass);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime);
//READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript); // not yet supported by edit messages...
READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_COLOR, setColor);
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MODEL_URL, setModelURL);
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_URL, setAnimationURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FPS, float, setAnimationFPS);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_PLAYING, bool, setAnimationIsPlaying);
return valid;
}
// NOTE: This version will only encode the portion of the edit message immediately following the
// header it does not include the send times and sequence number because that is handled by the
// edit packet sender...
bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityItemID,
unsigned char* outputBuffer, size_t maxLength, size_t& outputLength) {
unsigned char* copyAt = outputBuffer;
uint16_t numberOfIds = 1; // only one entity ID in this message
memcpy(copyAt, &numberOfIds, sizeof(numberOfIds));
copyAt += sizeof(numberOfIds);
outputLength = sizeof(numberOfIds);
QUuid entityID = entityItemID.id;
QByteArray encodedEntityID = entityID.toRfc4122();
memcpy(copyAt, encodedEntityID.constData(), NUM_BYTES_RFC4122_UUID);
copyAt += NUM_BYTES_RFC4122_UUID;
outputLength += NUM_BYTES_RFC4122_UUID;
return true;
}
void EntityItemProperties::markAllChanged() {
_positionChanged = true;
_radiusChanged = true;
_rotationChanged = true;
_massChanged = true;
_velocityChanged = true;
_gravityChanged = true;
_dampingChanged = true;
_lifetimeChanged = true;
_scriptChanged = true;
_colorChanged = true;
_modelURLChanged = true;
_animationURLChanged = true;
_animationIsPlayingChanged = true;
_animationFrameIndexChanged = true;
_animationFPSChanged = true;
_glowLevelChanged = true;
}

View file

@ -0,0 +1,363 @@
//
// EntityItemProperties.h
// libraries/entities/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
//
#ifndef hifi_EntityItemProperties_h
#define hifi_EntityItemProperties_h
#include <stdint.h>
#include <glm/glm.hpp>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <QVector>
#include <QString>
#include <AACube.h>
#include <FBXReader.h> // for SittingPoint
#include <PropertyFlags.h>
#include <OctreeConstants.h>
#include "EntityItemID.h"
#include "EntityTypes.h"
// TODO: should these be static members of EntityItem or EntityItemProperties?
const float ENTITY_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
const float ENTITY_MINIMUM_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
const QString ENTITY_DEFAULT_MODEL_URL("");
const glm::quat ENTITY_DEFAULT_ROTATION;
const QString ENTITY_DEFAULT_ANIMATION_URL("");
const float ENTITY_DEFAULT_ANIMATION_FPS = 30.0f;
const quint64 UNKNOWN_CREATED_TIME = (quint64)(-1);
const quint64 USE_EXISTING_CREATED_TIME = (quint64)(-2);
// PropertyFlags support
enum EntityPropertyList {
PROP_PAGED_PROPERTY,
PROP_CUSTOM_PROPERTIES_INCLUDED,
// these properties are supported by the EntityItem base class
PROP_VISIBLE,
PROP_POSITION,
PROP_RADIUS,
PROP_ROTATION,
PROP_MASS,
PROP_VELOCITY,
PROP_GRAVITY,
PROP_DAMPING,
PROP_LIFETIME,
PROP_SCRIPT,
// these properties are supported by some derived classes
PROP_COLOR,
PROP_MODEL_URL,
PROP_ANIMATION_URL,
PROP_ANIMATION_FPS,
PROP_ANIMATION_FRAME_INDEX,
PROP_ANIMATION_PLAYING,
PROP_LAST_ITEM = PROP_ANIMATION_PLAYING
};
typedef PropertyFlags<EntityPropertyList> EntityPropertyFlags;
/// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an
/// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete
/// set of entity item properties via JavaScript hashes/QScriptValues
/// all units for position, radius, etc are in meter units
class EntityItemProperties {
friend class EntityItem; // TODO: consider removing this friend relationship and use public methods
friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods
friend class BoxEntityItem; // TODO: consider removing this friend relationship and use public methods
friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods
public:
EntityItemProperties();
virtual ~EntityItemProperties() { };
virtual QScriptValue copyToScriptValue(QScriptEngine* engine) const;
virtual void copyFromScriptValue(const QScriptValue& object);
// editing related features supported by all entities
quint64 getLastEdited() const { return _lastEdited; }
EntityPropertyFlags getChangedProperties() const;
/// used by EntityScriptingInterface to return EntityItemProperties for unknown models
void setIsUnknownID() { _id = UNKNOWN_ENTITY_ID; _idSet = true; }
glm::vec3 getMinimumPointMeters() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPointMeters() const { return _position + glm::vec3(_radius, _radius, _radius); }
AACube getAACubeMeters() const { return AACube(getMinimumPointMeters(), getMaxDimension()); } /// AACube in meter units
glm::vec3 getMinimumPointTreeUnits() const { return getMinimumPointMeters() / (float)TREE_SCALE; }
glm::vec3 getMaximumPointTreeUnits() const { return getMaximumPointMeters() / (float)TREE_SCALE; }
/// AACube in domain scale units (0.0 - 1.0)
AACube getAACubeTreeUnits() const {
return AACube(getMinimumPointMeters() / (float)TREE_SCALE, getMaxDimension() / (float)TREE_SCALE);
}
void debugDump() const;
// properties of all entities
EntityTypes::EntityType getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; }
float getRadius() const { return _radius; }
float getMaxDimension() const { return _radius * 2.0f; }
glm::vec3 getDimensions() const { return glm::vec3(_radius, _radius, _radius) * 2.0f; }
const glm::quat& getRotation() const { return _rotation; }
void setType(EntityTypes::EntityType type) { _type = type; }
/// set position in meter units, will be clamped to domain bounds
void setPosition(const glm::vec3& value) { _position = glm::clamp(value, 0.0f, (float)TREE_SCALE); _positionChanged = true; }
void setRadius(float value) { _radius = value; _radiusChanged = true; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; _rotationChanged = true; }
float getMass() const { return _mass; }
void setMass(float value) { _mass = value; _massChanged = true; }
/// velocity in domain scale units (0.0-1.0) per second
const glm::vec3& getVelocity() const { return _velocity; }
/// velocity in domain scale units (0.0-1.0) per second
void setVelocity(const glm::vec3& value) { _velocity = value; _velocityChanged = true; }
/// gravity in domain scale units (0.0-1.0) per second squared
const glm::vec3& getGravity() const { return _gravity; }
/// gravity in domain scale units (0.0-1.0) per second squared
void setGravity(const glm::vec3& value) { _gravity = value; _gravityChanged = true; }
float getDamping() const { return _damping; }
void setDamping(float value) { _damping = value; _dampingChanged = true; }
float getLifetime() const { return _lifetime; } /// get the lifetime in seconds for the entity
void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; } /// set the lifetime in seconds for the entity
float getAge() const { return (float)(usecTimestampNow() - _created) / (float)USECS_PER_SECOND; }
quint64 getCreated() const { return _created; }
void setCreated(quint64 usecTime) { _created = usecTime; }
bool hasCreatedTime() const { return (_created != UNKNOWN_CREATED_TIME); }
// NOTE: how do we handle _defaultSettings???
bool containsBoundsProperties() const { return (_positionChanged || _radiusChanged); }
bool containsPositionChange() const { return _positionChanged; }
bool containsRadiusChange() const { return _radiusChanged; }
// TODO: this need to be more generic. for now, we're going to have the properties class support these as
// named getter/setters, but we want to move them to generic types...
// properties we want to move to just models and particles
xColor getColor() const { return _color; }
const QString& getModelURL() const { return _modelURL; }
const QString& getAnimationURL() const { return _animationURL; }
float getAnimationFrameIndex() const { return _animationFrameIndex; }
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFPS() const { return _animationFPS; }
float getGlowLevel() const { return _glowLevel; }
// model related properties
void setColor(const xColor& value) { _color = value; _colorChanged = true; }
void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; }
void setAnimationURL(const QString& url) { _animationURL = url; _animationURLChanged = true; }
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; }
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; }
void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; }
void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; }
static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
unsigned char* bufferOut, int sizeIn, int& sizeOut);
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID,
unsigned char* outputBuffer, size_t maxLength, size_t& outputLength);
static bool decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes,
EntityItemID& entityID, EntityItemProperties& properties);
bool positionChanged() const { return _positionChanged; }
bool radiusChanged() const { return _radiusChanged; }
bool rotationChanged() const { return _rotationChanged; }
bool massChanged() const { return _massChanged; }
bool velocityChanged() const { return _velocityChanged; }
bool gravityChanged() const { return _gravityChanged; }
bool dampingChanged() const { return _dampingChanged; }
bool lifetimeChanged() const { return _lifetimeChanged; }
bool scriptChanged() const { return _scriptChanged; }
bool colorChanged() const { return _colorChanged; }
bool modelURLChanged() const { return _modelURLChanged; }
bool animationURLChanged() const { return _animationURLChanged; }
bool animationIsPlayingChanged() const { return _animationIsPlayingChanged; }
bool animationFrameIndexChanged() const { return _animationFrameIndexChanged; }
bool animationFPSChanged() const { return _animationFPSChanged; }
bool glowLevelChanged() const { return _glowLevelChanged; }
void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; }
void markAllChanged();
private:
void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; }
QUuid _id;
bool _idSet;
quint64 _lastEdited;
quint64 _created;
EntityTypes::EntityType _type;
glm::vec3 _position;
float _radius;
glm::quat _rotation;
float _mass;
glm::vec3 _velocity;
glm::vec3 _gravity;
float _damping;
float _lifetime;
QString _script;
bool _positionChanged;
bool _radiusChanged;
bool _rotationChanged;
bool _massChanged;
bool _velocityChanged;
bool _gravityChanged;
bool _dampingChanged;
bool _lifetimeChanged;
bool _scriptChanged;
// TODO: this need to be more generic. for now, we're going to have the properties class support these as
// named getter/setters, but we want to move them to generic types...
xColor _color;
QString _modelURL;
QString _animationURL;
bool _animationIsPlaying;
float _animationFrameIndex;
float _animationFPS;
float _glowLevel;
QVector<SittingPoint> _sittingPoints;
bool _colorChanged;
bool _modelURLChanged;
bool _animationURLChanged;
bool _animationIsPlayingChanged;
bool _animationFrameIndexChanged;
bool _animationFPSChanged;
bool _glowLevelChanged;
bool _defaultSettings;
};
Q_DECLARE_METATYPE(EntityItemProperties);
QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties);
void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties);
#define APPEND_ENTITY_PROPERTY(P,O,V) \
if (requestedProperties.getHasProperty(P)) { \
LevelDetails propertyLevel = packetData->startLevel(); \
successPropertyFits = packetData->O(V); \
if (successPropertyFits) { \
propertyFlags |= P; \
propertiesDidntFit -= P; \
propertyCount++; \
packetData->endLevel(propertyLevel); \
} else { \
packetData->discardLevel(propertyLevel); \
appendState = OctreeElement::PARTIAL; \
} \
} else { \
propertiesDidntFit -= P; \
}
#define READ_ENTITY_PROPERTY(P,T,M) \
if (propertyFlags.getHasProperty(P)) { \
T fromBuffer; \
memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \
dataAt += sizeof(fromBuffer); \
bytesRead += sizeof(fromBuffer); \
if (overwriteLocalData) { \
M = fromBuffer; \
} \
}
#define READ_ENTITY_PROPERTY_QUAT(P,M) \
if (propertyFlags.getHasProperty(P)) { \
glm::quat fromBuffer; \
int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \
dataAt += bytes; \
bytesRead += bytes; \
if (overwriteLocalData) { \
M = fromBuffer; \
} \
}
#define READ_ENTITY_PROPERTY_STRING(P,O) \
if (propertyFlags.getHasProperty(P)) { \
uint16_t length; \
memcpy(&length, dataAt, sizeof(length)); \
dataAt += sizeof(length); \
bytesRead += sizeof(length); \
QString value((const char*)dataAt); \
dataAt += length; \
bytesRead += length; \
if (overwriteLocalData) { \
O(value); \
} \
}
#define READ_ENTITY_PROPERTY_COLOR(P,M) \
if (propertyFlags.getHasProperty(P)) { \
if (overwriteLocalData) { \
memcpy(M, dataAt, sizeof(M)); \
} \
dataAt += sizeof(rgbColor); \
bytesRead += sizeof(rgbColor); \
}
#define READ_ENTITY_PROPERTY_TO_PROPERTIES(P,T,O) \
if (propertyFlags.getHasProperty(P)) { \
T fromBuffer; \
memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \
dataAt += sizeof(fromBuffer); \
processedBytes += sizeof(fromBuffer); \
properties.O(fromBuffer); \
}
#define READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(P,O) \
if (propertyFlags.getHasProperty(P)) { \
glm::quat fromBuffer; \
int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \
dataAt += bytes; \
processedBytes += bytes; \
properties.O(fromBuffer); \
}
#define READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(P,O) \
if (propertyFlags.getHasProperty(P)) { \
uint16_t length; \
memcpy(&length, dataAt, sizeof(length)); \
dataAt += sizeof(length); \
processedBytes += sizeof(length); \
QString value((const char*)dataAt); \
dataAt += length; \
processedBytes += length; \
properties.O(value); \
}
#define READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(P,O) \
if (propertyFlags.getHasProperty(P)) { \
xColor color; \
memcpy(&color, dataAt, sizeof(color)); \
dataAt += sizeof(color); \
processedBytes += sizeof(color); \
properties.O(color); \
}
#endif // hifi_EntityItemProperties_h

View file

@ -0,0 +1,293 @@
//
// EntityScriptingInterface.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/6/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 "EntityScriptingInterface.h"
#include "EntityTree.h"
#include "ModelEntityItem.h"
EntityScriptingInterface::EntityScriptingInterface() :
_nextCreatorTokenID(0),
_entityTree(NULL)
{
}
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
EntityItemID entityID, const EntityItemProperties& properties) {
getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);
}
EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
// The application will keep track of creatorTokenID
uint32_t creatorTokenID = EntityItemID::getNextCreatorTokenID();
EntityItemID id(NEW_ENTITY, creatorTokenID, false );
// queue the packet
queueEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
// If we have a local model tree set, then also update it.
if (_entityTree) {
_entityTree->lockForWrite();
_entityTree->addEntity(id, properties);
_entityTree->unlock();
}
return id;
}
EntityItemID EntityScriptingInterface::identifyEntity(EntityItemID entityID) {
EntityItemID actualID = entityID;
if (!entityID.isKnownID) {
actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID);
if (actualID == UNKNOWN_ENTITY_ID) {
return entityID; // bailing early
}
// found it!
entityID.id = actualID.id;
entityID.isKnownID = true;
}
return entityID;
}
EntityItemProperties EntityScriptingInterface::getEntityProperties(EntityItemID entityID) {
EntityItemProperties results;
EntityItemID identity = identifyEntity(entityID);
if (_entityTree) {
_entityTree->lockForRead();
EntityItem* entity = const_cast<EntityItem*>(_entityTree->findEntityByEntityItemID(identity));
if (entity) {
// TODO: improve sitting points in the future, for now we've included the old model behavior for entity
// types that are models
if (entity->getType() == EntityTypes::Model) {
ModelEntityItem* model = dynamic_cast<ModelEntityItem*>(entity);
const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity);
if (geometry) {
model->setSittingPoints(geometry->sittingPoints);
}
}
results = entity->getProperties();
} else {
results.setIsUnknownID();
}
_entityTree->unlock();
}
return results;
}
EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const EntityItemProperties& properties) {
EntityItemID actualID = entityID;
// if the model is unknown, attempt to look it up
if (!entityID.isKnownID) {
actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID);
}
// if at this point, we know the id, send the update to the model server
if (actualID.id != UNKNOWN_ENTITY_ID) {
entityID.id = actualID.id;
entityID.isKnownID = true;
queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties);
}
// If we have a local model tree set, then also update it. We can do this even if we don't know
// the actual id, because we can edit out local models just with creatorTokenID
if (_entityTree) {
_entityTree->lockForWrite();
_entityTree->updateEntity(entityID, properties);
_entityTree->unlock();
}
return entityID;
}
void EntityScriptingInterface::deleteEntity(EntityItemID entityID) {
EntityItemID actualID = entityID;
// if the model is unknown, attempt to look it up
if (!entityID.isKnownID) {
actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID);
}
// if at this point, we know the id, send the update to the model server
if (actualID.id != UNKNOWN_ENTITY_ID) {
entityID.id = actualID.id;
entityID.isKnownID = true;
getEntityPacketSender()->queueEraseEntityMessage(entityID);
}
// If we have a local model tree set, then also update it.
if (_entityTree) {
_entityTree->lockForWrite();
_entityTree->deleteEntity(entityID);
_entityTree->unlock();
}
}
EntityItemID EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const {
EntityItemID result(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false);
if (_entityTree) {
_entityTree->lockForRead();
const EntityItem* closestEntity = _entityTree->findClosestEntity(center/(float)TREE_SCALE,
radius/(float)TREE_SCALE);
_entityTree->unlock();
if (closestEntity) {
result.id = closestEntity->getID();
result.isKnownID = true;
}
}
return result;
}
void EntityScriptingInterface::dumpTree() const {
if (_entityTree) {
_entityTree->lockForRead();
_entityTree->dumpTree();
_entityTree->unlock();
}
}
QVector<EntityItemID> EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const {
QVector<EntityItemID> result;
if (_entityTree) {
_entityTree->lockForRead();
QVector<const EntityItem*> models;
_entityTree->findEntities(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, models);
_entityTree->unlock();
foreach (const EntityItem* model, models) {
EntityItemID thisEntityItemID(model->getID(), UNKNOWN_ENTITY_TOKEN, true);
result << thisEntityItemID;
}
}
return result;
}
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray) {
return findRayIntersectionWorker(ray, Octree::TryLock);
}
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray) {
return findRayIntersectionWorker(ray, Octree::Lock);
}
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray,
Octree::lockType lockType) {
RayToEntityIntersectionResult result;
if (_entityTree) {
OctreeElement* element;
EntityItem* intersectedEntity = NULL;
result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
(void**)&intersectedEntity, lockType, &result.accurate);
if (result.intersects && intersectedEntity) {
result.entityID = intersectedEntity->getEntityItemID();
result.properties = intersectedEntity->getProperties();
result.intersection = ray.origin + (ray.direction * result.distance);
}
}
return result;
}
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
intersects(false),
accurate(true), // assume it's accurate
entityID(),
properties(),
distance(0),
face()
{
}
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
obj.setProperty("accurate", value.accurate);
QScriptValue modelItemValue = EntityItemIDtoScriptValue(engine, value.entityID);
obj.setProperty("entityID", modelItemValue);
QScriptValue modelPropertiesValue = EntityItemPropertiesToScriptValue(engine, value.properties);
obj.setProperty("properties", modelPropertiesValue);
obj.setProperty("distance", value.distance);
QString faceName = "";
// handle BoxFace
switch (value.face) {
case MIN_X_FACE:
faceName = "MIN_X_FACE";
break;
case MAX_X_FACE:
faceName = "MAX_X_FACE";
break;
case MIN_Y_FACE:
faceName = "MIN_Y_FACE";
break;
case MAX_Y_FACE:
faceName = "MAX_Y_FACE";
break;
case MIN_Z_FACE:
faceName = "MIN_Z_FACE";
break;
case MAX_Z_FACE:
faceName = "MAX_Z_FACE";
break;
case UNKNOWN_FACE:
faceName = "UNKNOWN_FACE";
break;
}
obj.setProperty("face", faceName);
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
return obj;
}
void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) {
value.intersects = object.property("intersects").toVariant().toBool();
value.accurate = object.property("accurate").toVariant().toBool();
QScriptValue modelIDValue = object.property("entityID");
if (modelIDValue.isValid()) {
EntityItemIDfromScriptValue(modelIDValue, value.entityID);
}
QScriptValue modelPropertiesValue = object.property("properties");
if (modelPropertiesValue.isValid()) {
EntityItemPropertiesFromScriptValue(modelPropertiesValue, value.properties);
}
value.distance = object.property("distance").toVariant().toFloat();
QString faceName = object.property("face").toVariant().toString();
if (faceName == "MIN_X_FACE") {
value.face = MIN_X_FACE;
} else if (faceName == "MAX_X_FACE") {
value.face = MAX_X_FACE;
} else if (faceName == "MIN_Y_FACE") {
value.face = MIN_Y_FACE;
} else if (faceName == "MAX_Y_FACE") {
value.face = MAX_Y_FACE;
} else if (faceName == "MIN_Z_FACE") {
value.face = MIN_Z_FACE;
} else {
value.face = MAX_Z_FACE;
};
QScriptValue intersection = object.property("intersection");
if (intersection.isValid()) {
vec3FromScriptValue(intersection, value.intersection);
}
}

View file

@ -0,0 +1,110 @@
//
// EntityScriptingInterface.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/6/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
//
// TODO: How will we handle collision callbacks with Entities
//
#ifndef hifi_EntityScriptingInterface_h
#define hifi_EntityScriptingInterface_h
#include <QtCore/QObject>
#include <CollisionInfo.h>
#include <Octree.h>
#include <OctreeScriptingInterface.h>
#include <RegisteredMetaTypes.h>
#include "EntityEditPacketSender.h"
class EntityTree;
class RayToEntityIntersectionResult {
public:
RayToEntityIntersectionResult();
bool intersects;
bool accurate;
EntityItemID entityID;
EntityItemProperties properties;
float distance;
BoxFace face;
glm::vec3 intersection;
};
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results);
void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results);
/// handles scripting of Entity commands from JS passed to assigned clients
class EntityScriptingInterface : public OctreeScriptingInterface {
Q_OBJECT
public:
EntityScriptingInterface();
EntityEditPacketSender* getEntityPacketSender() const { return (EntityEditPacketSender*)getPacketSender(); }
virtual NodeType_t getServerNodeType() const { return NodeType::EntityServer; }
virtual OctreeEditPacketSender* createPacketSender() { return new EntityEditPacketSender(); }
void setEntityTree(EntityTree* modelTree) { _entityTree = modelTree; }
EntityTree* getEntityTree(EntityTree*) { return _entityTree; }
public slots:
/// adds a model with the specific properties
Q_INVOKABLE EntityItemID addEntity(const EntityItemProperties& properties);
/// identify a recently created model to determine its true ID
Q_INVOKABLE EntityItemID identifyEntity(EntityItemID entityID);
/// gets the current model properties for a specific model
/// this function will not find return results in script engine contexts which don't have access to models
Q_INVOKABLE EntityItemProperties getEntityProperties(EntityItemID entityID);
/// edits a model updating only the included properties, will return the identified EntityItemID in case of
/// successful edit, if the input entityID is for an unknown model this function will have no effect
Q_INVOKABLE EntityItemID editEntity(EntityItemID entityID, const EntityItemProperties& properties);
/// deletes a model
Q_INVOKABLE void deleteEntity(EntityItemID entityID);
/// finds the closest model to the center point, within the radius
/// will return a EntityItemID.isKnownID = false if no models are in the radius
/// this function will not find any models in script engine contexts which don't have access to models
Q_INVOKABLE EntityItemID findClosestEntity(const glm::vec3& center, float radius) const;
/// finds models within the search sphere specified by the center point and radius
/// this function will not find any models in script engine contexts which don't have access to models
Q_INVOKABLE QVector<EntityItemID> findEntities(const glm::vec3& center, float radius) const;
/// If the scripting context has visible voxels, this will determine a ray intersection, the results
/// may be inaccurate if the engine is unable to access the visible voxels, in which case result.accurate
/// will be false.
Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray);
/// If the scripting context has visible voxels, this will determine a ray intersection, and will block in
/// order to return an accurate result
Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray);
Q_INVOKABLE void dumpTree() const;
private:
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
/// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType);
uint32_t _nextCreatorTokenID;
EntityTree* _entityTree;
};
#endif // hifi_EntityScriptingInterface_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,165 @@
//
// EntityTree.h
// libraries/entities/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
//
#ifndef hifi_EntityTree_h
#define hifi_EntityTree_h
#include <Octree.h>
#include "EntityTreeElement.h"
class Model;
class NewlyCreatedEntityHook {
public:
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) = 0;
};
class EntityItemFBXService {
public:
virtual const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem) = 0;
virtual const Model* getModelForEntityItem(const EntityItem* entityItem) = 0;
};
class SendEntitiesOperationArgs {
public:
glm::vec3 root;
EntityTree* localTree;
EntityEditPacketSender* packetSender;
};
class EntityTree : public Octree {
Q_OBJECT
public:
EntityTree(bool shouldReaverage = false);
virtual ~EntityTree();
/// Implements our type specific root element factory
virtual EntityTreeElement* createNewElement(unsigned char * octalCode = NULL);
/// Type safe version of getRoot()
EntityTreeElement* getRoot() { return static_cast<EntityTreeElement*>(_rootElement); }
virtual void eraseAllOctreeElements(bool createNewRoot = true);
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return true; }
virtual PacketType expectedDataPacketType() const { return PacketTypeEntityData; }
virtual bool canProcessVersion(PacketVersion thisVersion) const { return true; } // we support all versions
virtual bool handlesEditPacketType(PacketType packetType) const;
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
virtual bool rootElementHasData() const { return true; }
// the root at least needs to store the number of entities in the packet/buffer
virtual int minimumRequiredRootDataBytes() const { return sizeof(uint16_t); }
virtual bool suppressEmptySubtrees() const { return false; }
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const;
virtual bool versionHasSVOfileBreaks(PacketVersion thisVersion) const
{ return thisVersion >= VERSION_ENTITIES_HAS_FILE_BREAKS; }
virtual void update();
// The newer API...
EntityItem* getOrCreateEntityItem(const EntityItemID& entityID, const EntityItemProperties& properties);
void addEntityItem(EntityItem* entityItem);
EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
void deleteEntity(const EntityItemID& entityID);
void deleteEntities(QSet<EntityItemID> entityIDs);
void removeEntityFromSimulationLists(const EntityItemID& entityID);
const EntityItem* findClosestEntity(glm::vec3 position, float targetRadius);
EntityItem* findEntityByID(const QUuid& id);
EntityItem* findEntityByEntityItemID(const EntityItemID& entityID);
EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID
/// finds all entities that touch a sphere
/// \param center the center of the sphere
/// \param radius the radius of the sphere
/// \param foundEntities[out] vector of const EntityItem*
/// \remark Side effect: any initial contents in foundEntities will be lost
void findEntities(const glm::vec3& center, float radius, QVector<const EntityItem*>& foundEntities);
/// finds all entities that touch a cube
/// \param cube the query cube
/// \param foundEntities[out] vector of non-const EntityItem*
/// \remark Side effect: any initial contents in entities will be lost
void findEntities(const AACube& cube, QVector<EntityItem*>& foundEntities);
void addNewlyCreatedHook(NewlyCreatedEntityHook* hook);
void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook);
bool hasAnyDeletedEntities() const { return _recentlyDeletedEntityItemIDs.size() > 0; }
bool hasEntitiesDeletedSince(quint64 sinceTime);
bool encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime,
unsigned char* packetData, size_t maxLength, size_t& outputLength);
void forgetEntitiesDeletedBefore(quint64 sinceTime);
int processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
int processEraseMessageDetails(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
void handleAddEntityResponse(const QByteArray& packet);
EntityItemFBXService* getFBXService() const { return _fbxService; }
void setFBXService(EntityItemFBXService* service) { _fbxService = service; }
const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem) {
return _fbxService ? _fbxService->getGeometryForEntity(entityItem) : NULL;
}
EntityTreeElement* getContainingElement(const EntityItemID& entityItemID) /*const*/;
void setContainingElement(const EntityItemID& entityItemID, EntityTreeElement* element);
void resetContainingElement(const EntityItemID& entityItemID, EntityTreeElement* element);
void debugDumpMap();
void dumpTree();
void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z);
void changeEntityState(EntityItem* const entity,
EntityItem::SimulationState oldState, EntityItem::SimulationState newState);
void trackDeletedEntity(const EntityItemID& entityID);
private:
void updateChangingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
void updateMovingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
void updateMortalEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool findInSphereOperation(OctreeElement* element, void* extraData);
static bool findInCubeOperation(OctreeElement* element, void* extraData);
static bool sendEntitiesOperation(OctreeElement* element, void* extraData);
void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode);
QReadWriteLock _newlyCreatedHooksLock;
QVector<NewlyCreatedEntityHook*> _newlyCreatedHooks;
QReadWriteLock _recentlyDeletedEntitiesLock;
QMultiMap<quint64, QUuid> _recentlyDeletedEntityItemIDs;
EntityItemFBXService* _fbxService;
QHash<EntityItemID, EntityTreeElement*> _entityToElementMap;
QList<EntityItem*> _movingEntities; // entities that are moving as part of update
QList<EntityItem*> _changingEntities; // entities that are changing (like animating), but not moving
QList<EntityItem*> _mortalEntities; // entities that are mortal (have lifetime), but not moving or changing
};
#endif // hifi_EntityTree_h

View file

@ -0,0 +1,820 @@
//
// EntityTreeElement.cpp
// libraries/entities/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 <glm/gtx/transform.hpp>
#include <FBXReader.h>
#include <GeometryUtil.h>
#include "EntityTree.h"
#include "EntityTreeElement.h"
EntityTreeElement::EntityTreeElement(unsigned char* octalCode) : OctreeElement(), _entityItems(NULL) {
init(octalCode);
};
EntityTreeElement::~EntityTreeElement() {
_voxelMemoryUsage -= sizeof(EntityTreeElement);
delete _entityItems;
_entityItems = NULL;
}
// This will be called primarily on addChildAt(), which means we're adding a child of our
// own type to our own tree. This means we should initialize that child with any tree and type
// specific settings that our children must have. One example is out VoxelSystem, which
// we know must match ours.
OctreeElement* EntityTreeElement::createNewElement(unsigned char* octalCode) {
EntityTreeElement* newChild = new EntityTreeElement(octalCode);
newChild->setTree(_myTree);
return newChild;
}
void EntityTreeElement::init(unsigned char* octalCode) {
OctreeElement::init(octalCode);
_entityItems = new QList<EntityItem*>;
_voxelMemoryUsage += sizeof(EntityTreeElement);
}
EntityTreeElement* EntityTreeElement::addChildAtIndex(int index) {
EntityTreeElement* newElement = (EntityTreeElement*)OctreeElement::addChildAtIndex(index);
newElement->setTree(_myTree);
return newElement;
}
void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) const {
qDebug() << "EntityTreeElement::debugExtraEncodeData()... ";
qDebug() << " element:" << getAACube();
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
qDebug() << " encode data:" << entityTreeElementExtraEncodeData;
} else {
qDebug() << " encode data: MISSING!!";
}
}
void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
// Check to see if this element yet has encode data... if it doesn't create it
if (!extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData();
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
EntityTreeElement* child = getChildAtIndex(i);
if (!child) {
entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed
} else {
if (child->hasEntities()) {
entityTreeElementExtraEncodeData->childCompleted[i] = false; // HAS ENTITIES NEEDS ENCODING
} else {
entityTreeElementExtraEncodeData->childCompleted[i] = true; // child doesn't have enities, it is completed
}
}
}
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItem* entity = (*_entityItems)[i];
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
}
// TODO: some of these inserts might be redundant!!!
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
}
}
bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex];
// If we haven't completely sent the child yet, then we should include it
return !childCompleted;
}
// I'm not sure this should ever happen, since we should have the extra encode data if we're considering
// the child data for this element
assert(false);
return false;
}
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
EntityTreeElement* childElement = getChildAtIndex(childIndex);
if (childElement->alreadyFullyEncoded(params)) {
return false;
}
return true; // if we don't know otherwise than recurse!
}
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
// If we know that ALL subtrees below us have already been recursed, then we don't
// need to recurse this child.
return entityTreeElementExtraEncodeData->subtreeCompleted;
}
return false;
}
void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
if (childAppendState == OctreeElement::COMPLETED) {
entityTreeElementExtraEncodeData->childCompleted[childIndex] = true;
}
} else {
assert(false); // this shouldn't happen!
}
}
void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, OctreeElementBag* bag) const {
const bool wantDebug = false;
if (wantDebug) {
qDebug() << "EntityTreeElement::elementEncodeComplete() element:" << getAACube();
}
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
assert(extraEncodeData->contains(this));
EntityTreeElementExtraEncodeData* thisExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
// Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion()
// which means, it's possible that our parent element hasn't finished encoding OUR data... so
// in this case, our children may be complete, and we should clean up their encode data...
// but not necessarily cleanup our own encode data...
//
// If we're really complete here's what must be true...
// 1) out own data must be complete
// 2) the data for all our immediate children must be complete.
// However, the following might also be the case...
// 1) it's ok for our child trees to not yet be fully encoded/complete...
// SO LONG AS... the our child's node is in the bag ready for encoding
bool someChildTreeNotComplete = false;
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
EntityTreeElement* childElement = getChildAtIndex(i);
if (childElement) {
// why would this ever fail???
// If we've encoding this element before... but we're coming back a second time in an attempt to
// encoud our parent... this might happen.
if (extraEncodeData->contains(childElement)) {
EntityTreeElementExtraEncodeData* childExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(childElement));
if (wantDebug) {
qDebug() << "checking child: " << childElement->getAACube();
qDebug() << " childElement->isLeaf():" << childElement->isLeaf();
qDebug() << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted;
qDebug() << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted;
}
if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) {
if (wantDebug) {
qDebug() << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!";
}
childExtraEncodeData->subtreeCompleted = true;
}
if (!childExtraEncodeData->elementCompleted || !childExtraEncodeData->subtreeCompleted) {
someChildTreeNotComplete = true;
}
}
}
}
if (wantDebug) {
qDebug() << "for this element: " << getAACube();
qDebug() << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted;
qDebug() << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
}
thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete;
if (wantDebug) {
qDebug() << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted;
qDebug() << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
if (thisExtraEncodeData->subtreeCompleted) {
qDebug() << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
}
}
}
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
EncodeBitstreamParams& params) const {
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
// first, check the params.extraEncodeData to see if there's any partial re-encode data for this element
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL;
bool hadElementExtraData = false;
if (extraEncodeData && extraEncodeData->contains(this)) {
entityTreeElementExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
hadElementExtraData = true;
} else {
// if there wasn't one already, then create one
entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData();
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
EntityTreeElement* child = getChildAtIndex(i);
if (!child) {
entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed
} else {
if (child->hasEntities()) {
entityTreeElementExtraEncodeData->childCompleted[i] = false;
} else {
entityTreeElementExtraEncodeData->childCompleted[i] = true; // if the child doesn't have enities, it is completed
}
}
}
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItem* entity = (*_entityItems)[i];
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
}
}
//assert(extraEncodeData);
//assert(extraEncodeData->contains(this));
//entityTreeElementExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
LevelDetails elementLevel = packetData->startLevel();
// write our entities out... first determine which of the entities are in view based on our params
uint16_t numberOfEntities = 0;
uint16_t actualNumberOfEntities = 0;
QVector<uint16_t> indexesOfEntitiesToInclude;
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
// need to handle the case where our sibling elements need encoding but we don't.
if (!entityTreeElementExtraEncodeData->elementCompleted) {
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItem* entity = (*_entityItems)[i];
bool includeThisEntity = true;
if (!params.forceSendScene && entity->getLastEdited() < params.lastViewFrustumSent) {
includeThisEntity = false;
}
if (hadElementExtraData) {
includeThisEntity = includeThisEntity &&
entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID());
}
if (includeThisEntity && params.viewFrustum) {
AACube entityCube = entity->getAACube();
entityCube.scale(TREE_SCALE);
if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
includeThisEntity = false; // out of view, don't include it
}
}
if (includeThisEntity) {
indexesOfEntitiesToInclude << i;
numberOfEntities++;
}
}
}
int numberOfEntitiesOffset = packetData->getUncompressedByteOffset();
bool successAppendEntityCount = packetData->appendValue(numberOfEntities);
if (successAppendEntityCount) {
foreach (uint16_t i, indexesOfEntitiesToInclude) {
EntityItem* entity = (*_entityItems)[i];
LevelDetails entityLevel = packetData->startLevel();
OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData,
params, entityTreeElementExtraEncodeData);
// If none of this entity data was able to be appended, then discard it
// and don't include it in our entity count
if (appendEntityState == OctreeElement::NONE) {
packetData->discardLevel(entityLevel);
} else {
// If either ALL or some of it got appended, then end the level (commit it)
// and include the entity in our final count of entities
packetData->endLevel(entityLevel);
actualNumberOfEntities++;
}
// If the entity item got completely appended, then we can remove it from the extra encode data
if (appendEntityState == OctreeElement::COMPLETED) {
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
}
// If any part of the entity items didn't fit, then the element is considered partial
// NOTE: if the entity item didn't fit or only partially fit, then the entity item should have
// added itself to the extra encode data.
if (appendEntityState != OctreeElement::COMPLETED) {
appendElementState = OctreeElement::PARTIAL;
}
}
} else {
// we we couldn't add the entity count, then we couldn't add anything for this element and we're in a NONE state
appendElementState = OctreeElement::NONE;
}
// If we were provided with extraEncodeData, and we allocated and/or got entityTreeElementExtraEncodeData
// then we need to do some additional processing, namely make sure our extraEncodeData is up to date for
// this octree element.
if (extraEncodeData && entityTreeElementExtraEncodeData) {
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
// Only our patent can remove our extra data in these cases and only after it knows that all of it's
// children have been encoded.
// If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data
// since that will signal that the entire element needs to be encoded on the next attempt
if (appendElementState == OctreeElement::NONE) {
if (!entityTreeElementExtraEncodeData->elementCompleted && entityTreeElementExtraEncodeData->entities.size() == 0) {
// TODO: we used to delete the extra encode data here. But changing the logic around
// this is now a dead code branch. Clean this up!
} else {
// TODO: some of these inserts might be redundant!!!
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
}
} else {
// If we weren't previously completed, check to see if we are
if (!entityTreeElementExtraEncodeData->elementCompleted) {
// If all of our items have been encoded, then we are complete as an element.
if (entityTreeElementExtraEncodeData->entities.size() == 0) {
entityTreeElementExtraEncodeData->elementCompleted = true;
}
}
// TODO: some of these inserts might be redundant!!!
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
}
}
// Determine if no entities at all were able to fit
bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0);
// If we wrote fewer entities than we expected, update the number of entities in our packet
bool successUpdateEntityCount = true;
if (!noEntitiesFit && numberOfEntities != actualNumberOfEntities) {
successUpdateEntityCount = packetData->updatePriorBytes(numberOfEntitiesOffset,
(const unsigned char*)&actualNumberOfEntities, sizeof(actualNumberOfEntities));
}
// If we weren't able to update our entity count, or we couldn't fit any entities, then
// we should discard our element and return a result of NONE
if (!successUpdateEntityCount || noEntitiesFit) {
packetData->discardLevel(elementLevel);
appendElementState = OctreeElement::NONE;
} else {
packetData->endLevel(elementLevel);
}
return appendElementState;
}
bool EntityTreeElement::containsEntityBounds(const EntityItem* entity) const {
return containsBounds(entity->getMinimumPoint(), entity->getMaximumPoint());
}
bool EntityTreeElement::bestFitEntityBounds(const EntityItem* entity) const {
return bestFitBounds(entity->getMinimumPoint(), entity->getMaximumPoint());
}
bool EntityTreeElement::containsBounds(const EntityItemProperties& properties) const {
return containsBounds(properties.getMinimumPointTreeUnits(), properties.getMaximumPointTreeUnits());
}
bool EntityTreeElement::bestFitBounds(const EntityItemProperties& properties) const {
return bestFitBounds(properties.getMinimumPointTreeUnits(), properties.getMaximumPointTreeUnits());
}
bool EntityTreeElement::containsBounds(const AACube& bounds) const {
return containsBounds(bounds.getMinimumPoint(), bounds.getMaximumPoint());
}
bool EntityTreeElement::bestFitBounds(const AACube& bounds) const {
return bestFitBounds(bounds.getMinimumPoint(), bounds.getMaximumPoint());
}
bool EntityTreeElement::containsBounds(const AABox& bounds) const {
return containsBounds(bounds.getMinimumPoint(), bounds.getMaximumPoint());
}
bool EntityTreeElement::bestFitBounds(const AABox& bounds) const {
return bestFitBounds(bounds.getMinimumPoint(), bounds.getMaximumPoint());
}
bool EntityTreeElement::containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const {
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f);
return _cube.contains(clampedMin) && _cube.contains(clampedMax);
}
bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const {
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f);
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {
// If our child would be smaller than our smallest reasonable element, then we are the best fit.
float childScale = _cube.getScale() / 2.0f;
if (childScale <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
return true;
}
int childForMinimumPoint = getMyChildContainingPoint(clampedMin);
int childForMaximumPoint = getMyChildContainingPoint(clampedMax);
// If I contain both the minimum and maximum point, but two different children of mine
// contain those points, then I am the best fit for that entity
if (childForMinimumPoint != childForMaximumPoint) {
return true;
}
}
return false;
}
bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject) {
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
QList<EntityItem*>::iterator entityItr = _entityItems->begin();
QList<EntityItem*>::const_iterator entityEnd = _entityItems->end();
bool somethingIntersected = false;
while(entityItr != entityEnd) {
EntityItem* entity = (*entityItr);
AACube entityCube = entity->getAACube();
float localDistance;
BoxFace localFace;
// if the ray doesn't intersect with our cube, we can stop searching!
if (entityCube.findRayIntersection(origin, direction, localDistance, localFace)) {
const FBXGeometry* fbxGeometry = _myTree->getGeometryForEntity(entity);
if (fbxGeometry && fbxGeometry->meshExtents.isValid()) {
Extents extents = fbxGeometry->meshExtents;
// NOTE: If the entity has a bad mesh, then extents will be 0,0,0 & 0,0,0
if (extents.minimum == extents.maximum && extents.minimum == glm::vec3(0,0,0)) {
extents.maximum = glm::vec3(1.0f,1.0f,1.0f); // in this case we will simulate the unit cube
}
// NOTE: these extents are entity space, so we need to scale and center them accordingly
// size is our "target size in world space"
// we need to set our entity scale so that the extents of the mesh, fit in a cube that size...
float maxDimension = glm::distance(extents.maximum, extents.minimum);
float scale = entity->getSize() / maxDimension;
glm::vec3 halfDimensions = (extents.maximum - extents.minimum) * 0.5f;
glm::vec3 offset = -extents.minimum - halfDimensions;
extents.minimum += offset;
extents.maximum += offset;
extents.minimum *= scale;
extents.maximum *= scale;
Extents rotatedExtents = extents;
calculateRotatedExtents(rotatedExtents, entity->getRotation());
rotatedExtents.minimum += entity->getPosition();
rotatedExtents.maximum += entity->getPosition();
AABox rotatedExtentsBox(rotatedExtents.minimum, (rotatedExtents.maximum - rotatedExtents.minimum));
// if it's in our AABOX for our rotated extents, then check to see if it's in our non-AABox
if (rotatedExtentsBox.findRayIntersection(origin, direction, localDistance, localFace)) {
// extents is the entity relative, scaled, centered extents of the entity
glm::mat4 rotation = glm::mat4_cast(entity->getRotation());
glm::mat4 translation = glm::translate(entity->getPosition());
glm::mat4 entityToWorldMatrix = translation * rotation;
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
AABox entityFrameBox(extents.minimum, (extents.maximum - extents.minimum));
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f));
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace)) {
if (localDistance < distance) {
distance = localDistance;
face = localFace;
*intersectedObject = (void*)entity;
somethingIntersected = true;
}
}
}
} else if (localDistance < distance) {
distance = localDistance;
face = localFace;
*intersectedObject = (void*)entity;
somethingIntersected = true;
}
}
++entityItr;
}
return somethingIntersected;
}
bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const {
QList<EntityItem*>::iterator entityItr = _entityItems->begin();
QList<EntityItem*>::const_iterator entityEnd = _entityItems->end();
while(entityItr != entityEnd) {
EntityItem* entity = (*entityItr);
glm::vec3 entityCenter = entity->getPosition();
float entityRadius = entity->getRadius();
// don't penetrate yourself
if (entityCenter == center && entityRadius == radius) {
return false;
}
if (findSphereSpherePenetration(center, radius, entityCenter, entityRadius, penetration)) {
// return true on first valid entity penetration
*penetratedObject = (void*)(entity);
return true;
}
++entityItr;
}
return false;
}
void EntityTreeElement::updateEntityItemID(const EntityItemID& creatorTokenEntityID, const EntityItemID& knownIDEntityID) {
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItem* thisEntity = (*_entityItems)[i];
EntityItemID thisEntityID = thisEntity->getEntityItemID();
if (thisEntityID == creatorTokenEntityID) {
thisEntity->setID(knownIDEntityID.id);
}
}
}
const EntityItem* EntityTreeElement::getClosestEntity(glm::vec3 position) const {
const EntityItem* closestEntity = NULL;
float closestEntityDistance = FLT_MAX;
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
float distanceToEntity = glm::distance(position, (*_entityItems)[i]->getPosition());
if (distanceToEntity < closestEntityDistance) {
closestEntity = (*_entityItems)[i];
}
}
return closestEntity;
}
void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector<const EntityItem*>& foundEntities) const {
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
const EntityItem* entity = (*_entityItems)[i];
float distance = glm::length(entity->getPosition() - searchPosition);
if (distance < searchRadius + entity->getRadius()) {
foundEntities.push_back(entity);
}
}
}
void EntityTreeElement::getEntities(const AACube& box, QVector<EntityItem*>& foundEntities) {
QList<EntityItem*>::iterator entityItr = _entityItems->begin();
QList<EntityItem*>::iterator entityEnd = _entityItems->end();
AACube entityCube;
while(entityItr != entityEnd) {
EntityItem* entity = (*entityItr);
float radius = entity->getRadius();
// NOTE: we actually do cube-cube collision queries here, which is sloppy but good enough for now
// TODO: decide whether to replace entityCube-cube query with sphere-cube (requires a square root
// but will be slightly more accurate).
entityCube.setBox(entity->getPosition() - glm::vec3(radius), 2.f * radius);
if (entityCube.touches(box)) {
foundEntities.push_back(entity);
}
++entityItr;
}
}
const EntityItem* EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const {
const EntityItem* foundEntity = NULL;
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
if ((*_entityItems)[i]->getEntityItemID() == id) {
foundEntity = (*_entityItems)[i];
break;
}
}
return foundEntity;
}
EntityItem* EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) {
EntityItem* foundEntity = NULL;
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
if ((*_entityItems)[i]->getEntityItemID() == id) {
foundEntity = (*_entityItems)[i];
break;
}
}
return foundEntity;
}
void EntityTreeElement::cleanupEntities() {
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItem* entity = (*_entityItems)[i];
delete entity;
}
_entityItems->clear();
}
bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) {
bool foundEntity = false;
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
if ((*_entityItems)[i]->getEntityItemID() == id) {
foundEntity = true;
_entityItems->removeAt(i);
break;
}
}
return foundEntity;
}
bool EntityTreeElement::removeEntityItem(EntityItem* entity) {
return _entityItems->removeAll(entity) > 0;
}
// Things we want to accomplish as we read these entities from the data buffer.
//
// 1) correctly update the properties of the entity
// 2) add any new entities that didn't previously exist
//
// TODO: Do we also need to do this?
// 3) mark our tree as dirty down to the path of the previous location of the entity
// 4) mark our tree as dirty down to the path of the new location of the entity
//
// Since we're potentially reading several entities, we'd prefer to do all the moving around
// and dirty path marking in one pass.
int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args) {
// If we're the root, but this bitstream doesn't support root elements with data, then
// return without reading any bytes
if (this == _myTree->getRoot() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
return 0;
}
const unsigned char* dataAt = data;
int bytesRead = 0;
uint16_t numberOfEntities = 0;
int expectedBytesPerEntity = EntityItem::expectedBytes();
if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) {
// read our entities in....
numberOfEntities = *(uint16_t*)dataAt;
dataAt += sizeof(numberOfEntities);
bytesLeftToRead -= (int)sizeof(numberOfEntities);
bytesRead += sizeof(numberOfEntities);
if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) {
for (uint16_t i = 0; i < numberOfEntities; i++) {
int bytesForThisEntity = 0;
EntityItemID entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead);
EntityItem* entityItem = _myTree->findEntityByEntityItemID(entityItemID);
bool newEntity = false;
// If the item already exists in our tree, we want do the following...
// 1) allow the existing item to read from the databuffer
// 2) check to see if after reading the item, the containing element is still correct, fix it if needed
//
// TODO: Do we need to also do this?
// 3) remember the old cube for the entity so we can mark it as dirty
if (entityItem) {
bool bestFitBefore = bestFitEntityBounds(entityItem);
EntityTreeElement* currentContainingElement = _myTree->getContainingElement(entityItemID);
EntityItem::SimulationState oldState = entityItem->getSimulationState();
bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
EntityItem::SimulationState newState = entityItem->getSimulationState();
if (oldState != newState) {
_myTree->changeEntityState(entityItem, oldState, newState);
}
bool bestFitAfter = bestFitEntityBounds(entityItem);
if (bestFitBefore != bestFitAfter) {
// This is the case where the entity existed, and is in some element in our tree...
if (!bestFitBefore && bestFitAfter) {
// This is the case where the entity existed, and is in some element in our tree...
if (currentContainingElement != this) {
currentContainingElement->removeEntityItem(entityItem);
addEntityItem(entityItem);
_myTree->setContainingElement(entityItemID, this);
}
}
}
} else {
entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args);
if (entityItem) {
bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
addEntityItem(entityItem); // add this new entity to this elements entities
_myTree->setContainingElement(entityItemID, this);
newEntity = true;
EntityItem::SimulationState newState = entityItem->getSimulationState();
_myTree->changeEntityState(entityItem, EntityItem::Static, newState);
}
}
// Move the buffer forward to read more entities
dataAt += bytesForThisEntity;
bytesLeftToRead -= bytesForThisEntity;
bytesRead += bytesForThisEntity;
}
}
}
return bytesRead;
}
void EntityTreeElement::addEntityItem(EntityItem* entity) {
_entityItems->push_back(entity);
}
// will average a "common reduced LOD view" from the the child elements...
void EntityTreeElement::calculateAverageFromChildren() {
// nothing to do here yet...
}
// will detect if children are leaves AND collapsable into the parent node
// and in that case will collapse children and make this node
// a leaf, returns TRUE if all the leaves are collapsed into a
// single node
bool EntityTreeElement::collapseChildren() {
// nothing to do here yet...
return false;
}
bool EntityTreeElement::pruneChildren() {
bool somethingPruned = false;
for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) {
EntityTreeElement* child = getChildAtIndex(childIndex);
// if my child is a leaf, but has no entities, then it's safe to delete my child
if (child && child->isLeaf() && !child->hasEntities()) {
deleteChildAtIndex(childIndex);
somethingPruned = true;
}
}
return somethingPruned;
}
void EntityTreeElement::debugDump() {
qDebug() << "EntityTreeElement...";
qDebug() << "entity count:" << _entityItems->size();
qDebug() << "cube:" << getAACube();
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItem* entity = (*_entityItems)[i];
entity->debugDump();
}
}

View file

@ -0,0 +1,205 @@
//
// EntityTreeElement.h
// libraries/entities/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
//
#ifndef hifi_EntityTreeElement_h
#define hifi_EntityTreeElement_h
#include <OctreeElement.h>
#include <QList>
#include "EntityEditPacketSender.h"
#include "EntityItem.h"
#include "EntityTree.h"
class EntityTree;
class EntityTreeElement;
class EntityTreeUpdateArgs {
public:
EntityTreeUpdateArgs() :
_totalElements(0),
_totalItems(0),
_movingItems(0)
{ }
QList<EntityItem*> _movingEntities;
int _totalElements;
int _totalItems;
int _movingItems;
};
class EntityTreeElementExtraEncodeData {
public:
EntityTreeElementExtraEncodeData() :
elementCompleted(false),
subtreeCompleted(false),
entities() {
memset(childCompleted, 0, sizeof(childCompleted));
}
bool elementCompleted;
bool subtreeCompleted;
bool childCompleted[NUMBER_OF_CHILDREN];
QMap<EntityItemID, EntityPropertyFlags> entities;
};
inline QDebug operator<<(QDebug debug, const EntityTreeElementExtraEncodeData* data) {
debug << "{";
debug << " elementCompleted: " << data->elementCompleted << ", ";
debug << " subtreeCompleted: " << data->subtreeCompleted << ", ";
debug << " childCompleted[]: ";
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
debug << " " << i << ":" << data->childCompleted[i] << ", ";
}
debug << " entities.size: " << data->entities.size() << "}";
return debug;
}
class SendModelsOperationArgs {
public:
glm::vec3 root;
EntityEditPacketSender* packetSender;
};
class EntityTreeElement : public OctreeElement {
friend class EntityTree; // to allow createElement to new us...
EntityTreeElement(unsigned char* octalCode = NULL);
virtual OctreeElement* createNewElement(unsigned char* octalCode = NULL);
public:
virtual ~EntityTreeElement();
// type safe versions of OctreeElement methods
EntityTreeElement* getChildAtIndex(int index) const { return (EntityTreeElement*)OctreeElement::getChildAtIndex(index); }
// methods you can and should override to implement your tree functionality
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
virtual EntityTreeElement* addChildAtIndex(int index);
/// Override this to implement LOD averaging on changes to the tree.
virtual void calculateAverageFromChildren();
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
virtual bool collapseChildren();
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
virtual bool hasContent() const { return hasEntities(); }
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
virtual bool hasDetailedContent() const { return hasEntities(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
virtual void splitChildren() { }
/// Override to indicate that this element requires a split before editing lower elements in the octree
virtual bool requiresSplit() const { return false; }
virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const;
virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) const;
virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const;
virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const;
virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const;
virtual void elementEncodeComplete(EncodeBitstreamParams& params, OctreeElementBag* bag) const;
bool alreadyFullyEncoded(EncodeBitstreamParams& params) const;
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual OctreeElement::AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
/// the element should be rendered, then your rendering engine is rendering. But some rendering engines my have cases
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return !hasEntities(); }
virtual bool canRayIntersect() const { return hasEntities(); }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject);
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;
const QList<EntityItem*>& getEntities() const { return *_entityItems; }
QList<EntityItem*>& getEntities() { return *_entityItems; }
bool hasEntities() const { return _entityItems ? _entityItems->size() > 0 : false; }
void setTree(EntityTree* tree) { _myTree = tree; }
bool updateEntity(const EntityItem& entity);
void addEntityItem(EntityItem* entity);
void updateEntityItemID(const EntityItemID& creatorTokenEntityID, const EntityItemID& knownIDEntityID);
const EntityItem* getClosestEntity(glm::vec3 position) const;
/// finds all entities that touch a sphere
/// \param position the center of the query sphere
/// \param radius the radius of the query sphere
/// \param entities[out] vector of const EntityItem*
void getEntities(const glm::vec3& position, float radius, QVector<const EntityItem*>& foundEntities) const;
/// finds all entities that touch a box
/// \param box the query box
/// \param entities[out] vector of non-const EntityItem*
void getEntities(const AACube& box, QVector<EntityItem*>& foundEntities);
const EntityItem* getEntityWithID(uint32_t id) const;
const EntityItem* getEntityWithEntityItemID(const EntityItemID& id) const;
void getEntitiesInside(const AACube& box, QVector<EntityItem*>& foundEntities);
EntityItem* getEntityWithEntityItemID(const EntityItemID& id);
void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities
bool removeEntityWithEntityItemID(const EntityItemID& id);
bool removeEntityItem(EntityItem* entity);
bool containsEntityBounds(const EntityItem* entity) const;
bool bestFitEntityBounds(const EntityItem* entity) const;
bool containsBounds(const EntityItemProperties& properties) const; // NOTE: property units in meters
bool bestFitBounds(const EntityItemProperties& properties) const; // NOTE: property units in meters
bool containsBounds(const AACube& bounds) const; // NOTE: units in tree units
bool bestFitBounds(const AACube& bounds) const; // NOTE: units in tree units
bool containsBounds(const AABox& bounds) const; // NOTE: units in tree units
bool bestFitBounds(const AABox& bounds) const; // NOTE: units in tree units
bool containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const; // NOTE: units in tree units
bool bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const; // NOTE: units in tree units
void debugDump();
bool pruneChildren();
protected:
virtual void init(unsigned char * octalCode);
EntityTree* _myTree;
QList<EntityItem*>* _entityItems;
};
#endif // hifi_EntityTreeElement_h

View file

@ -0,0 +1,38 @@
//
// EntityTreeHeadlessViewer.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 2/26/14.
// Copyright 2014 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 "EntityTreeHeadlessViewer.h"
EntityTreeHeadlessViewer::EntityTreeHeadlessViewer() :
OctreeHeadlessViewer() {
}
EntityTreeHeadlessViewer::~EntityTreeHeadlessViewer() {
}
void EntityTreeHeadlessViewer::init() {
OctreeHeadlessViewer::init();
}
void EntityTreeHeadlessViewer::update() {
if (_tree) {
EntityTree* tree = static_cast<EntityTree*>(_tree);
if (tree->tryLockForWrite()) {
tree->update();
tree->unlock();
}
}
}
void EntityTreeHeadlessViewer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
static_cast<EntityTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
}

View file

@ -1,5 +1,5 @@
//
// ModelTreeHeadlessViewer.h
// EntityTreeHeadlessViewer.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 2/26/14.
@ -9,8 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelTreeHeadlessViewer_h
#define hifi_ModelTreeHeadlessViewer_h
#ifndef hifi_EntityTreeHeadlessViewer_h
#define hifi_EntityTreeHeadlessViewer_h
#include <PacketHeaders.h>
#include <SharedUtil.h>
@ -19,27 +19,27 @@
#include <OctreeHeadlessViewer.h>
#include <ViewFrustum.h>
#include "ModelTree.h"
#include "EntityTree.h"
// Generic client side Octree renderer class.
class ModelTreeHeadlessViewer : public OctreeHeadlessViewer {
class EntityTreeHeadlessViewer : public OctreeHeadlessViewer {
Q_OBJECT
public:
ModelTreeHeadlessViewer();
virtual ~ModelTreeHeadlessViewer();
EntityTreeHeadlessViewer();
virtual ~EntityTreeHeadlessViewer();
virtual Octree* createTree() { return new ModelTree(true); }
virtual char getMyNodeType() const { return NodeType::ModelServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; }
virtual Octree* createTree() { return new EntityTree(true); }
virtual char getMyNodeType() const { return NodeType::EntityServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; }
virtual PacketType getExpectedPacketType() const { return PacketTypeEntityData; }
void update();
ModelTree* getTree() { return (ModelTree*)_tree; }
EntityTree* getTree() { return (EntityTree*)_tree; }
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
};
#endif // hifi_ModelTreeHeadlessViewer_h
#endif // hifi_EntityTreeHeadlessViewer_h

View file

@ -0,0 +1,129 @@
//
// EntityTypes.cpp
// libraries/entities/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 <ByteCountCoding.h>
#include <Octree.h>
#include "EntityItem.h"
#include "EntityItemProperties.h"
#include "EntityTypes.h"
#include "BoxEntityItem.h"
#include "ModelEntityItem.h"
#include "SphereEntityItem.h"
QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap;
QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap;
EntityTypeFactory EntityTypes::_factories[EntityTypes::LAST];
bool EntityTypes::_factoriesInitialized = false;
const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown";
// Register Entity the default implementations of entity types here...
REGISTER_ENTITY_TYPE(Model)
REGISTER_ENTITY_TYPE(Box)
REGISTER_ENTITY_TYPE(Sphere)
const QString& EntityTypes::getEntityTypeName(EntityType entityType) {
QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType);
if (matchedTypeName != _typeToNameMap.end()) {
return matchedTypeName.value();
}
return ENTITY_TYPE_NAME_UNKNOWN;
}
EntityTypes::EntityType EntityTypes::getEntityTypeFromName(const QString& name) {
QMap<QString, EntityTypes::EntityType>::iterator matchedTypeName = _nameToTypeMap.find(name);
if (matchedTypeName != _nameToTypeMap.end()) {
return matchedTypeName.value();
}
return Unknown;
}
bool EntityTypes::registerEntityType(EntityType entityType, const char* name, EntityTypeFactory factoryMethod) {
_typeToNameMap[entityType] = name;
_nameToTypeMap[name] = entityType;
if (!_factoriesInitialized) {
memset(&_factories,0,sizeof(_factories));
_factoriesInitialized = true;
}
_factories[entityType] = factoryMethod;
return true;
}
EntityItem* EntityTypes::constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItem* newEntityItem = NULL;
EntityTypeFactory factory = NULL;
if (entityType >= 0 && entityType <= LAST) {
factory = _factories[entityType];
}
if (factory) {
// NOTE: if someone attempts to create an entity with properties that do not include a proper "created" time
// then set the created time to now
if (!properties.hasCreatedTime()) {
EntityItemProperties mutableProperties = properties;
mutableProperties.setCreated(usecTimestampNow());
newEntityItem = factory(entityID, mutableProperties);
} else {
newEntityItem = factory(entityID, properties);
}
}
return newEntityItem;
}
EntityItem* EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead,
ReadBitstreamToTreeParams& args) {
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
EntityItemID tempEntityID;
EntityItemProperties tempProperties;
return constructEntityItem(Model, tempEntityID, tempProperties);
}
// Header bytes
// 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...
const int MINIMUM_HEADER_BYTES = 27;
int bytesRead = 0;
if (bytesToRead >= MINIMUM_HEADER_BYTES) {
int originalLength = bytesToRead;
QByteArray originalDataBuffer((const char*)data, originalLength);
// id
QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size
QUuid actualID = QUuid::fromRfc4122(encodedID);
bytesRead += encodedID.size();
// type
QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint32> typeCoder = encodedType;
encodedType = typeCoder; // determine true length
bytesRead += encodedType.size();
quint32 type = typeCoder;
EntityTypes::EntityType entityType = (EntityTypes::EntityType)type;
EntityItemID tempEntityID(actualID);
EntityItemProperties tempProperties;
tempProperties.setCreated(usecTimestampNow()); // this is temporary...
return constructEntityItem(entityType, tempEntityID, tempProperties);
}
return NULL;
}

View file

@ -0,0 +1,70 @@
//
// EntityTypes.h
// libraries/entities/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
//
#ifndef hifi_EntityTypes_h
#define hifi_EntityTypes_h
#include <stdint.h>
#include <QHash>
#include <QString>
#include <OctreeRenderer.h> // for RenderArgs
class EntityItem;
class EntityItemID;
class EntityItemProperties;
class ReadBitstreamToTreeParams;
typedef EntityItem* (*EntityTypeFactory)(const EntityItemID& entityID, const EntityItemProperties& properties);
class EntityTypes {
public:
typedef enum EntityType_t {
Unknown,
Model,
Box,
Sphere,
LAST = Sphere
} EntityType;
static const QString& getEntityTypeName(EntityType entityType);
static EntityTypes::EntityType getEntityTypeFromName(const QString& name);
static bool registerEntityType(EntityType entityType, const char* name, EntityTypeFactory factoryMethod);
static EntityItem* constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties);
static EntityItem* constructEntityItem(const unsigned char* data, int bytesToRead, ReadBitstreamToTreeParams& args);
private:
static QMap<EntityType, QString> _typeToNameMap;
static QMap<QString, EntityTypes::EntityType> _nameToTypeMap;
static EntityTypeFactory _factories[LAST];
static bool _factoriesInitialized;
};
/// Macro for registering entity types. Make sure to add an element to the EntityType enum with your name, and your class should be
/// named NameEntityItem and must of a static method called factory that takes an EnityItemID, and EntityItemProperties and return a newly
/// constructed (heap allocated) instance of your type. e.g. The following prototype:
// static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
#define REGISTER_ENTITY_TYPE(x) static bool x##Registration = \
EntityTypes::registerEntityType(EntityTypes::x, #x, x##EntityItem::factory);
/// Macro for registering entity types with an overloaded factory. Like using the REGISTER_ENTITY_TYPE macro: Make sure to add
/// an element to the EntityType enum with your name. But unlike REGISTER_ENTITY_TYPE, your class can be named anything
/// so long as you provide a static method passed to the macro, that takes an EnityItemID, and EntityItemProperties and
/// returns a newly constructed (heap allocated) instance of your type. e.g. The following prototype:
// static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
#define REGISTER_ENTITY_TYPE_WITH_FACTORY(x,y) static bool x##Registration = \
EntityTypes::registerEntityType(EntityTypes::x, #x, y); \
assert(x##Registration);
#endif // hifi_EntityTypes_h

View file

@ -0,0 +1,398 @@
//
// ModelEntityItem.cpp
// libraries/entities/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 <ByteCountCoding.h>
#include <GLMHelpers.h>
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "ModelEntityItem.h"
EntityItem* ModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new ModelEntityItem(entityID, properties);
}
ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
{
_type = EntityTypes::Model;
setProperties(properties, true);
_animationFrameIndex = 0.0f;
_lastAnimated = usecTimestampNow();
_jointMappingCompleted = false;
_color[0] = _color[1] = _color[2] = 0;
}
EntityItemProperties ModelEntityItem::getProperties() const {
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
properties._color = getXColor();
properties._modelURL = getModelURL();
properties._animationURL = getAnimationURL();
properties._animationIsPlaying = getAnimationIsPlaying();
properties._animationFrameIndex = getAnimationFrameIndex();
properties._animationFPS = getAnimationFPS();
properties._sittingPoints = getSittingPoints(); // sitting support
properties._colorChanged = false;
properties._modelURLChanged = false;
properties._animationURLChanged = false;
properties._animationIsPlayingChanged = false;
properties._animationFrameIndexChanged = false;
properties._animationFPSChanged = false;
properties._glowLevel = getGlowLevel();
properties._glowLevelChanged = false;
return properties;
}
bool ModelEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
bool somethingChanged = false;
somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class
if (properties._colorChanged || forceCopy) {
setColor(properties._color);
somethingChanged = true;
}
if (properties._modelURLChanged || forceCopy) {
setModelURL(properties._modelURL);
somethingChanged = true;
}
if (properties._animationURLChanged || forceCopy) {
setAnimationURL(properties._animationURL);
somethingChanged = true;
}
if (properties._animationIsPlayingChanged || forceCopy) {
setAnimationIsPlaying(properties._animationIsPlaying);
somethingChanged = true;
}
if (properties._animationFrameIndexChanged || forceCopy) {
setAnimationFrameIndex(properties._animationFrameIndex);
somethingChanged = true;
}
if (properties._animationFPSChanged || forceCopy) {
setAnimationFPS(properties._animationFPS);
somethingChanged = true;
}
if (properties._glowLevelChanged || forceCopy) {
setGlowLevel(properties._glowLevel);
somethingChanged = true;
}
if (somethingChanged) {
bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - getLastEdited();
qDebug() << "ModelEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
}
setLastEdited(properties._lastEdited);
}
return somethingChanged;
}
int ModelEntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
return oldVersionReadEntityDataFromBuffer(data, bytesLeftToRead, args);
}
// let our base class do most of the work... it will call us back for our porition...
return EntityItem::readEntityDataFromBuffer(data, bytesLeftToRead, args);
}
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL);
READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, _animationFPS);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, _animationFrameIndex);
READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, _animationIsPlaying);
return bytesRead;
}
int ModelEntityItem::oldVersionReadEntityDataFromBuffer(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
// this old bitstream format had 32bit IDs. They are obsolete and need to be replaced with our new UUID
// format. We can simply read and ignore the old ID since they should not be repeated. This code should only
// run on loading from an old file.
quint32 oldID;
memcpy(&oldID, dataAt, sizeof(oldID));
dataAt += sizeof(oldID);
bytesRead += sizeof(oldID);
_id = QUuid::createUuid();
// _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;
_created = _lastEdited; // NOTE: old models didn't have age or created time, assume their last edit was a create
QString ageAsString = formatSecondsElapsed(getAge());
qDebug() << "Loading old model file, _created = _lastEdited =" << _created
<< " age=" << getAge() << "seconds - " << ageAsString;
// 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);
// TODO: how to handle this? Presumable, this would only ever be true if the model file was saved with
// a model being in a shouldBeDeleted state. Which seems unlikely. But if it happens, maybe we should delete the entity after loading?
// shouldBeDeleted
bool shouldBeDeleted = false;
memcpy(&shouldBeDeleted, dataAt, sizeof(shouldBeDeleted));
dataAt += sizeof(shouldBeDeleted);
bytesRead += sizeof(shouldBeDeleted);
if (shouldBeDeleted) {
qDebug() << "UNEXPECTED - read shouldBeDeleted=TRUE from an old format file";
}
// 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;
// rotation
int bytes = unpackOrientationQuatFromBytes(dataAt, _rotation);
dataAt += bytes;
bytesRead += bytes;
if (args.bitstreamVersion >= VERSION_ENTITIES_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;
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_MODEL_URL;
requestedProperties += PROP_ANIMATION_URL;
requestedProperties += PROP_ANIMATION_FPS;
requestedProperties += PROP_ANIMATION_FRAME_INDEX;
requestedProperties += PROP_ANIMATION_PLAYING;
return requestedProperties;
}
void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount, OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, getModelURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, getAnimationURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, getAnimationFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, getAnimationIsPlaying());
}
QMap<QString, AnimationPointer> ModelEntityItem::_loadedAnimations; // TODO: improve cleanup by leveraging the AnimationPointer(s)
AnimationCache ModelEntityItem::_animationCache;
// This class/instance will cleanup the animations once unloaded.
class EntityAnimationsBookkeeper {
public:
~EntityAnimationsBookkeeper() {
ModelEntityItem::cleanupLoadedAnimations();
}
};
EntityAnimationsBookkeeper modelAnimationsBookkeeperInstance;
void ModelEntityItem::cleanupLoadedAnimations() {
foreach(AnimationPointer animation, _loadedAnimations) {
animation.clear();
}
_loadedAnimations.clear();
}
Animation* ModelEntityItem::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 ModelEntityItem::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> ModelEntityItem::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;
if (animationFrameIndex < 0 || animationFrameIndex > frameCount) {
animationFrameIndex = 0;
}
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;
}
bool ModelEntityItem::isAnimatingSomething() const {
return getAnimationIsPlaying() &&
getAnimationFPS() != 0.0f &&
!getAnimationURL().isEmpty();
}
EntityItem::SimulationState ModelEntityItem::getSimulationState() const {
EntityItem::SimulationState baseClassState = EntityItem::getSimulationState();
// if the base class is static, then consider our animation state, and upgrade to changing if
// we are animating. If the base class has a higher simulation state than static, then
// use the base class state.
if (baseClassState == EntityItem::Static) {
if (isAnimatingSomething()) {
return EntityItem::Changing;
}
}
return baseClassState;
}
void ModelEntityItem::update(const quint64& updateTime) {
EntityItem::update(updateTime); // let our base class handle it's updates...
quint64 now = updateTime;
// only advance the frame index if we're playing
if (getAnimationIsPlaying()) {
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
_lastAnimated = now;
_animationFrameIndex += deltaTime * _animationFPS;
} else {
_lastAnimated = now;
}
}
void ModelEntityItem::debugDump() const {
qDebug() << "ModelEntityItem id:" << getEntityItemID();
qDebug() << " edited ago:" << getEditedAgo();
qDebug() << " position:" << getPosition() * (float)TREE_SCALE;
qDebug() << " radius:" << getRadius() * (float)TREE_SCALE;
qDebug() << " model URL:" << getModelURL();
}

View file

@ -0,0 +1,111 @@
//
// ModelEntityItem.h
// libraries/entities/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
//
#ifndef hifi_ModelEntityItem_h
#define hifi_ModelEntityItem_h
#include "EntityItem.h"
class ModelEntityItem : public EntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
ALLOW_INSTANTIATION // This class can be instantiated
// methods for getting/setting all properties of an entity
virtual EntityItemProperties getProperties() const;
virtual bool setProperties(const EntityItemProperties& properties, bool forceCopy = false);
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const;
virtual int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
virtual void update(const quint64& now);
virtual SimulationState getSimulationState() const;
virtual void debugDump() const;
// TODO: Move these to subclasses, or other appropriate abstraction
// getters/setters applicable to models and particles
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
bool hasModel() const { return !_modelURL.isEmpty(); }
const QString& getModelURL() const { return _modelURL; }
bool hasAnimation() const { return !_animationURL.isEmpty(); }
const QString& getAnimationURL() const { return _animationURL; }
QVector<SittingPoint> getSittingPoints() const { return _sittingPoints; }
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
// model related properties
void setModelURL(const QString& url) { _modelURL = url; }
void setAnimationURL(const QString& url) { _animationURL = url; }
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; }
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; }
void setAnimationFPS(float value) { _animationFPS = value; }
void setSittingPoints(QVector<SittingPoint> sittingPoints) { _sittingPoints = sittingPoints; }
void mapJoints(const QStringList& modelJointNames);
QVector<glm::quat> getAnimationFrame();
bool jointsMapped() const { return _jointMappingCompleted; }
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFrameIndex() const { return _animationFrameIndex; }
float getAnimationFPS() const { return _animationFPS; }
static void cleanupLoadedAnimations();
protected:
/// For reading models from pre V3 bitstreams
int oldVersionReadEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
bool isAnimatingSomething() const;
rgbColor _color;
QString _modelURL;
QVector<SittingPoint> _sittingPoints;
quint64 _lastAnimated;
QString _animationURL;
float _animationFrameIndex; // we keep this as a float and round to int only when we need the exact index
bool _animationIsPlaying;
float _animationFPS;
bool _jointMappingCompleted;
QVector<int> _jointMapping;
static Animation* getAnimation(const QString& url);
static QMap<QString, AnimationPointer> _loadedAnimations;
static AnimationCache _animationCache;
};
#endif // hifi_ModelEntityItem_h

View file

@ -0,0 +1,158 @@
//
// MovingEntitiesOperator.cpp
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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 "EntityItem.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "MovingEntitiesOperator.h"
MovingEntitiesOperator::MovingEntitiesOperator(EntityTree* tree) :
_tree(tree),
_changeTime(usecTimestampNow()),
_foundOldCount(0),
_foundNewCount(0),
_lookingCount(0)
{
}
MovingEntitiesOperator::~MovingEntitiesOperator() {
}
void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACube& oldCube, const AACube& newCube) {
EntityTreeElement* oldContainingElement = _tree->getContainingElement(entity->getEntityItemID());
AABox newBox = newCube.clamp(0.0f, 1.0f);
// If the original containing element is the best fit for the requested newCube locations then
// we don't actually need to add the entity for moving and we can short circuit all this work
if (!oldContainingElement->bestFitBounds(newBox)) {
// check our tree, to determine if this entity is known
EntityToMoveDetails details;
details.oldContainingElement = oldContainingElement;
details.entity = entity;
details.oldFound = false;
details.newFound = false;
details.oldCube = oldCube;
details.newCube = newCube;
details.newBox = newBox;
_entitiesToMove << details;
_lookingCount++;
}
}
// does this entity tree element contain the old entity
bool MovingEntitiesOperator::shouldRecurseSubTree(OctreeElement* element) {
bool containsEntity = false;
// If we don't have an old entity, then we don't contain the entity, otherwise
// check the bounds
if (_entitiesToMove.size() > 0) {
AACube elementCube = element->getAACube();
foreach(const EntityToMoveDetails& details, _entitiesToMove) {
if (elementCube.contains(details.oldCube) || elementCube.contains(details.newCube)) {
containsEntity = true;
break; // if it contains at least one, we're good to go
}
}
}
return containsEntity;
}
bool MovingEntitiesOperator::preRecursion(OctreeElement* element) {
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
// In Pre-recursion, we're generally deciding whether or not we want to recurse this
// path of the tree. For this operation, we want to recurse the branch of the tree if
// and of the following are true:
// * We have not yet found the old entity, and this branch contains our old entity
// * We have not yet found the new entity, and this branch contains our new entity
//
// Note: it's often the case that the branch in question contains both the old entity
// and the new entity.
bool keepSearching = (_foundOldCount < _lookingCount) || (_foundNewCount < _lookingCount);
// If we haven't yet found all the entities, and this sub tree contains at least one of our
// entities, then we need to keep searching.
if (keepSearching && shouldRecurseSubTree(element)) {
// check against each of our search entities
foreach(const EntityToMoveDetails& details, _entitiesToMove) {
// If this is one of the old elements we're looking for, then ask it to remove the old entity
if (!details.oldFound && entityTreeElement == details.oldContainingElement) {
entityTreeElement->removeEntityItem(details.entity);
_foundOldCount++;
//details.oldFound = true; // TODO: would be nice to add this optimization
}
// If this element is the best fit for the new bounds of this entity then add the entity to the element
if (!details.newFound && entityTreeElement->bestFitBounds(details.newCube)) {
EntityItemID entityItemID = details.entity->getEntityItemID();
entityTreeElement->addEntityItem(details.entity);
_tree->setContainingElement(entityItemID, entityTreeElement);
_foundNewCount++;
//details.newFound = true; // TODO: would be nice to add this optimization
}
}
// if we haven't found all of our search for entities, then keep looking
keepSearching = (_foundOldCount < _lookingCount) || (_foundNewCount < _lookingCount);
}
return keepSearching; // if we haven't yet found it, keep looking
}
bool MovingEntitiesOperator::postRecursion(OctreeElement* element) {
// Post-recursion is the unwinding process. For this operation, while we
// unwind we want to mark the path as being dirty if we changed it below.
// We might have two paths, one for the old entity and one for the new entity.
bool keepSearching = (_foundOldCount < _lookingCount) || (_foundNewCount < _lookingCount);
// As we unwind, if we're in either of these two paths, we mark our element
// as dirty.
if ((shouldRecurseSubTree(element))) {
element->markWithChangedTime();
}
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
entityTreeElement->pruneChildren(); // take this opportunity to prune any empty leaves
return keepSearching; // if we haven't yet found it, keep looking
}
OctreeElement* MovingEntitiesOperator::possiblyCreateChildAt(OctreeElement* element, int childIndex) {
// If we're getting called, it's because there was no child element at this index while recursing.
// We only care if this happens while still searching for the new entity locations.
if (_foundNewCount < _lookingCount) {
float childElementScale = element->getAACube().getScale() / 2.0f; // all of our children will be half our scale
// check against each of our entities
foreach(const EntityToMoveDetails& details, _entitiesToMove) {
// if the scale of our desired cube is smaller than our children, then consider making a child
if (details.newBox.getLargestDimension() <= childElementScale) {
int indexOfChildContainingNewEntity = element->getMyChildContaining(details.newBox);
// If the childIndex we were asked if we wanted to create contains this newCube,
// then we will create this branch and continue. We can exit this loop immediately
// because if we need this branch for any one entity then it doesn't matter if it's
// needed for more entities.
if (childIndex == indexOfChildContainingNewEntity) {
return element->addChildAtIndex(childIndex);
}
}
}
}
return NULL;
}

View file

@ -0,0 +1,54 @@
//
// MovingEntitiesOperator.h
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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
//
#ifndef hifi_MovingEntitiesOperator_h
#define hifi_MovingEntitiesOperator_h
class EntityToMoveDetails {
public:
EntityItem* entity;
AACube oldCube;
AACube newCube;
AABox newBox;
EntityTreeElement* oldContainingElement;
bool oldFound;
bool newFound;
};
inline uint qHash(const EntityToMoveDetails& a, uint seed) {
return qHash(a.entity->getEntityItemID(), seed);
}
inline bool operator==(const EntityToMoveDetails& a, const EntityToMoveDetails& b) {
return a.entity->getEntityItemID() == b.entity->getEntityItemID();
}
class MovingEntitiesOperator : public RecurseOctreeOperator {
public:
MovingEntitiesOperator(EntityTree* tree);
~MovingEntitiesOperator();
void addEntityToMoveList(EntityItem* entity, const AACube& oldCube, const AACube& newCube);
virtual bool preRecursion(OctreeElement* element);
virtual bool postRecursion(OctreeElement* element);
virtual OctreeElement* possiblyCreateChildAt(OctreeElement* element, int childIndex);
bool hasMovingEntities() const { return _entitiesToMove.size() > 0; }
private:
EntityTree* _tree;
QSet<EntityToMoveDetails> _entitiesToMove;
quint64 _changeTime;
int _foundOldCount;
int _foundNewCount;
int _lookingCount;
bool shouldRecurseSubTree(OctreeElement* element);
};
#endif // hifi_MovingEntitiesOperator_h

View file

@ -0,0 +1,99 @@
//
// SphereEntityItem.cpp
// libraries/entities/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 <QDebug>
#include <ByteCountCoding.h>
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "SphereEntityItem.h"
EntityItem* SphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new SphereEntityItem(entityID, properties);
}
// our non-pure virtual subclass for now...
SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
{
_type = EntityTypes::Sphere;
setProperties(properties, true);
}
EntityItemProperties SphereEntityItem::getProperties() const {
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
properties.setColor(getXColor());
properties.setGlowLevel(getGlowLevel());
return properties;
}
bool SphereEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
bool somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class
if (properties.colorChanged() || forceCopy) {
setColor(properties.getColor());
somethingChanged = true;
}
if (properties.glowLevelChanged() || forceCopy) {
setGlowLevel(properties.getGlowLevel());
somethingChanged = true;
}
if (somethingChanged) {
bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - getLastEdited();
qDebug() << "SphereEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
}
setLastEdited(properties.getLastEdited());
}
return somethingChanged;
}
int SphereEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
return bytesRead;
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
EntityPropertyFlags SphereEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;
return requestedProperties;
}
void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
}

View file

@ -0,0 +1,57 @@
//
// SphereEntityItem.h
// libraries/entities/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
//
#ifndef hifi_SphereEntityItem_h
#define hifi_SphereEntityItem_h
#include "EntityItem.h"
class SphereEntityItem : public EntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
SphereEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
ALLOW_INSTANTIATION // This class can be instantiated
// methods for getting/setting all properties of an entity
virtual EntityItemProperties getProperties() const;
virtual bool setProperties(const EntityItemProperties& properties, bool forceCopy = false);
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const;
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
protected:
rgbColor _color;
};
#endif // hifi_SphereEntityItem_h

View file

@ -0,0 +1,251 @@
//
// UpdateEntityOperator.cpp
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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 "EntityItem.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "UpdateEntityOperator.h"
UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree,
EntityTreeElement* containingElement,
EntityItem* existingEntity,
const EntityItemProperties& properties) :
_tree(tree),
_existingEntity(existingEntity),
_containingElement(containingElement),
_containingElementCube(containingElement->getAACube()),
_properties(properties),
_entityItemID(existingEntity->getEntityItemID()),
_foundOld(false),
_foundNew(false),
_removeOld(false),
_dontMove(false), // assume we'll be moving
_changeTime(usecTimestampNow()),
_oldEntityCube(),
_newEntityCube()
{
// caller must have verified existence of containingElement and oldEntity
assert(_containingElement && _existingEntity);
_oldEntityCube = _existingEntity->getAACube();
_oldEntityBox = _oldEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds
// If the new properties has position OR radius changes, but not both, we need to
// get the old property value and set it in our properties in order for our bounds
// calculations to work.
if (_properties.containsPositionChange() && !_properties.containsRadiusChange()) {
float oldRadiusInMeters = _existingEntity->getRadius() * (float)TREE_SCALE;
_properties.setRadius(oldRadiusInMeters);
}
if (!_properties.containsPositionChange() && _properties.containsRadiusChange()) {
glm::vec3 oldPositionInMeters = _existingEntity->getPosition() * (float)TREE_SCALE;
_properties.setPosition(oldPositionInMeters);
}
// If our new properties don't have bounds details (no change to position, etc) or if this containing element would
// be the best fit for our new properties, then just do the new portion of the store pass, since the change path will
// be the same for both parts of the update
bool oldElementBestFit = _containingElement->bestFitBounds(_properties);
// if we don't have bounds properties, then use our old clamped box to determine best fit
if (!_properties.containsBoundsProperties()) {
oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox);
}
// For some reason we've seen a case where the original containing element isn't a best fit for the old properties
// in this case we want to move it, even if the properties haven't changed.
if (!_properties.containsBoundsProperties() && !oldElementBestFit) {
_newEntityCube = _oldEntityCube;
_removeOld = true; // our properties are going to move us, so remember this for later processing
} else if (!_properties.containsBoundsProperties() || oldElementBestFit) {
_foundOld = true;
_newEntityCube = _oldEntityCube;
_dontMove = true;
} else {
_newEntityCube = _properties.getAACubeTreeUnits();
_removeOld = true; // our properties are going to move us, so remember this for later processing
}
_newEntityBox = _newEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds
}
UpdateEntityOperator::~UpdateEntityOperator() {
}
// does this entity tree element contain the old entity
bool UpdateEntityOperator::subTreeContainsOldEntity(OctreeElement* element) {
// We've found cases where the old entity might be placed in an element that is not actually the best fit
// so when we're searching the tree for the old element, we use the known cube for the known containing element
bool elementContainsOldBox = element->getAACube().contains(_containingElementCube);
/*
bool elementContainsOldCube = element->getAACube().contains(_oldEntityCube);
qDebug() << "UpdateEntityOperator::subTreeContainsOldEntity()....";
qDebug() << " element->getAACube()=" << element->getAACube();
qDebug() << " _oldEntityCube=" << _oldEntityCube;
qDebug() << " _oldEntityBox=" << _oldEntityBox;
qDebug() << " elementContainsOldCube=" << elementContainsOldCube;
qDebug() << " elementContainsOldBox=" << elementContainsOldBox;
*/
return elementContainsOldBox;
}
bool UpdateEntityOperator::subTreeContainsNewEntity(OctreeElement* element) {
bool elementContainsNewBox = element->getAACube().contains(_newEntityBox);
/*
bool elementContainsNewCube = element->getAACube().contains(_newEntityCube);
qDebug() << "UpdateEntityOperator::subTreeContainsNewEntity()....";
qDebug() << " element->getAACube()=" << element->getAACube();
qDebug() << " _newEntityCube=" << _newEntityCube;
qDebug() << " _newEntityBox=" << _newEntityBox;
qDebug() << " elementContainsNewCube=" << elementContainsNewCube;
qDebug() << " elementContainsNewBox=" << elementContainsNewBox;
*/
return elementContainsNewBox;
}
bool UpdateEntityOperator::preRecursion(OctreeElement* element) {
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
// In Pre-recursion, we're generally deciding whether or not we want to recurse this
// path of the tree. For this operation, we want to recurse the branch of the tree if
// and of the following are true:
// * We have not yet found the old entity, and this branch contains our old entity
// * We have not yet found the new entity, and this branch contains our new entity
//
// Note: it's often the case that the branch in question contains both the old entity
// and the new entity.
bool keepSearching = false; // assume we don't need to search any more
bool subtreeContainsOld = subTreeContainsOldEntity(element);
bool subtreeContainsNew = subTreeContainsNewEntity(element);
// If we haven't yet found the old entity, and this subTreeContains our old
// entity, then we need to keep searching.
if (!_foundOld && subtreeContainsOld) {
// If this is the element we're looking for, then ask it to remove the old entity
// and we can stop searching.
if (entityTreeElement == _containingElement) {
// If the containgElement IS NOT the best fit for the new entity properties
// then we need to remove it, and the updateEntity below will store it in the
// correct element.
if (_removeOld) {
entityTreeElement->removeEntityItem(_existingEntity); // NOTE: only removes the entity, doesn't delete it
// If we haven't yet found the new location, then we need to
// make sure to remove our entity to element map, because for
// now we're not in that map
if (!_foundNew) {
_tree->setContainingElement(_entityItemID, NULL);
}
}
_foundOld = true;
} else {
// if this isn't the element we're looking for, then keep searching
keepSearching = true;
}
}
// If we haven't yet found the new entity, and this subTreeContains our new
// entity, then we need to keep searching.
if (!_foundNew && subtreeContainsNew) {
// If this element is the best fit for the new entity properties, then add/or update it
if (entityTreeElement->bestFitBounds(_newEntityBox)) {
// if we are the existing containing element, then we can just do the update of the entity properties
if (entityTreeElement == _containingElement) {
assert(!_removeOld); // We shouldn't be in a remove old case and also be the new best fit
// set the entity properties and mark our element as changed.
_existingEntity->setProperties(_properties);
} else {
// otherwise, this is an add case.
entityTreeElement->addEntityItem(_existingEntity);
_existingEntity->setProperties(_properties); // still need to update the properties!
_tree->setContainingElement(_entityItemID, entityTreeElement);
}
_foundNew = true; // we found the new item!
} else {
keepSearching = true;
}
}
return keepSearching; // if we haven't yet found it, keep looking
}
bool UpdateEntityOperator::postRecursion(OctreeElement* element) {
// Post-recursion is the unwinding process. For this operation, while we
// unwind we want to mark the path as being dirty if we changed it below.
// We might have two paths, one for the old entity and one for the new entity.
bool keepSearching = !_foundOld || !_foundNew;
bool subtreeContainsOld = subTreeContainsOldEntity(element);
bool subtreeContainsNew = subTreeContainsNewEntity(element);
// As we unwind, if we're in either of these two paths, we mark our element
// as dirty.
if ((_foundOld && subtreeContainsOld) ||
(_foundNew && subtreeContainsNew)) {
element->markWithChangedTime();
}
// It's not OK to prune if we have the potential of deleting the original containig element.
// because if we prune the containing element then new might end up reallocating the same memory later
// and that will confuse our logic.
//
// it's ok to prune if:
// 1) we're not removing the old
// 2) we are removing the old, but this subtree doesn't contain the old
// 3) we are removing the old, this subtree contains the old, but this element isn't a direct parent of _containingElement
if (!_removeOld || !subtreeContainsOld || !element->isParentOf(_containingElement)) {
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
entityTreeElement->pruneChildren(); // take this opportunity to prune any empty leaves
}
return keepSearching; // if we haven't yet found it, keep looking
}
OctreeElement* UpdateEntityOperator::possiblyCreateChildAt(OctreeElement* element, int childIndex) {
// If we're getting called, it's because there was no child element at this index while recursing.
// We only care if this happens while still searching for the new entity location.
// Check to see if
if (!_foundNew) {
float childElementScale = element->getScale() / 2.0f; // all of our children will be half our scale
// Note: because the entity's bounds might have been clamped to the domain. We want to check if the
// bounds of the clamped box would fit in our child elements. It may be the case that the actual
// bounds of the element would hang outside of the child elements cells.
bool entityWouldFitInChild = _newEntityBox.getLargestDimension() <= childElementScale;
// if the scale of our desired cube is smaller than our children, then consider making a child
if (entityWouldFitInChild) {
int indexOfChildContainingNewEntity = element->getMyChildContaining(_newEntityBox);
if (childIndex == indexOfChildContainingNewEntity) {
return element->addChildAtIndex(childIndex);;
}
}
}
return NULL;
}

View file

@ -0,0 +1,47 @@
//
// UpdateEntityOperator.h
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 8/11/2014.
// Copyright 2014 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
//
#ifndef hifi_UpdateEntityOperator_h
#define hifi_UpdateEntityOperator_h
class UpdateEntityOperator : public RecurseOctreeOperator {
public:
UpdateEntityOperator(EntityTree* tree, EntityTreeElement* containingElement,
EntityItem* existingEntity, const EntityItemProperties& properties);
~UpdateEntityOperator();
virtual bool preRecursion(OctreeElement* element);
virtual bool postRecursion(OctreeElement* element);
virtual OctreeElement* possiblyCreateChildAt(OctreeElement* element, int childIndex);
private:
EntityTree* _tree;
EntityItem* _existingEntity;
EntityTreeElement* _containingElement;
AACube _containingElementCube; // we temporarily store our cube here in case we need to delete the containing element
EntityItemProperties _properties;
EntityItemID _entityItemID;
bool _foundOld;
bool _foundNew;
bool _removeOld;
bool _dontMove;
quint64 _changeTime;
AACube _oldEntityCube;
AACube _newEntityCube;
AABox _oldEntityBox; // clamped to domain
AABox _newEntityBox; // clamped to domain
bool subTreeContainsOldEntity(OctreeElement* element);
bool subTreeContainsNewEntity(OctreeElement* element);
};
#endif // hifi_UpdateEntityOperator_h

View file

@ -1,60 +0,0 @@
//
// ModelEditPacketSender.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 8/12/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 <assert.h>
#include <PerfStat.h>
#include <OctalCode.h>
#include <PacketHeaders.h>
#include "ModelEditPacketSender.h"
#include "ModelItem.h"
void ModelEditPacketSender::sendEditModelMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties) {
// allows app to disable sending if for example voxels have been disabled
if (!_shouldSend) {
return; // bail early
}
static unsigned char bufferOut[MAX_PACKET_SIZE];
int sizeOut = 0;
// This encodes the voxel edit message into a buffer...
if (ModelItem::encodeModelEditMessageDetails(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)){
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
// jurisdictions for processing
if (!serversExist()) {
queuePendingPacketToNodes(type, bufferOut, sizeOut);
} else {
queuePacketToNodes(bufferOut, sizeOut);
}
}
}
void ModelEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, size_t length, int clockSkew) {
ModelItem::adjustEditPacketForClockSkew(codeColorBuffer, length, clockSkew);
}
void ModelEditPacketSender::queueModelEditMessage(PacketType type, ModelItemID modelID,
const ModelItemProperties& properties) {
if (!_shouldSend) {
return; // bail early
}
// use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize
static unsigned char bufferOut[MAX_PACKET_SIZE];
int sizeOut = 0;
if (ModelItem::encodeModelEditMessageDetails(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(type, bufferOut, sizeOut);
}
}

View file

@ -1,37 +0,0 @@
//
// ModelEditPacketSender.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 8/12/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
//
#ifndef hifi_ModelEditPacketSender_h
#define hifi_ModelEditPacketSender_h
#include <OctreeEditPacketSender.h>
#include "ModelItem.h"
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
class ModelEditPacketSender : public OctreeEditPacketSender {
Q_OBJECT
public:
/// Send model add message immediately
/// NOTE: ModelItemProperties assumes that all distances are in meter units
void sendEditModelMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties);
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
/// NOTE: ModelItemProperties assumes that all distances are in meter units
void queueModelEditMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties);
// My server type is the model server
virtual char getMyNodeType() const { return NodeType::ModelServer; }
virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, size_t length, int clockSkew);
};
#endif // hifi_ModelEditPacketSender_h

File diff suppressed because it is too large Load diff

View file

@ -1,340 +0,0 @@
//
// ModelItem.h
// 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
//
#ifndef hifi_ModelItem_h
#define hifi_ModelItem_h
#include <stdint.h>
#include <glm/glm.hpp>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <AnimationCache.h>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <OctreePacketData.h>
#include <FBXReader.h>
class ModelItem;
class ModelEditPacketSender;
class ModelItemProperties;
class ModelsScriptingInterface;
class ModelTree;
class ScriptEngine;
class VoxelEditPacketSender;
class VoxelsScriptingInterface;
struct VoxelDetail;
const uint32_t NEW_MODEL = 0xFFFFFFFF;
const uint32_t UNKNOWN_MODEL_TOKEN = 0xFFFFFFFF;
const uint32_t UNKNOWN_MODEL_ID = 0xFFFFFFFF;
const uint16_t MODEL_PACKET_CONTAINS_RADIUS = 1;
const uint16_t MODEL_PACKET_CONTAINS_POSITION = 2;
const uint16_t MODEL_PACKET_CONTAINS_COLOR = 4;
const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 8;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 16;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 32;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_URL = 64;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_PLAYING = 128;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FRAME = 256;
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FPS = 512;
const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
const QString MODEL_DEFAULT_MODEL_URL("");
const glm::quat MODEL_DEFAULT_MODEL_ROTATION;
const QString MODEL_DEFAULT_ANIMATION_URL("");
const float MODEL_DEFAULT_ANIMATION_FPS = 30.0f;
const PacketVersion VERSION_MODELS_HAVE_ANIMATION = 1;
const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2;
/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of
/// model item properties via JavaScript hashes/QScriptValues
/// all units for position, radius, etc are in meter units
class ModelItemProperties {
public:
ModelItemProperties();
QScriptValue copyToScriptValue(QScriptEngine* engine) const;
void copyFromScriptValue(const QScriptValue& object);
void copyToModelItem(ModelItem& modelItem) const;
void copyFromModelItem(const ModelItem& modelItem);
void copyFromNewModelItem(const ModelItem& modelItem);
const glm::vec3& getPosition() const { return _position; }
xColor getColor() const { return _color; }
float getRadius() const { return _radius; }
bool getShouldDie() const { return _shouldDie; }
const QString& getModelURL() const { return _modelURL; }
const glm::quat& getModelRotation() const { return _modelRotation; }
const QString& getAnimationURL() const { return _animationURL; }
float getAnimationFrameIndex() const { return _animationFrameIndex; }
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFPS() const { return _animationFPS; }
float getGlowLevel() const { return _glowLevel; }
quint64 getLastEdited() const { return _lastEdited; }
uint16_t getChangedBits() const;
/// set position in meter units
void setPosition(const glm::vec3& value) { _position = value; _positionChanged = true; }
void setColor(const xColor& value) { _color = value; _colorChanged = true; }
void setRadius(float value) { _radius = value; _radiusChanged = true; }
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; }
// model related properties
void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; }
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; }
void setAnimationURL(const QString& url) { _animationURL = url; _animationURLChanged = true; }
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; }
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; }
void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; }
void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; }
/// used by ModelScriptingInterface to return ModelItemProperties for unknown models
void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; }
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
private:
glm::vec3 _position;
xColor _color;
float _radius;
bool _shouldDie; /// to delete it
QString _modelURL;
glm::quat _modelRotation;
QString _animationURL;
bool _animationIsPlaying;
float _animationFrameIndex;
float _animationFPS;
float _glowLevel;
QVector<SittingPoint> _sittingPoints;
uint32_t _id;
bool _idSet;
quint64 _lastEdited;
bool _positionChanged;
bool _colorChanged;
bool _radiusChanged;
bool _shouldDieChanged;
bool _modelURLChanged;
bool _modelRotationChanged;
bool _animationURLChanged;
bool _animationIsPlayingChanged;
bool _animationFrameIndexChanged;
bool _animationFPSChanged;
bool _glowLevelChanged;
bool _defaultSettings;
};
Q_DECLARE_METATYPE(ModelItemProperties);
QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties);
void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties);
/// Abstract ID for editing model items. Used in ModelItem JS API - When models are created in the JS api, they are given a
/// local creatorTokenID, the actual id for the model is not known until the server responds to the creator with the
/// correct mapping. This class works with the scripting API an allows the developer to edit models they created.
class ModelItemID {
public:
ModelItemID() :
id(NEW_MODEL), creatorTokenID(UNKNOWN_MODEL_TOKEN), isKnownID(false) { };
ModelItemID(uint32_t id, uint32_t creatorTokenID, bool isKnownID) :
id(id), creatorTokenID(creatorTokenID), isKnownID(isKnownID) { };
ModelItemID(uint32_t id) :
id(id), creatorTokenID(UNKNOWN_MODEL_TOKEN), isKnownID(true) { };
uint32_t id;
uint32_t creatorTokenID;
bool isKnownID;
};
Q_DECLARE_METATYPE(ModelItemID);
Q_DECLARE_METATYPE(QVector<ModelItemID>);
QScriptValue ModelItemIDtoScriptValue(QScriptEngine* engine, const ModelItemID& properties);
void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& properties);
/// ModelItem class - this is the actual model item class.
class ModelItem {
public:
ModelItem();
ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& properties);
/// creates an NEW model from an model add or edit message data buffer
static ModelItem fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid);
virtual ~ModelItem();
virtual void init(glm::vec3 position, float radius, rgbColor color, uint32_t id = NEW_MODEL);
/// get position in domain scale units (0.0 - 1.0)
const glm::vec3& getPosition() const { return _position; }
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
/// get radius in domain scale units (0.0 - 1.0)
float getRadius() const { return _radius; }
/// get maximum dimension in domain scale units (0.0 - 1.0)
float getSize() const { return _radius * 2.0f; }
/// get maximum dimension in domain scale units (0.0 - 1.0)
AACube getAACube() const { return AACube(getMinimumPoint(), getSize()); }
// model related properties
bool hasModel() const { return !_modelURL.isEmpty(); }
const QString& getModelURL() const { return _modelURL; }
const glm::quat& getModelRotation() const { return _modelRotation; }
bool hasAnimation() const { return !_animationURL.isEmpty(); }
const QString& getAnimationURL() const { return _animationURL; }
float getGlowLevel() const { return _glowLevel; }
QVector<SittingPoint> getSittingPoints() const { return _sittingPoints; }
ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); }
ModelItemProperties getProperties() const;
/// The last updated/simulated time of this model from the time perspective of the authoritative server/source
quint64 getLastUpdated() const { return _lastUpdated; }
/// The last edited time of this model from the time perspective of the authoritative server/source
quint64 getLastEdited() const { return _lastEdited; }
void setLastEdited(quint64 lastEdited) { _lastEdited = lastEdited; }
/// how long ago was this model edited in seconds
float getEditedAgo() const { return static_cast<float>(usecTimestampNow() - _lastEdited) / static_cast<float>(USECS_PER_SECOND); }
uint32_t getID() const { return _id; }
void setID(uint32_t id) { _id = id; }
bool getShouldDie() const { return _shouldDie; }
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
bool isNewlyCreated() const { return _newlyCreated; }
bool isKnownID() const { return getID() != UNKNOWN_MODEL_ID; }
/// set position in domain scale units (0.0 - 1.0)
void setPosition(const glm::vec3& value) { _position = value; }
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
/// set radius in domain scale units (0.0 - 1.0)
void setRadius(float value) { _radius = value; }
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; }
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
// model related properties
void setModelURL(const QString& url) { _modelURL = url; }
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; }
void setAnimationURL(const QString& url) { _animationURL = url; }
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; }
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; }
void setAnimationFPS(float value) { _animationFPS = value; }
void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; }
void setSittingPoints(QVector<SittingPoint> sittingPoints) { _sittingPoints = sittingPoints; }
void setProperties(const ModelItemProperties& properties);
bool appendModelData(OctreePacketData* packetData) const;
int readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
static int expectedBytes();
static bool encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& details,
unsigned char* bufferOut, int sizeIn, int& sizeOut);
static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, size_t length, int clockSkew);
void update(const quint64& now);
void debugDump() const;
// similar to assignment/copy, but it handles keeping lifetime accurate
void copyChangedProperties(const ModelItem& other);
// these methods allow you to create models, and later edit them.
static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID);
static uint32_t getNextCreatorTokenID();
static void handleAddModelResponse(const QByteArray& packet);
void mapJoints(const QStringList& modelJointNames);
QVector<glm::quat> getAnimationFrame();
bool jointsMapped() const { return _jointMappingCompleted; }
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFrameIndex() const { return _animationFrameIndex; }
float getAnimationFPS() const { return _animationFPS; }
static void cleanupLoadedAnimations();
protected:
glm::vec3 _position;
rgbColor _color;
float _radius;
uint32_t _id;
static uint32_t _nextID;
bool _shouldDie;
// model related items
QString _modelURL;
glm::quat _modelRotation;
QVector<SittingPoint> _sittingPoints;
float _glowLevel;
uint32_t _creatorTokenID;
bool _newlyCreated;
quint64 _lastUpdated;
quint64 _lastEdited;
quint64 _lastAnimated;
QString _animationURL;
float _animationFrameIndex; // we keep this as a float and round to int only when we need the exact index
bool _animationIsPlaying;
float _animationFPS;
bool _jointMappingCompleted;
QVector<int> _jointMapping;
// used by the static interfaces for creator token ids
static uint32_t _nextCreatorTokenID;
static std::map<uint32_t,uint32_t> _tokenIDsToIDs;
static Animation* getAnimation(const QString& url);
static QMap<QString, AnimationPointer> _loadedAnimations;
static AnimationCache _animationCache;
};
#endif // hifi_ModelItem_h

View file

@ -1,743 +0,0 @@
//
// ModelTree.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 "ModelEditPacketSender.h"
#include "ModelItem.h"
#include "ModelTree.h"
ModelTree::ModelTree(bool shouldReaverage) : Octree(shouldReaverage) {
_rootElement = createNewElement();
}
ModelTreeElement* ModelTree::createNewElement(unsigned char * octalCode) {
ModelTreeElement* newElement = new ModelTreeElement(octalCode);
newElement->setTree(this);
return newElement;
}
bool ModelTree::handlesEditPacketType(PacketType packetType) const {
// we handle these types of "edit" packets
switch (packetType) {
case PacketTypeModelAddOrEdit:
case PacketTypeModelErase:
return true;
default:
return false;
}
}
class FindAndDeleteModelsArgs {
public:
QList<uint32_t> _idsToDelete;
};
bool ModelTree::findAndDeleteOperation(OctreeElement* element, void* extraData) {
//qDebug() << "findAndDeleteOperation()";
FindAndDeleteModelsArgs* args = static_cast< FindAndDeleteModelsArgs*>(extraData);
// if we've found and deleted all our target models, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
//qDebug() << "findAndDeleteOperation() args->_idsToDelete.size():" << args->_idsToDelete.size();
for (QList<uint32_t>::iterator it = args->_idsToDelete.begin(); it != args->_idsToDelete.end(); it++) {
uint32_t modelID = *it;
//qDebug() << "findAndDeleteOperation() modelID:" << modelID;
if (modelTreeElement->removeModelWithID(modelID)) {
// if the model was in this element, then remove it from our search list.
//qDebug() << "findAndDeleteOperation() it = args->_idsToDelete.erase(it)";
it = args->_idsToDelete.erase(it);
}
if (it == args->_idsToDelete.end()) {
//qDebug() << "findAndDeleteOperation() breaking";
break;
}
}
// if we've found and deleted all our target models, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
return true;
}
class FindAndUpdateModelOperator : public RecurseOctreeOperator {
public:
FindAndUpdateModelOperator(const ModelItem& searchModel);
virtual bool PreRecursion(OctreeElement* element);
virtual bool PostRecursion(OctreeElement* element);
bool wasFound() const { return _found; }
private:
const ModelItem& _searchModel;
bool _found;
};
FindAndUpdateModelOperator::FindAndUpdateModelOperator(const ModelItem& searchModel) :
_searchModel(searchModel),
_found(false) {
};
bool FindAndUpdateModelOperator::PreRecursion(OctreeElement* element) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
// Note: updateModel() will only operate on correctly found models
if (modelTreeElement->updateModel(_searchModel)) {
_found = true;
return false; // stop searching
}
return !_found; // if we haven't yet found it, keep looking
}
bool FindAndUpdateModelOperator::PostRecursion(OctreeElement* element) {
if (_found) {
element->markWithChangedTime();
}
return !_found; // if we haven't yet found it, keep looking
}
// TODO: improve this to not use multiple recursions
void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) {
// First, look for the existing model in the tree..
FindAndUpdateModelOperator theOperator(model);
recurseTreeWithOperator(&theOperator);
// if we didn't find it in the tree, then store it...
if (!theOperator.wasFound()) {
AACube modelCube = model.getAACube();
ModelTreeElement* element = static_cast<ModelTreeElement*>(getOrCreateChildElementContaining(model.getAACube()));
element->storeModel(model);
// In the case where we stored it, we also need to mark the entire "path" down to the model as
// having changed. Otherwise viewers won't see this change. So we call this recursion now that
// we know it will be found, this find/update will correctly mark the tree as changed.
recurseTreeWithOperator(&theOperator);
}
_isDirty = true;
}
class FindAndUpdateModelWithIDandPropertiesOperator : public RecurseOctreeOperator {
public:
FindAndUpdateModelWithIDandPropertiesOperator(const ModelItemID& modelID, const ModelItemProperties& properties);
virtual bool PreRecursion(OctreeElement* element);
virtual bool PostRecursion(OctreeElement* element);
bool wasFound() const { return _found; }
private:
const ModelItemID& _modelID;
const ModelItemProperties& _properties;
bool _found;
};
FindAndUpdateModelWithIDandPropertiesOperator::FindAndUpdateModelWithIDandPropertiesOperator(const ModelItemID& modelID,
const ModelItemProperties& properties) :
_modelID(modelID),
_properties(properties),
_found(false) {
};
bool FindAndUpdateModelWithIDandPropertiesOperator::PreRecursion(OctreeElement* element) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
// Note: updateModel() will only operate on correctly found models
if (modelTreeElement->updateModel(_modelID, _properties)) {
_found = true;
return false; // stop searching
}
return !_found; // if we haven't yet found it, keep looking
}
bool FindAndUpdateModelWithIDandPropertiesOperator::PostRecursion(OctreeElement* element) {
if (_found) {
element->markWithChangedTime();
}
return !_found; // if we haven't yet found it, keep looking
}
void ModelTree::updateModel(const ModelItemID& modelID, const ModelItemProperties& properties) {
// Look for the existing model in the tree..
FindAndUpdateModelWithIDandPropertiesOperator theOperator(modelID, properties);
recurseTreeWithOperator(&theOperator);
if (theOperator.wasFound()) {
_isDirty = true;
}
}
void ModelTree::addModel(const ModelItemID& modelID, const ModelItemProperties& properties) {
// This only operates on locally created models
if (modelID.isKnownID) {
return; // not allowed
}
ModelItem model(modelID, properties);
glm::vec3 position = model.getPosition();
float size = std::max(MINIMUM_MODEL_ELEMENT_SIZE, model.getRadius());
ModelTreeElement* element = static_cast<ModelTreeElement*>(getOrCreateChildElementAt(position.x, position.y, position.z, size));
element->storeModel(model);
_isDirty = true;
}
void ModelTree::deleteModel(const ModelItemID& modelID) {
if (modelID.isKnownID) {
FindAndDeleteModelsArgs args;
args._idsToDelete.push_back(modelID.id);
recurseTreeWithOperation(findAndDeleteOperation, &args);
}
}
void ModelTree::sendModels(ModelEditPacketSender* packetSender, float x, float y, float z) {
SendModelsOperationArgs args;
args.packetSender = packetSender;
args.root = glm::vec3(x, y, z);
recurseTreeWithOperation(sendModelsOperation, &args);
packetSender->releaseQueuedMessages();
}
bool ModelTree::sendModelsOperation(OctreeElement* element, void* extraData) {
SendModelsOperationArgs* args = static_cast<SendModelsOperationArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
const QList<ModelItem>& modelList = modelTreeElement->getModels();
for (int i = 0; i < modelList.size(); i++) {
uint32_t creatorTokenID = ModelItem::getNextCreatorTokenID();
ModelItemID id(NEW_MODEL, creatorTokenID, false);
ModelItemProperties properties;
properties.copyFromNewModelItem(modelList.at(i));
properties.setPosition(properties.getPosition() + args->root);
args->packetSender->queueModelEditMessage(PacketTypeModelAddOrEdit, id, properties);
}
return true;
}
// scans the tree and handles mapping locally created models to know IDs.
// in the event that this tree is also viewing the scene, then we need to also
// search the tree to make sure we don't have a duplicate model from the viewing
// operation.
bool ModelTree::findAndUpdateModelItemIDOperation(OctreeElement* element, void* extraData) {
bool keepSearching = true;
FindAndUpdateModelItemIDArgs* args = static_cast<FindAndUpdateModelItemIDArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
// Note: updateModelItemID() will only operate on correctly found models
modelTreeElement->updateModelItemID(args);
// if we've found and replaced both the creatorTokenID and the viewedModel, then we
// can stop looking, otherwise we will keep looking
if (args->creatorTokenFound && args->viewedModelFound) {
keepSearching = false;
}
return keepSearching;
}
void ModelTree::handleAddModelResponse(const QByteArray& packet) {
int numBytesPacketHeader = numBytesForPacketHeader(packet);
const unsigned char* dataAt = reinterpret_cast<const unsigned char*>(packet.data()) + numBytesPacketHeader;
uint32_t creatorTokenID;
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
dataAt += sizeof(creatorTokenID);
uint32_t modelID;
memcpy(&modelID, dataAt, sizeof(modelID));
dataAt += sizeof(modelID);
// update models in our tree
bool assumeModelFound = !getIsViewing(); // if we're not a viewing tree, then we don't have to find the actual model
FindAndUpdateModelItemIDArgs args = {
modelID,
creatorTokenID,
false,
assumeModelFound,
getIsViewing()
};
const bool wantDebug = false;
if (wantDebug) {
qDebug() << "looking for creatorTokenID=" << creatorTokenID << " modelID=" << modelID
<< " getIsViewing()=" << getIsViewing();
}
lockForWrite();
recurseTreeWithOperation(findAndUpdateModelItemIDOperation, &args);
unlock();
}
class FindNearPointArgs {
public:
glm::vec3 position;
float targetRadius;
bool found;
const ModelItem* closestModel;
float closestModelDistance;
};
bool ModelTree::findNearPointOperation(OctreeElement* element, void* extraData) {
FindNearPointArgs* args = static_cast<FindNearPointArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
glm::vec3 penetration;
bool sphereIntersection = modelTreeElement->getAACube().findSpherePenetration(args->position,
args->targetRadius, penetration);
// If this modelTreeElement contains the point, then search it...
if (sphereIntersection) {
const ModelItem* thisClosestModel = modelTreeElement->getClosestModel(args->position);
// we may have gotten NULL back, meaning no model was available
if (thisClosestModel) {
glm::vec3 modelPosition = thisClosestModel->getPosition();
float distanceFromPointToModel = glm::distance(modelPosition, args->position);
// If we're within our target radius
if (distanceFromPointToModel <= args->targetRadius) {
// we are closer than anything else we've found
if (distanceFromPointToModel < args->closestModelDistance) {
args->closestModel = thisClosestModel;
args->closestModelDistance = distanceFromPointToModel;
args->found = true;
}
}
}
// we should be able to optimize this...
return true; // keep searching in case children have closer models
}
// if this element doesn't contain the point, then none of it's children can contain the point, so stop searching
return false;
}
const ModelItem* ModelTree::findClosestModel(glm::vec3 position, float targetRadius) {
FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX };
lockForRead();
recurseTreeWithOperation(findNearPointOperation, &args);
unlock();
return args.closestModel;
}
class FindAllNearPointArgs {
public:
glm::vec3 position;
float targetRadius;
QVector<const ModelItem*> models;
};
bool ModelTree::findInSphereOperation(OctreeElement* element, void* extraData) {
FindAllNearPointArgs* args = static_cast<FindAllNearPointArgs*>(extraData);
glm::vec3 penetration;
bool sphereIntersection = element->getAACube().findSpherePenetration(args->position,
args->targetRadius, penetration);
// If this element contains the point, then search it...
if (sphereIntersection) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
modelTreeElement->getModels(args->position, args->targetRadius, args->models);
return true; // keep searching in case children have closer models
}
// if this element doesn't contain the point, then none of it's children can contain the point, so stop searching
return false;
}
void ModelTree::findModels(const glm::vec3& center, float radius, QVector<const ModelItem*>& foundModels) {
FindAllNearPointArgs args = { center, radius };
lockForRead();
recurseTreeWithOperation(findInSphereOperation, &args);
unlock();
// swap the two lists of model pointers instead of copy
foundModels.swap(args.models);
}
class FindModelsInCubeArgs {
public:
FindModelsInCubeArgs(const AACube& cube)
: _cube(cube), _foundModels() {
}
AACube _cube;
QVector<ModelItem*> _foundModels;
};
void ModelTree::findModelsInCube(const AACube& cube, QVector<ModelItem*>& foundModels) {
FindModelsInCubeArgs args(cube);
lockForRead();
recurseTreeWithOperation(findInCubeOperation, &args);
unlock();
// swap the two lists of model pointers instead of copy
foundModels.swap(args._foundModels);
}
bool ModelTree::findInCubeOperation(OctreeElement* element, void* extraData) {
FindModelsInCubeArgs* args = static_cast<FindModelsInCubeArgs*>(extraData);
const AACube& elementCube = element->getAACube();
if (elementCube.touches(args->_cube)) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
modelTreeElement->getModelsInside(args->_cube, args->_foundModels);
return true;
}
return false;
}
bool ModelTree::findInCubeForUpdateOperation(OctreeElement* element, void* extraData) {
FindModelsInCubeArgs* args = static_cast<FindModelsInCubeArgs*>(extraData);
const AACube& elementCube = element->getAACube();
if (elementCube.touches(args->_cube)) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
modelTreeElement->getModelsForUpdate(args->_cube, args->_foundModels);
return true;
}
return false;
}
void ModelTree::findModelsForUpdate(const AACube& cube, QVector<ModelItem*>& foundModels) {
FindModelsInCubeArgs args(cube);
lockForRead();
recurseTreeWithOperation(findInCubeForUpdateOperation, &args);
unlock();
// swap the two lists of model pointers instead of copy
foundModels.swap(args._foundModels);
}
class FindByIDArgs {
public:
uint32_t id;
bool found;
const ModelItem* foundModel;
};
bool ModelTree::findByIDOperation(OctreeElement* element, void* extraData) {
//qDebug() << "ModelTree::findByIDOperation()....";
FindByIDArgs* args = static_cast<FindByIDArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
// if already found, stop looking
if (args->found) {
return false;
}
// as the tree element if it has this model
const ModelItem* foundModel = modelTreeElement->getModelWithID(args->id);
if (foundModel) {
args->foundModel = foundModel;
args->found = true;
return false;
}
// keep looking
return true;
}
const ModelItem* ModelTree::findModelByID(uint32_t id, bool alreadyLocked) {
FindByIDArgs args = { id, false, NULL };
if (!alreadyLocked) {
lockForRead();
}
recurseTreeWithOperation(findByIDOperation, &args);
if (!alreadyLocked) {
unlock();
}
return args.foundModel;
}
int ModelTree::processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) {
int processedBytes = 0;
// we handle these types of "edit" packets
switch (packetType) {
case PacketTypeModelAddOrEdit: {
bool isValid;
ModelItem newModel = ModelItem::fromEditPacket(editData, maxLength, processedBytes, this, isValid);
if (isValid) {
storeModel(newModel, senderNode);
if (newModel.isNewlyCreated()) {
notifyNewlyCreatedModel(newModel, senderNode);
}
}
} break;
// TODO: wire in support here for server to get PacketTypeModelErase messages
// instead of using PacketTypeModelAddOrEdit messages to delete models
case PacketTypeModelErase:
processedBytes = 0;
break;
default:
processedBytes = 0;
break;
}
return processedBytes;
}
void ModelTree::notifyNewlyCreatedModel(const ModelItem& newModel, const SharedNodePointer& senderNode) {
_newlyCreatedHooksLock.lockForRead();
for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) {
_newlyCreatedHooks[i]->modelCreated(newModel, senderNode);
}
_newlyCreatedHooksLock.unlock();
}
void ModelTree::addNewlyCreatedHook(NewlyCreatedModelHook* hook) {
_newlyCreatedHooksLock.lockForWrite();
_newlyCreatedHooks.push_back(hook);
_newlyCreatedHooksLock.unlock();
}
void ModelTree::removeNewlyCreatedHook(NewlyCreatedModelHook* hook) {
_newlyCreatedHooksLock.lockForWrite();
for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) {
if (_newlyCreatedHooks[i] == hook) {
_newlyCreatedHooks.erase(_newlyCreatedHooks.begin() + i);
break;
}
}
_newlyCreatedHooksLock.unlock();
}
bool ModelTree::updateOperation(OctreeElement* element, void* extraData) {
ModelTreeUpdateArgs* args = static_cast<ModelTreeUpdateArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
modelTreeElement->update(*args);
return true;
}
bool ModelTree::pruneOperation(OctreeElement* element, void* extraData) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
ModelTreeElement* childAt = modelTreeElement->getChildAtIndex(i);
if (childAt && childAt->isLeaf() && !childAt->hasModels()) {
modelTreeElement->deleteChildAtIndex(i);
}
}
return true;
}
void ModelTree::update() {
lockForWrite();
_isDirty = true;
ModelTreeUpdateArgs args;
recurseTreeWithOperation(updateOperation, &args);
// now add back any of the particles that moved elements....
int movingModels = args._movingModels.size();
for (int i = 0; i < movingModels; i++) {
bool shouldDie = args._movingModels[i].getShouldDie();
// if the particle is still inside our total bounds, then re-add it
AACube treeBounds = getRoot()->getAACube();
if (!shouldDie && treeBounds.contains(args._movingModels[i].getPosition())) {
storeModel(args._movingModels[i]);
} else {
uint32_t modelItemID = args._movingModels[i].getID();
quint64 deletedAt = usecTimestampNow();
_recentlyDeletedModelsLock.lockForWrite();
_recentlyDeletedModelItemIDs.insert(deletedAt, modelItemID);
_recentlyDeletedModelsLock.unlock();
}
}
// prune the tree...
recurseTreeWithOperation(pruneOperation, NULL);
unlock();
}
bool ModelTree::hasModelsDeletedSince(quint64 sinceTime) {
// we can probably leverage the ordered nature of QMultiMap to do this quickly...
bool hasSomethingNewer = false;
_recentlyDeletedModelsLock.lockForRead();
QMultiMap<quint64, uint32_t>::const_iterator iterator = _recentlyDeletedModelItemIDs.constBegin();
while (iterator != _recentlyDeletedModelItemIDs.constEnd()) {
//qDebug() << "considering... time/key:" << iterator.key();
if (iterator.key() > sinceTime) {
//qDebug() << "YES newer... time/key:" << iterator.key();
hasSomethingNewer = true;
}
++iterator;
}
_recentlyDeletedModelsLock.unlock();
return hasSomethingNewer;
}
// sinceTime is an in/out parameter - it will be side effected with the last time sent out
bool ModelTree::encodeModelsDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, unsigned char* outputBuffer,
size_t maxLength, size_t& outputLength) {
bool hasMoreToSend = true;
unsigned char* copyAt = outputBuffer;
size_t numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(outputBuffer), PacketTypeModelErase);
copyAt += numBytesPacketHeader;
outputLength = numBytesPacketHeader;
// pack in flags
OCTREE_PACKET_FLAGS flags = 0;
OCTREE_PACKET_FLAGS* flagsAt = (OCTREE_PACKET_FLAGS*)copyAt;
*flagsAt = flags;
copyAt += sizeof(OCTREE_PACKET_FLAGS);
outputLength += sizeof(OCTREE_PACKET_FLAGS);
// pack in sequence number
OCTREE_PACKET_SEQUENCE* sequenceAt = (OCTREE_PACKET_SEQUENCE*)copyAt;
*sequenceAt = sequenceNumber;
copyAt += sizeof(OCTREE_PACKET_SEQUENCE);
outputLength += sizeof(OCTREE_PACKET_SEQUENCE);
// pack in timestamp
OCTREE_PACKET_SENT_TIME now = usecTimestampNow();
OCTREE_PACKET_SENT_TIME* timeAt = (OCTREE_PACKET_SENT_TIME*)copyAt;
*timeAt = now;
copyAt += sizeof(OCTREE_PACKET_SENT_TIME);
outputLength += sizeof(OCTREE_PACKET_SENT_TIME);
uint16_t numberOfIds = 0; // placeholder for now
unsigned char* numberOfIDsAt = copyAt;
memcpy(copyAt, &numberOfIds, sizeof(numberOfIds));
copyAt += sizeof(numberOfIds);
outputLength += sizeof(numberOfIds);
// we keep a multi map of model IDs to timestamps, we only want to include the model IDs that have been
// deleted since we last sent to this node
_recentlyDeletedModelsLock.lockForRead();
QMultiMap<quint64, uint32_t>::const_iterator iterator = _recentlyDeletedModelItemIDs.constBegin();
while (iterator != _recentlyDeletedModelItemIDs.constEnd()) {
QList<uint32_t> values = _recentlyDeletedModelItemIDs.values(iterator.key());
for (int valueItem = 0; valueItem < values.size(); ++valueItem) {
// if the timestamp is more recent then out last sent time, include it
if (iterator.key() > sinceTime) {
uint32_t modelID = values.at(valueItem);
memcpy(copyAt, &modelID, sizeof(modelID));
copyAt += sizeof(modelID);
outputLength += sizeof(modelID);
numberOfIds++;
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
break;
}
}
}
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
// let our caller know how far we got
sinceTime = iterator.key();
break;
}
++iterator;
}
// if we got to the end, then we're done sending
if (iterator == _recentlyDeletedModelItemIDs.constEnd()) {
hasMoreToSend = false;
}
_recentlyDeletedModelsLock.unlock();
// replace the correct count for ids included
memcpy(numberOfIDsAt, &numberOfIds, sizeof(numberOfIds));
return hasMoreToSend;
}
// called by the server when it knows all nodes have been sent deleted packets
void ModelTree::forgetModelsDeletedBefore(quint64 sinceTime) {
//qDebug() << "forgetModelsDeletedBefore()";
QSet<quint64> keysToRemove;
_recentlyDeletedModelsLock.lockForWrite();
QMultiMap<quint64, uint32_t>::iterator iterator = _recentlyDeletedModelItemIDs.begin();
// First find all the keys in the map that are older and need to be deleted
while (iterator != _recentlyDeletedModelItemIDs.end()) {
if (iterator.key() <= sinceTime) {
keysToRemove << iterator.key();
}
++iterator;
}
// Now run through the keysToRemove and remove them
foreach (quint64 value, keysToRemove) {
//qDebug() << "removing the key, _recentlyDeletedModelItemIDs.remove(value); time/key:" << value;
_recentlyDeletedModelItemIDs.remove(value);
}
_recentlyDeletedModelsLock.unlock();
}
void ModelTree::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
const unsigned char* packetData = (const unsigned char*)dataByteArray.constData();
const unsigned char* dataAt = packetData;
size_t packetLength = dataByteArray.size();
size_t numBytesPacketHeader = numBytesForPacketHeader(dataByteArray);
size_t processedBytes = numBytesPacketHeader;
dataAt += numBytesPacketHeader;
dataAt += sizeof(OCTREE_PACKET_FLAGS);
dataAt += sizeof(OCTREE_PACKET_SEQUENCE);
dataAt += sizeof(OCTREE_PACKET_SENT_TIME);
uint16_t numberOfIds = 0; // placeholder for now
memcpy(&numberOfIds, dataAt, sizeof(numberOfIds));
dataAt += sizeof(numberOfIds);
processedBytes += sizeof(numberOfIds);
if (numberOfIds > 0) {
FindAndDeleteModelsArgs args;
for (size_t i = 0; i < numberOfIds; i++) {
if (processedBytes + sizeof(uint32_t) > packetLength) {
break; // bail to prevent buffer overflow
}
uint32_t modelID = 0; // placeholder for now
memcpy(&modelID, dataAt, sizeof(modelID));
dataAt += sizeof(modelID);
processedBytes += sizeof(modelID);
args._idsToDelete.push_back(modelID);
}
// calling recurse to actually delete the models
recurseTreeWithOperation(findAndDeleteOperation, &args);
}
}

View file

@ -1,120 +0,0 @@
//
// ModelTree.h
// 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
//
#ifndef hifi_ModelTree_h
#define hifi_ModelTree_h
#include <Octree.h>
#include "ModelTreeElement.h"
class Model;
class NewlyCreatedModelHook {
public:
virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) = 0;
};
class ModelItemFBXService {
public:
virtual const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) = 0;
virtual const Model* getModelForModelItem(const ModelItem& modelItem) = 0;
};
class ModelTree : public Octree {
Q_OBJECT
public:
ModelTree(bool shouldReaverage = false);
/// Implements our type specific root element factory
virtual ModelTreeElement* createNewElement(unsigned char * octalCode = NULL);
/// Type safe version of getRoot()
ModelTreeElement* getRoot() { return static_cast<ModelTreeElement*>(_rootElement); }
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return true; }
virtual PacketType expectedDataPacketType() const { return PacketTypeModelData; }
virtual bool canProcessVersion(PacketVersion thisVersion) const { return true; } // we support all versions
virtual bool handlesEditPacketType(PacketType packetType) const;
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
virtual bool rootElementHasData() const { return true; }
virtual void update();
void storeModel(const ModelItem& model, const SharedNodePointer& senderNode = SharedNodePointer());
void updateModel(const ModelItemID& modelID, const ModelItemProperties& properties);
void addModel(const ModelItemID& modelID, const ModelItemProperties& properties);
void deleteModel(const ModelItemID& modelID);
const ModelItem* findClosestModel(glm::vec3 position, float targetRadius);
const ModelItem* findModelByID(uint32_t id, bool alreadyLocked = false);
/// finds all models that touch a sphere
/// \param center the center of the sphere
/// \param radius the radius of the sphere
/// \param foundModels[out] vector of const ModelItem*
/// \remark Side effect: any initial contents in foundModels will be lost
void findModels(const glm::vec3& center, float radius, QVector<const ModelItem*>& foundModels);
void findModelsInCube(const AACube& cube, QVector<ModelItem*>& foundModels);
/// finds all models that touch a cube
/// \param cube the query cube
/// \param foundModels[out] vector of non-const ModelItem*
/// \remark Side effect: any initial contents in models will be lost
void findModelsForUpdate(const AACube& cube, QVector<ModelItem*>& foundModels);
void addNewlyCreatedHook(NewlyCreatedModelHook* hook);
void removeNewlyCreatedHook(NewlyCreatedModelHook* hook);
bool hasAnyDeletedModels() const { return _recentlyDeletedModelItemIDs.size() > 0; }
bool hasModelsDeletedSince(quint64 sinceTime);
bool encodeModelsDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength);
void forgetModelsDeletedBefore(quint64 sinceTime);
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
void handleAddModelResponse(const QByteArray& packet);
ModelItemFBXService* getFBXService() const { return _fbxService; }
void setFBXService(ModelItemFBXService* service) { _fbxService = service; }
const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) {
return _fbxService ? _fbxService->getGeometryForModel(modelItem) : NULL;
}
void sendModels(ModelEditPacketSender* packetSender, float x, float y, float z);
private:
static bool sendModelsOperation(OctreeElement* element, void* extraData);
static bool updateOperation(OctreeElement* element, void* extraData);
static bool findInCubeOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData);
static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool findInSphereOperation(OctreeElement* element, void* extraData);
static bool pruneOperation(OctreeElement* element, void* extraData);
static bool findByIDOperation(OctreeElement* element, void* extraData);
static bool findAndDeleteOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateModelItemIDOperation(OctreeElement* element, void* extraData);
static bool findInCubeForUpdateOperation(OctreeElement* element, void* extraData);
void notifyNewlyCreatedModel(const ModelItem& newModel, const SharedNodePointer& senderNode);
QReadWriteLock _newlyCreatedHooksLock;
std::vector<NewlyCreatedModelHook*> _newlyCreatedHooks;
QReadWriteLock _recentlyDeletedModelsLock;
QMultiMap<quint64, uint32_t> _recentlyDeletedModelItemIDs;
ModelItemFBXService* _fbxService;
};
#endif // hifi_ModelTree_h

View file

@ -1,517 +0,0 @@
//
// ModelTreeElement.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 <glm/gtx/transform.hpp>
#include <FBXReader.h>
#include <GeometryUtil.h>
#include "ModelTree.h"
#include "ModelTreeElement.h"
ModelTreeElement::ModelTreeElement(unsigned char* octalCode) : OctreeElement(), _modelItems(NULL) {
init(octalCode);
};
ModelTreeElement::~ModelTreeElement() {
_voxelMemoryUsage -= sizeof(ModelTreeElement);
delete _modelItems;
_modelItems = NULL;
}
// This will be called primarily on addChildAt(), which means we're adding a child of our
// own type to our own tree. This means we should initialize that child with any tree and type
// specific settings that our children must have. One example is out VoxelSystem, which
// we know must match ours.
OctreeElement* ModelTreeElement::createNewElement(unsigned char* octalCode) {
ModelTreeElement* newChild = new ModelTreeElement(octalCode);
newChild->setTree(_myTree);
return newChild;
}
void ModelTreeElement::init(unsigned char* octalCode) {
OctreeElement::init(octalCode);
_modelItems = new QList<ModelItem>;
_voxelMemoryUsage += sizeof(ModelTreeElement);
}
ModelTreeElement* ModelTreeElement::addChildAtIndex(int index) {
ModelTreeElement* newElement = (ModelTreeElement*)OctreeElement::addChildAtIndex(index);
newElement->setTree(_myTree);
return newElement;
}
// TODO: This will attempt to store as many models as will fit in the packetData, if an individual model won't
// fit, but some models did fit, then the element outputs what can fit. Once the general Octree::encodeXXX()
// process supports partial encoding of an octree element, this will need to be updated to handle spanning its
// contents across multiple packets.
bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const {
bool success = true; // assume the best...
// write our models out... first determine which of the models are in view based on our params
uint16_t numberOfModels = 0;
uint16_t actualNumberOfModels = 0;
QVector<uint16_t> indexesOfModelsToInclude;
for (uint16_t i = 0; i < _modelItems->size(); i++) {
if (params.viewFrustum) {
const ModelItem& model = (*_modelItems)[i];
AACube modelCube = model.getAACube();
modelCube.scale(TREE_SCALE);
if (params.viewFrustum->cubeInFrustum(modelCube) != ViewFrustum::OUTSIDE) {
indexesOfModelsToInclude << i;
numberOfModels++;
}
} else {
indexesOfModelsToInclude << i;
numberOfModels++;
}
}
int numberOfModelsOffset = packetData->getUncompressedByteOffset();
success = packetData->appendValue(numberOfModels);
if (success) {
foreach (uint16_t i, indexesOfModelsToInclude) {
const ModelItem& model = (*_modelItems)[i];
LevelDetails modelLevel = packetData->startLevel();
success = model.appendModelData(packetData);
if (success) {
packetData->endLevel(modelLevel);
actualNumberOfModels++;
}
if (!success) {
packetData->discardLevel(modelLevel);
break;
}
}
}
if (!success) {
success = packetData->updatePriorBytes(numberOfModelsOffset,
(const unsigned char*)&actualNumberOfModels, sizeof(actualNumberOfModels));
}
return success;
}
bool ModelTreeElement::containsModelBounds(const ModelItem& model) const {
glm::vec3 clampedMin = glm::clamp(model.getMinimumPoint(), 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(model.getMaximumPoint(), 0.0f, 1.0f);
return _cube.contains(clampedMin) && _cube.contains(clampedMax);
}
bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
glm::vec3 clampedMin = glm::clamp(model.getMinimumPoint(), 0.0f, 1.0f);
glm::vec3 clampedMax = glm::clamp(model.getMaximumPoint(), 0.0f, 1.0f);
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {
int childForMinimumPoint = getMyChildContainingPoint(clampedMin);
int childForMaximumPoint = getMyChildContainingPoint(clampedMax);
// if this is a really small box, then it's close enough!
if (_cube.getScale() <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
return true;
}
// If I contain both the minimum and maximum point, but two different children of mine
// contain those points, then I am the best fit for that model
if (childForMinimumPoint != childForMaximumPoint) {
return true;
}
}
return false;
}
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
args._totalElements++;
// update our contained models
QList<ModelItem>::iterator modelItr = _modelItems->begin();
while(modelItr != _modelItems->end()) {
ModelItem& model = (*modelItr);
args._totalItems++;
// TODO: this _lastChanged isn't actually changing because we're not marking this element as changed.
// how do we want to handle this??? We really only want to consider an element changed when it is
// edited... not just animated...
model.update(_lastChanged);
// If the model wants to die, or if it's left our bounding box, then move it
// into the arguments moving models. These will be added back or deleted completely
if (model.getShouldDie() || !bestFitModelBounds(model)) {
args._movingModels.push_back(model);
// erase this model
modelItr = _modelItems->erase(modelItr);
args._movingItems++;
// this element has changed so mark it...
markWithChangedTime();
} else {
++modelItr;
}
}
}
bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject) {
// only called if we do intersect our bounding cube, but find if we actually intersect with models...
QList<ModelItem>::iterator modelItr = _modelItems->begin();
QList<ModelItem>::const_iterator modelEnd = _modelItems->end();
bool somethingIntersected = false;
while(modelItr != modelEnd) {
ModelItem& model = (*modelItr);
AACube modelCube = model.getAACube();
float localDistance;
BoxFace localFace;
// if the ray doesn't intersect with our cube, we can stop searching!
if (modelCube.findRayIntersection(origin, direction, localDistance, localFace)) {
const FBXGeometry* fbxGeometry = _myTree->getGeometryForModel(model);
if (fbxGeometry && fbxGeometry->meshExtents.isValid()) {
Extents extents = fbxGeometry->meshExtents;
// NOTE: If the model has a bad mesh, then extents will be 0,0,0 & 0,0,0
if (extents.minimum == extents.maximum && extents.minimum == glm::vec3(0,0,0)) {
extents.maximum = glm::vec3(1.0f,1.0f,1.0f); // in this case we will simulate the unit cube
}
// NOTE: these extents are model space, so we need to scale and center them accordingly
// size is our "target size in world space"
// we need to set our model scale so that the extents of the mesh, fit in a cube that size...
float maxDimension = glm::distance(extents.maximum, extents.minimum);
float scale = model.getSize() / maxDimension;
glm::vec3 halfDimensions = (extents.maximum - extents.minimum) * 0.5f;
glm::vec3 offset = -extents.minimum - halfDimensions;
extents.minimum += offset;
extents.maximum += offset;
extents.minimum *= scale;
extents.maximum *= scale;
Extents rotatedExtents = extents;
calculateRotatedExtents(rotatedExtents, model.getModelRotation());
rotatedExtents.minimum += model.getPosition();
rotatedExtents.maximum += model.getPosition();
AABox rotatedExtentsBox(rotatedExtents.minimum, (rotatedExtents.maximum - rotatedExtents.minimum));
// if it's in our AABOX for our rotated extents, then check to see if it's in our non-AABox
if (rotatedExtentsBox.findRayIntersection(origin, direction, localDistance, localFace)) {
// extents is the model relative, scaled, centered extents of the model
glm::mat4 rotation = glm::mat4_cast(model.getModelRotation());
glm::mat4 translation = glm::translate(model.getPosition());
glm::mat4 modelToWorldMatrix = translation * rotation;
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
AABox modelFrameBox(extents.minimum, (extents.maximum - extents.minimum));
glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f));
glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f));
// we can use the AABox's ray intersection by mapping our origin and direction into the model frame
// and testing intersection there.
if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, localDistance, localFace)) {
if (localDistance < distance) {
distance = localDistance;
face = localFace;
*intersectedObject = (void*)(&model);
somethingIntersected = true;
}
}
}
} else if (localDistance < distance) {
distance = localDistance;
face = localFace;
*intersectedObject = (void*)(&model);
somethingIntersected = true;
}
}
++modelItr;
}
return somethingIntersected;
}
bool ModelTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const {
QList<ModelItem>::iterator modelItr = _modelItems->begin();
QList<ModelItem>::const_iterator modelEnd = _modelItems->end();
while(modelItr != modelEnd) {
ModelItem& model = (*modelItr);
glm::vec3 modelCenter = model.getPosition();
float modelRadius = model.getRadius();
// don't penetrate yourself
if (modelCenter == center && modelRadius == radius) {
return false;
}
if (findSphereSpherePenetration(center, radius, modelCenter, modelRadius, penetration)) {
// return true on first valid model penetration
*penetratedObject = (void*)(&model);
return true;
}
++modelItr;
}
return false;
}
bool ModelTreeElement::updateModel(const ModelItem& model) {
// NOTE: this method must first lookup the model by ID, hence it is O(N)
// and "model is not found" is worst-case (full N) but maybe we don't care?
// (guaranteed that num models per elemen is small?)
const bool wantDebug = false;
uint16_t numberOfModels = _modelItems->size();
for (uint16_t i = 0; i < numberOfModels; i++) {
ModelItem& thisModel = (*_modelItems)[i];
if (thisModel.getID() == model.getID()) {
int difference = thisModel.getLastUpdated() - model.getLastUpdated();
bool changedOnServer = thisModel.getLastEdited() < model.getLastEdited();
bool localOlder = thisModel.getLastUpdated() < model.getLastUpdated();
if (changedOnServer || localOlder) {
if (wantDebug) {
qDebug("local model [id:%d] %s and %s than server model by %d, model.isNewlyCreated()=%s",
model.getID(), (changedOnServer ? "CHANGED" : "same"),
(localOlder ? "OLDER" : "NEWER"),
difference, debug::valueOf(model.isNewlyCreated()) );
}
thisModel.copyChangedProperties(model);
markWithChangedTime();
} else {
if (wantDebug) {
qDebug(">>> IGNORING SERVER!!! Would've caused jutter! <<< "
"local model [id:%d] %s and %s than server model by %d, model.isNewlyCreated()=%s",
model.getID(), (changedOnServer ? "CHANGED" : "same"),
(localOlder ? "OLDER" : "NEWER"),
difference, debug::valueOf(model.isNewlyCreated()) );
}
}
return true;
}
}
return false;
}
bool ModelTreeElement::updateModel(const ModelItemID& modelID, const ModelItemProperties& properties) {
uint16_t numberOfModels = _modelItems->size();
for (uint16_t i = 0; i < numberOfModels; i++) {
// note: unlike storeModel() which is called from inbound packets, this is only called by local editors
// and therefore we can be confident that this change is higher priority and should be honored
ModelItem& thisModel = (*_modelItems)[i];
bool found = false;
if (modelID.isKnownID) {
found = thisModel.getID() == modelID.id;
} else {
found = thisModel.getCreatorTokenID() == modelID.creatorTokenID;
}
if (found) {
thisModel.setProperties(properties);
if (_myTree->getGeometryForModel(thisModel)) {
thisModel.setSittingPoints(_myTree->getGeometryForModel(thisModel)->sittingPoints);
}
markWithChangedTime(); // mark our element as changed..
const bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - thisModel.getLastEdited();
qDebug() << "ModelTreeElement::updateModel() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " thisModel.getLastEdited()=" << thisModel.getLastEdited();
}
return true;
}
}
return false;
}
void ModelTreeElement::updateModelItemID(FindAndUpdateModelItemIDArgs* args) {
uint16_t numberOfModels = _modelItems->size();
for (uint16_t i = 0; i < numberOfModels; i++) {
ModelItem& thisModel = (*_modelItems)[i];
if (!args->creatorTokenFound) {
// first, we're looking for matching creatorTokenIDs, if we find that, then we fix it to know the actual ID
if (thisModel.getCreatorTokenID() == args->creatorTokenID) {
thisModel.setID(args->modelID);
args->creatorTokenFound = true;
}
}
// if we're in an isViewing tree, we also need to look for an kill any viewed models
if (!args->viewedModelFound && args->isViewing) {
if (thisModel.getCreatorTokenID() == UNKNOWN_MODEL_TOKEN && thisModel.getID() == args->modelID) {
_modelItems->removeAt(i); // remove the model at this index
numberOfModels--; // this means we have 1 fewer model in this list
i--; // and we actually want to back up i as well.
args->viewedModelFound = true;
}
}
}
}
const ModelItem* ModelTreeElement::getClosestModel(glm::vec3 position) const {
const ModelItem* closestModel = NULL;
float closestModelDistance = FLT_MAX;
uint16_t numberOfModels = _modelItems->size();
for (uint16_t i = 0; i < numberOfModels; i++) {
float distanceToModel = glm::distance(position, (*_modelItems)[i].getPosition());
if (distanceToModel < closestModelDistance) {
closestModel = &(*_modelItems)[i];
}
}
return closestModel;
}
void ModelTreeElement::getModels(const glm::vec3& searchPosition, float searchRadius, QVector<const ModelItem*>& foundModels) const {
uint16_t numberOfModels = _modelItems->size();
for (uint16_t i = 0; i < numberOfModels; i++) {
const ModelItem* model = &(*_modelItems)[i];
float distance = glm::length(model->getPosition() - searchPosition);
if (distance < searchRadius + model->getRadius()) {
foundModels.push_back(model);
}
}
}
void ModelTreeElement::getModelsInside(const AACube& box, QVector<ModelItem*>& foundModels) {
QList<ModelItem>::iterator modelItr = _modelItems->begin();
QList<ModelItem>::iterator modelEnd = _modelItems->end();
AACube modelCube;
while(modelItr != modelEnd) {
ModelItem* model = &(*modelItr);
if (box.contains(model->getPosition())) {
foundModels.push_back(model);
}
++modelItr;
}
}
void ModelTreeElement::getModelsForUpdate(const AACube& box, QVector<ModelItem*>& foundModels) {
QList<ModelItem>::iterator modelItr = _modelItems->begin();
QList<ModelItem>::iterator modelEnd = _modelItems->end();
AACube modelCube;
while(modelItr != modelEnd) {
ModelItem* model = &(*modelItr);
float radius = model->getRadius();
// NOTE: we actually do cube-cube collision queries here, which is sloppy but good enough for now
// TODO: decide whether to replace modelCube-cube query with sphere-cube (requires a square root
// but will be slightly more accurate).
modelCube.setBox(model->getPosition() - glm::vec3(radius), 2.f * radius);
if (modelCube.touches(_cube)) {
foundModels.push_back(model);
}
++modelItr;
}
}
const ModelItem* ModelTreeElement::getModelWithID(uint32_t id) const {
// NOTE: this lookup is O(N) but maybe we don't care? (guaranteed that num models per elemen is small?)
const ModelItem* foundModel = NULL;
uint16_t numberOfModels = _modelItems->size();
for (uint16_t i = 0; i < numberOfModels; i++) {
if ((*_modelItems)[i].getID() == id) {
foundModel = &(*_modelItems)[i];
break;
}
}
return foundModel;
}
bool ModelTreeElement::removeModelWithID(uint32_t id) {
bool foundModel = false;
uint16_t numberOfModels = _modelItems->size();
for (uint16_t i = 0; i < numberOfModels; i++) {
if ((*_modelItems)[i].getID() == id) {
foundModel = true;
_modelItems->removeAt(i);
break;
}
}
return foundModel;
}
int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args) {
// If we're the root, but this bitstream doesn't support root elements with data, then
// return without reading any bytes
if (this == _myTree->getRoot() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
qDebug() << "ROOT ELEMENT: no root data for "
"bitstreamVersion=" << (int)args.bitstreamVersion << " bytesLeftToRead=" << bytesLeftToRead;
return 0;
}
const unsigned char* dataAt = data;
int bytesRead = 0;
uint16_t numberOfModels = 0;
int expectedBytesPerModel = ModelItem::expectedBytes();
if (bytesLeftToRead >= (int)sizeof(numberOfModels)) {
// read our models in....
numberOfModels = *(uint16_t*)dataAt;
dataAt += sizeof(numberOfModels);
bytesLeftToRead -= (int)sizeof(numberOfModels);
bytesRead += sizeof(numberOfModels);
if (bytesLeftToRead >= (int)(numberOfModels * expectedBytesPerModel)) {
for (uint16_t i = 0; i < numberOfModels; i++) {
ModelItem tempModel;
int bytesForThisModel = tempModel.readModelDataFromBuffer(dataAt, bytesLeftToRead, args);
_myTree->storeModel(tempModel);
dataAt += bytesForThisModel;
bytesLeftToRead -= bytesForThisModel;
bytesRead += bytesForThisModel;
}
}
}
return bytesRead;
}
// will average a "common reduced LOD view" from the the child elements...
void ModelTreeElement::calculateAverageFromChildren() {
// nothing to do here yet...
}
// will detect if children are leaves AND collapsable into the parent node
// and in that case will collapse children and make this node
// a leaf, returns TRUE if all the leaves are collapsed into a
// single node
bool ModelTreeElement::collapseChildren() {
// nothing to do here yet...
return false;
}
void ModelTreeElement::storeModel(const ModelItem& model) {
_modelItems->push_back(model);
markWithChangedTime();
}

View file

@ -1,159 +0,0 @@
//
// ModelTreeElement.h
// 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
//
#ifndef hifi_ModelTreeElement_h
#define hifi_ModelTreeElement_h
#include <OctreeElement.h>
#include <QList>
#include "ModelItem.h"
#include "ModelTree.h"
class ModelTree;
class ModelTreeElement;
class ModelTreeUpdateArgs {
public:
ModelTreeUpdateArgs() :
_totalElements(0),
_totalItems(0),
_movingItems(0)
{ }
QList<ModelItem> _movingModels;
int _totalElements;
int _totalItems;
int _movingItems;
};
class FindAndUpdateModelItemIDArgs {
public:
uint32_t modelID;
uint32_t creatorTokenID;
bool creatorTokenFound;
bool viewedModelFound;
bool isViewing;
};
class SendModelsOperationArgs {
public:
glm::vec3 root;
ModelEditPacketSender* packetSender;
};
class ModelTreeElement : public OctreeElement {
friend class ModelTree; // to allow createElement to new us...
ModelTreeElement(unsigned char* octalCode = NULL);
virtual OctreeElement* createNewElement(unsigned char* octalCode = NULL);
public:
virtual ~ModelTreeElement();
// type safe versions of OctreeElement methods
ModelTreeElement* getChildAtIndex(int index) { return (ModelTreeElement*)OctreeElement::getChildAtIndex(index); }
// methods you can and should override to implement your tree functionality
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
virtual ModelTreeElement* addChildAtIndex(int index);
/// Override this to implement LOD averaging on changes to the tree.
virtual void calculateAverageFromChildren();
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
virtual bool collapseChildren();
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
virtual bool hasContent() const { return hasModels(); }
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
virtual bool hasDetailedContent() const { return hasModels(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
virtual void splitChildren() { }
/// Override to indicate that this element requires a split before editing lower elements in the octree
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
/// the element should be rendered, then your rendering engine is rendering. But some rendering engines my have cases
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return !hasModels(); }
virtual bool canRayIntersect() const { return hasModels(); }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject);
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;
const QList<ModelItem>& getModels() const { return *_modelItems; }
QList<ModelItem>& getModels() { return *_modelItems; }
bool hasModels() const { return _modelItems ? _modelItems->size() > 0 : false; }
void update(ModelTreeUpdateArgs& args);
void setTree(ModelTree* tree) { _myTree = tree; }
bool updateModel(const ModelItem& model);
bool updateModel(const ModelItemID& modelID, const ModelItemProperties& properties);
void updateModelItemID(FindAndUpdateModelItemIDArgs* args);
const ModelItem* getClosestModel(glm::vec3 position) const;
/// finds all models that touch a sphere
/// \param position the center of the query sphere
/// \param radius the radius of the query sphere
/// \param models[out] vector of const ModelItem*
void getModels(const glm::vec3& position, float radius, QVector<const ModelItem*>& foundModels) const;
/// finds all models that touch a box
/// \param box the query box
/// \param models[out] vector of non-const ModelItem*
void getModelsForUpdate(const AACube& box, QVector<ModelItem*>& foundModels);
void getModelsInside(const AACube& box, QVector<ModelItem*>& foundModels);
const ModelItem* getModelWithID(uint32_t id) const;
bool removeModelWithID(uint32_t id);
bool containsModelBounds(const ModelItem& model) const;
bool bestFitModelBounds(const ModelItem& model) const;
protected:
virtual void init(unsigned char * octalCode);
void storeModel(const ModelItem& model);
ModelTree* _myTree;
QList<ModelItem>* _modelItems;
};
#endif // hifi_ModelTreeElement_h

View file

@ -1,38 +0,0 @@
//
// ModelTreeHeadlessViewer.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 2/26/14.
// Copyright 2014 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 "ModelTreeHeadlessViewer.h"
ModelTreeHeadlessViewer::ModelTreeHeadlessViewer() :
OctreeHeadlessViewer() {
}
ModelTreeHeadlessViewer::~ModelTreeHeadlessViewer() {
}
void ModelTreeHeadlessViewer::init() {
OctreeHeadlessViewer::init();
}
void ModelTreeHeadlessViewer::update() {
if (_tree) {
ModelTree* tree = static_cast<ModelTree*>(_tree);
if (tree->tryLockForWrite()) {
tree->update();
tree->unlock();
}
}
}
void ModelTreeHeadlessViewer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
static_cast<ModelTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
}

View file

@ -1,286 +0,0 @@
//
// ModelsScriptingInterface.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/6/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 "ModelsScriptingInterface.h"
#include "ModelTree.h"
ModelsScriptingInterface::ModelsScriptingInterface() :
_nextCreatorTokenID(0),
_modelTree(NULL)
{
}
void ModelsScriptingInterface::queueModelMessage(PacketType packetType,
ModelItemID modelID, const ModelItemProperties& properties) {
getModelPacketSender()->queueModelEditMessage(packetType, modelID, properties);
}
ModelItemID ModelsScriptingInterface::addModel(const ModelItemProperties& properties) {
// The application will keep track of creatorTokenID
uint32_t creatorTokenID = ModelItem::getNextCreatorTokenID();
ModelItemID id(NEW_MODEL, creatorTokenID, false );
// queue the packet
queueModelMessage(PacketTypeModelAddOrEdit, id, properties);
// If we have a local model tree set, then also update it.
if (_modelTree) {
_modelTree->lockForWrite();
_modelTree->addModel(id, properties);
_modelTree->unlock();
}
return id;
}
ModelItemID ModelsScriptingInterface::identifyModel(ModelItemID modelID) {
uint32_t actualID = modelID.id;
if (!modelID.isKnownID) {
actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID);
if (actualID == UNKNOWN_MODEL_ID) {
return modelID; // bailing early
}
// found it!
modelID.id = actualID;
modelID.isKnownID = true;
}
return modelID;
}
ModelItemProperties ModelsScriptingInterface::getModelProperties(ModelItemID modelID) {
ModelItemProperties results;
ModelItemID identity = identifyModel(modelID);
if (!identity.isKnownID) {
results.setIsUnknownID();
return results;
}
if (_modelTree) {
_modelTree->lockForRead();
ModelItem* model = const_cast<ModelItem*>(_modelTree->findModelByID(identity.id, true));
if (model && _modelTree->getGeometryForModel(*model)) {
model->setSittingPoints(_modelTree->getGeometryForModel(*model)->sittingPoints);
results.copyFromModelItem(*model);
} else {
results.setIsUnknownID();
}
_modelTree->unlock();
}
return results;
}
ModelItemID ModelsScriptingInterface::editModel(ModelItemID modelID, const ModelItemProperties& properties) {
uint32_t actualID = modelID.id;
// if the model is unknown, attempt to look it up
if (!modelID.isKnownID) {
actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID);
}
// if at this point, we know the id, send the update to the model server
if (actualID != UNKNOWN_MODEL_ID) {
modelID.id = actualID;
modelID.isKnownID = true;
queueModelMessage(PacketTypeModelAddOrEdit, modelID, properties);
}
// If we have a local model tree set, then also update it. We can do this even if we don't know
// the actual id, because we can edit out local models just with creatorTokenID
if (_modelTree) {
_modelTree->lockForWrite();
_modelTree->updateModel(modelID, properties);
_modelTree->unlock();
}
return modelID;
}
// TODO: This deleteModel() method uses the PacketType_MODEL_ADD_OR_EDIT message to send
// a changed model with a shouldDie() property set to true. This works and is currently the only
// way to tell the model server to delete a model. But we should change this to use the PacketType_MODEL_ERASE
// message which takes a list of model id's to delete.
void ModelsScriptingInterface::deleteModel(ModelItemID modelID) {
// setup properties to kill the model
ModelItemProperties properties;
properties.setShouldDie(true);
uint32_t actualID = modelID.id;
// if the model is unknown, attempt to look it up
if (!modelID.isKnownID) {
actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID);
}
// if at this point, we know the id, send the update to the model server
if (actualID != UNKNOWN_MODEL_ID) {
modelID.id = actualID;
modelID.isKnownID = true;
queueModelMessage(PacketTypeModelAddOrEdit, modelID, properties);
}
// If we have a local model tree set, then also update it.
if (_modelTree) {
_modelTree->lockForWrite();
_modelTree->deleteModel(modelID);
_modelTree->unlock();
}
}
ModelItemID ModelsScriptingInterface::findClosestModel(const glm::vec3& center, float radius) const {
ModelItemID result(UNKNOWN_MODEL_ID, UNKNOWN_MODEL_TOKEN, false);
if (_modelTree) {
_modelTree->lockForRead();
const ModelItem* closestModel = _modelTree->findClosestModel(center/(float)TREE_SCALE,
radius/(float)TREE_SCALE);
_modelTree->unlock();
if (closestModel) {
result.id = closestModel->getID();
result.isKnownID = true;
}
}
return result;
}
QVector<ModelItemID> ModelsScriptingInterface::findModels(const glm::vec3& center, float radius) const {
QVector<ModelItemID> result;
if (_modelTree) {
QVector<const ModelItem*> models;
_modelTree->findModels(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, models);
foreach (const ModelItem* model, models) {
ModelItemID thisModelItemID(model->getID(), UNKNOWN_MODEL_TOKEN, true);
result << thisModelItemID;
}
}
return result;
}
RayToModelIntersectionResult ModelsScriptingInterface::findRayIntersection(const PickRay& ray) {
return findRayIntersectionWorker(ray, Octree::TryLock);
}
RayToModelIntersectionResult ModelsScriptingInterface::findRayIntersectionBlocking(const PickRay& ray) {
return findRayIntersectionWorker(ray, Octree::Lock);
}
RayToModelIntersectionResult ModelsScriptingInterface::findRayIntersectionWorker(const PickRay& ray,
Octree::lockType lockType) {
RayToModelIntersectionResult result;
if (_modelTree) {
OctreeElement* element;
ModelItem* intersectedModel;
result.intersects = _modelTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
(void**)&intersectedModel, lockType, &result.accurate);
if (result.intersects && intersectedModel) {
result.modelID = intersectedModel->getModelItemID();
result.modelProperties = intersectedModel->getProperties();
result.intersection = ray.origin + (ray.direction * result.distance);
}
}
return result;
}
RayToModelIntersectionResult::RayToModelIntersectionResult() :
intersects(false),
accurate(true), // assume it's accurate
modelID(),
modelProperties(),
distance(0),
face()
{
};
QScriptValue RayToModelIntersectionResultToScriptValue(QScriptEngine* engine, const RayToModelIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
obj.setProperty("accurate", value.accurate);
QScriptValue modelItemValue = ModelItemIDtoScriptValue(engine, value.modelID);
obj.setProperty("modelID", modelItemValue);
QScriptValue modelPropertiesValue = ModelItemPropertiesToScriptValue(engine, value.modelProperties);
obj.setProperty("modelProperties", modelPropertiesValue);
obj.setProperty("distance", value.distance);
QString faceName = "";
// handle BoxFace
switch (value.face) {
case MIN_X_FACE:
faceName = "MIN_X_FACE";
break;
case MAX_X_FACE:
faceName = "MAX_X_FACE";
break;
case MIN_Y_FACE:
faceName = "MIN_Y_FACE";
break;
case MAX_Y_FACE:
faceName = "MAX_Y_FACE";
break;
case MIN_Z_FACE:
faceName = "MIN_Z_FACE";
break;
case MAX_Z_FACE:
faceName = "MAX_Z_FACE";
break;
case UNKNOWN_FACE:
faceName = "UNKNOWN_FACE";
break;
}
obj.setProperty("face", faceName);
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
return obj;
}
void RayToModelIntersectionResultFromScriptValue(const QScriptValue& object, RayToModelIntersectionResult& value) {
value.intersects = object.property("intersects").toVariant().toBool();
value.accurate = object.property("accurate").toVariant().toBool();
QScriptValue modelIDValue = object.property("modelID");
if (modelIDValue.isValid()) {
ModelItemIDfromScriptValue(modelIDValue, value.modelID);
}
QScriptValue modelPropertiesValue = object.property("modelProperties");
if (modelPropertiesValue.isValid()) {
ModelItemPropertiesFromScriptValue(modelPropertiesValue, value.modelProperties);
}
value.distance = object.property("distance").toVariant().toFloat();
QString faceName = object.property("face").toVariant().toString();
if (faceName == "MIN_X_FACE") {
value.face = MIN_X_FACE;
} else if (faceName == "MAX_X_FACE") {
value.face = MAX_X_FACE;
} else if (faceName == "MIN_Y_FACE") {
value.face = MIN_Y_FACE;
} else if (faceName == "MAX_Y_FACE") {
value.face = MAX_Y_FACE;
} else if (faceName == "MIN_Z_FACE") {
value.face = MIN_Z_FACE;
} else {
value.face = MAX_Z_FACE;
};
QScriptValue intersection = object.property("intersection");
if (intersection.isValid()) {
vec3FromScriptValue(intersection, value.intersection);
}
}

View file

@ -1,106 +0,0 @@
//
// ModelsScriptingInterface.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/6/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
//
#ifndef hifi_ModelsScriptingInterface_h
#define hifi_ModelsScriptingInterface_h
#include <QtCore/QObject>
#include <CollisionInfo.h>
#include <Octree.h>
#include <OctreeScriptingInterface.h>
#include <RegisteredMetaTypes.h>
#include "ModelEditPacketSender.h"
class RayToModelIntersectionResult {
public:
RayToModelIntersectionResult();
bool intersects;
bool accurate;
ModelItemID modelID;
ModelItemProperties modelProperties;
float distance;
BoxFace face;
glm::vec3 intersection;
};
Q_DECLARE_METATYPE(RayToModelIntersectionResult)
QScriptValue RayToModelIntersectionResultToScriptValue(QScriptEngine* engine, const RayToModelIntersectionResult& results);
void RayToModelIntersectionResultFromScriptValue(const QScriptValue& object, RayToModelIntersectionResult& results);
/// handles scripting of Model commands from JS passed to assigned clients
class ModelsScriptingInterface : public OctreeScriptingInterface {
Q_OBJECT
public:
ModelsScriptingInterface();
ModelEditPacketSender* getModelPacketSender() const { return (ModelEditPacketSender*)getPacketSender(); }
virtual NodeType_t getServerNodeType() const { return NodeType::ModelServer; }
virtual OctreeEditPacketSender* createPacketSender() { return new ModelEditPacketSender(); }
void setModelTree(ModelTree* modelTree) { _modelTree = modelTree; }
ModelTree* getModelTree(ModelTree*) { return _modelTree; }
public slots:
/// adds a model with the specific properties
ModelItemID addModel(const ModelItemProperties& properties);
/// identify a recently created model to determine its true ID
ModelItemID identifyModel(ModelItemID modelID);
/// gets the current model properties for a specific model
/// this function will not find return results in script engine contexts which don't have access to models
ModelItemProperties getModelProperties(ModelItemID modelID);
/// edits a model updating only the included properties, will return the identified ModelItemID in case of
/// successful edit, if the input modelID is for an unknown model this function will have no effect
ModelItemID editModel(ModelItemID modelID, const ModelItemProperties& properties);
/// deletes a model
void deleteModel(ModelItemID modelID);
/// finds the closest model to the center point, within the radius
/// will return a ModelItemID.isKnownID = false if no models are in the radius
/// this function will not find any models in script engine contexts which don't have access to models
ModelItemID findClosestModel(const glm::vec3& center, float radius) const;
/// finds models within the search sphere specified by the center point and radius
/// this function will not find any models in script engine contexts which don't have access to models
QVector<ModelItemID> findModels(const glm::vec3& center, float radius) const;
/// If the scripting context has visible voxels, this will determine a ray intersection, the results
/// may be inaccurate if the engine is unable to access the visible voxels, in which case result.accurate
/// will be false.
Q_INVOKABLE RayToModelIntersectionResult findRayIntersection(const PickRay& ray);
/// If the scripting context has visible voxels, this will determine a ray intersection, and will block in
/// order to return an accurate result
Q_INVOKABLE RayToModelIntersectionResult findRayIntersectionBlocking(const PickRay& ray);
signals:
void modelCollisionWithVoxel(const ModelItemID& modelID, const VoxelDetail& voxel, const CollisionInfo& collision);
void modelCollisionWithModel(const ModelItemID& idA, const ModelItemID& idB, const CollisionInfo& collision);
private:
void queueModelMessage(PacketType packetType, ModelItemID modelID, const ModelItemProperties& properties);
/// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode
RayToModelIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType);
uint32_t _nextCreatorTokenID;
ModelTree* _modelTree;
};
#endif // hifi_ModelsScriptingInterface_h

Some files were not shown because too many files have changed in this diff Show more