first cut at modelserver

This commit is contained in:
ZappoMan 2014-04-30 09:26:14 -07:00
parent aac42058ac
commit f993f984c9
17 changed files with 3625 additions and 0 deletions

View file

@ -0,0 +1,34 @@
//
// 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

@ -0,0 +1,137 @@
//
// 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) {
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?
while (hasMoreToSend) {
hasMoreToSend = tree->encodeModelsDeletedSince(deletedModelsSentAt,
outputBuffer, MAX_PACKET_SIZE, packetLength);
//qDebug() << "sending PacketType_MODEL_ERASE packetLength:" << packetLength;
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node));
}
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

@ -0,0 +1,50 @@
//
// ModelServer.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_ModelServer_h
#define hifi_ModelServer_h
#include "../octree/OctreeServer.h"
#include "ModelItem.h"
#include "ModelServerConsts.h"
#include "ModelTree.h"
/// Handles assignments of type ModelServer - sending models to various clients.
class ModelServer : public OctreeServer, public NewlyCreatedModelHook {
Q_OBJECT
public:
ModelServer(const QByteArray& packet);
~ModelServer();
// Subclasses must implement these methods
virtual OctreeQueryNode* createOctreeQueryNode();
virtual Octree* createTree();
virtual unsigned char getMyNodeType() const { return NodeType::ModelServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
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; }
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node);
virtual int sendSpecialPacket(const SharedNodePointer& node);
virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode);
public slots:
void pruneDeletedModels();
private:
};
#endif // hifi_ModelServer_h

View file

@ -0,0 +1,19 @@
//
// ModelServerConsts.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_ModelServerConsts_h
#define hifi_ModelServerConsts_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

View file

@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 2.8)
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(ROOT_DIR ../..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
set(TARGET_NAME models)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB and GnuTLS
find_package(ZLIB)
find_package(GnuTLS REQUIRED)
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")

View file

@ -0,0 +1,60 @@
//
// ModelEditPacketSender.cpp
// libraries/particles/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, ssize_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

@ -0,0 +1,37 @@
//
// ModelEditPacketSender.h
// libraries/particles/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 particle 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 particle server
virtual unsigned char getMyNodeType() const { return NodeType::ModelServer; }
virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew);
};
#endif // hifi_ModelEditPacketSender_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,362 @@
//
// ModelItem.h
// libraries/particles/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 <glm/glm.hpp>
#include <stdint.h>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <OctreePacketData.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_VELOCITY = 8;
const uint16_t MODEL_PACKET_CONTAINS_GRAVITY = 16;
const uint16_t MODEL_PACKET_CONTAINS_DAMPING = 32;
const uint16_t MODEL_PACKET_CONTAINS_LIFETIME = 64;
const uint16_t MODEL_PACKET_CONTAINS_INHAND = 128;
const uint16_t MODEL_PACKET_CONTAINS_SCRIPT = 256;
const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_TRANSLATION = 1024;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048;
const uint16_t MODEL_PACKET_CONTAINS_MODEL_SCALE = 4096;
const float MODEL_DEFAULT_LIFETIME = 10.0f; // particles live for 10 seconds by default
const float MODEL_DEFAULT_DAMPING = 0.99f;
const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
const float MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
const glm::vec3 MODEL_DEFAULT_GRAVITY(0, (-9.8f / TREE_SCALE), 0);
const QString MODEL_DEFAULT_SCRIPT("");
const QString MODEL_DEFAULT_MODEL_URL("");
const glm::vec3 MODEL_DEFAULT_MODEL_TRANSLATION(0, 0, 0);
const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0);
const float MODEL_DEFAULT_MODEL_SCALE = 1.0f;
const bool MODEL_IN_HAND = true; // it's in a hand
const bool MODEL_NOT_IN_HAND = !MODEL_IN_HAND; // it's not in a hand
/// A collection of properties of a particle used in the scripting API. Translates between the actual properties of a particle
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of
/// particle properties via JavaScript hashes/QScriptValues
/// all units for position, velocity, gravity, radius, etc are in meter units
class ModelItemProperties {
public:
ModelItemProperties();
QScriptValue copyToScriptValue(QScriptEngine* engine) const;
void copyFromScriptValue(const QScriptValue& object);
void copyToModelItem(ModelItem& particle) const;
void copyFromModelItem(const ModelItem& particle);
const glm::vec3& getPosition() const { return _position; }
xColor getColor() const { return _color; }
float getRadius() const { return _radius; }
const glm::vec3& getVelocity() const { return _velocity; }
const glm::vec3& getGravity() const { return _gravity; }
float getDamping() const { return _damping; }
float getLifetime() const { return _lifetime; }
const QString& getScript() const { return _script; }
bool getInHand() const { return _inHand; }
bool getShouldDie() const { return _shouldDie; }
const QString& getModelURL() const { return _modelURL; }
float getModelScale() const { return _modelScale; }
const glm::vec3& getModelTranslation() const { return _modelTranslation; }
const glm::quat& getModelRotation() const { return _modelRotation; }
quint64 getLastEdited() const { return _lastEdited; }
uint16_t getChangedBits() const;
/// set position in meter units
void setPosition(const glm::vec3& value) { _position = value; _positionChanged = true; }
/// set velocity in meter units
void setVelocity(const glm::vec3& value) { _velocity = value; _velocityChanged = true; }
void setColor(const xColor& value) { _color = value; _colorChanged = true; }
void setRadius(float value) { _radius = value; _radiusChanged = true; }
/// set gravity in meter units
void setGravity(const glm::vec3& value) { _gravity = value; _gravityChanged = true; }
void setInHand(bool inHand) { _inHand = inHand; _inHandChanged = true; }
void setDamping(float value) { _damping = value; _dampingChanged = true; }
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; }
void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; }
void setScript(const QString& updateScript) { _script = updateScript; _scriptChanged = true; }
// model related properties
void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; }
void setModelScale(float scale) { _modelScale = scale; _modelScaleChanged = true; }
void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation;
_modelTranslationChanged = true; }
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; }
/// used by ModelScriptingInterface to return ModelItemProperties for unknown particles
void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; }
private:
glm::vec3 _position;
xColor _color;
float _radius;
glm::vec3 _velocity;
glm::vec3 _gravity;
float _damping;
float _lifetime;
QString _script;
bool _inHand;
bool _shouldDie;
QString _modelURL;
float _modelScale;
glm::vec3 _modelTranslation;
glm::quat _modelRotation;
uint32_t _id;
bool _idSet;
quint64 _lastEdited;
bool _positionChanged;
bool _colorChanged;
bool _radiusChanged;
bool _velocityChanged;
bool _gravityChanged;
bool _dampingChanged;
bool _lifetimeChanged;
bool _scriptChanged;
bool _inHandChanged;
bool _shouldDieChanged;
bool _modelURLChanged;
bool _modelScaleChanged;
bool _modelTranslationChanged;
bool _modelRotationChanged;
bool _defaultSettings;
};
Q_DECLARE_METATYPE(ModelItemProperties);
QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties);
void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties);
/// Abstract ID for editing particles. Used in ModelItem JS API - When particles are created in the JS api, they are given a
/// local creatorTokenID, the actual id for the particle 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 particles 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 particle class.
class ModelItem {
public:
ModelItem();
ModelItem(const ModelItemID& particleID, const ModelItemProperties& properties);
/// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD_OR_EDIT edit 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, glm::vec3 velocity,
glm::vec3 gravity = MODEL_DEFAULT_GRAVITY, float damping = MODEL_DEFAULT_DAMPING, float lifetime = MODEL_DEFAULT_LIFETIME,
bool inHand = MODEL_NOT_IN_HAND, QString updateScript = MODEL_DEFAULT_SCRIPT, uint32_t id = NEW_MODEL);
/// get position in domain scale units (0.0 - 1.0)
const glm::vec3& getPosition() const { return _position; }
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; }
float getMass() const { return _mass; }
/// get velocity in domain scale units (0.0 - 1.0)
const glm::vec3& getVelocity() const { return _velocity; }
/// get gravity in domain scale units (0.0 - 1.0)
const glm::vec3& getGravity() const { return _gravity; }
bool getInHand() const { return _inHand; }
float getDamping() const { return _damping; }
float getLifetime() const { return _lifetime; }
// model related properties
bool hasModel() const { return !_modelURL.isEmpty(); }
const QString& getModelURL() const { return _modelURL; }
float getModelScale() const { return _modelScale; }
const glm::vec3& getModelTranslation() const { return _modelTranslation; }
const glm::quat& getModelRotation() const { return _modelRotation; }
ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); }
ModelItemProperties getProperties() const;
/// The last updated/simulated time of this particle from the time perspective of the authoritative server/source
quint64 getLastUpdated() const { return _lastUpdated; }
/// The last edited time of this particle from the time perspective of the authoritative server/source
quint64 getLastEdited() const { return _lastEdited; }
void setLastEdited(quint64 lastEdited) { _lastEdited = lastEdited; }
/// lifetime of the particle in seconds
float getAge() const { return static_cast<float>(usecTimestampNow() - _created) / static_cast<float>(USECS_PER_SECOND); }
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; }
QString getScript() const { return _script; }
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
bool isNewlyCreated() const { return _newlyCreated; }
/// set position in domain scale units (0.0 - 1.0)
void setPosition(const glm::vec3& value) { _position = value; }
/// set velocity in domain scale units (0.0 - 1.0)
void setVelocity(const glm::vec3& value) { _velocity = 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 setMass(float value);
/// set gravity in domain scale units (0.0 - 1.0)
void setGravity(const glm::vec3& value) { _gravity = value; }
void setInHand(bool inHand) { _inHand = inHand; }
void setDamping(float value) { _damping = value; }
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; }
void setLifetime(float value) { _lifetime = value; }
void setScript(QString updateScript) { _script = updateScript; }
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
// model related properties
void setModelURL(const QString& url) { _modelURL = url; }
void setModelScale(float scale) { _modelScale = scale; }
void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; }
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; }
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, ssize_t length, int clockSkew);
void applyHardCollision(const CollisionInfo& collisionInfo);
void update(const quint64& now);
void debugDump() const;
// similar to assignment/copy, but it handles keeping lifetime accurate
void copyChangedProperties(const ModelItem& other);
static VoxelEditPacketSender* getVoxelEditPacketSender() { return _voxelEditSender; }
static ModelEditPacketSender* getModelEditPacketSender() { return _particleEditSender; }
static void setVoxelEditPacketSender(VoxelEditPacketSender* senderInterface)
{ _voxelEditSender = senderInterface; }
static void setModelEditPacketSender(ModelEditPacketSender* senderInterface)
{ _particleEditSender = senderInterface; }
// these methods allow you to create particles, and later edit them.
static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID);
static uint32_t getNextCreatorTokenID();
static void handleAddModelResponse(const QByteArray& packet);
protected:
static VoxelEditPacketSender* _voxelEditSender;
static ModelEditPacketSender* _particleEditSender;
void setAge(float age);
glm::vec3 _position;
rgbColor _color;
float _radius;
float _mass;
glm::vec3 _velocity;
uint32_t _id;
static uint32_t _nextID;
bool _shouldDie;
glm::vec3 _gravity;
float _damping;
float _lifetime;
QString _script;
bool _inHand;
// model related items
QString _modelURL;
float _modelScale;
glm::vec3 _modelTranslation;
glm::quat _modelRotation;
uint32_t _creatorTokenID;
bool _newlyCreated;
quint64 _lastUpdated;
quint64 _lastEdited;
// this doesn't go on the wire, we send it as lifetime
quint64 _created;
// used by the static interfaces for creator token ids
static uint32_t _nextCreatorTokenID;
static std::map<uint32_t,uint32_t> _tokenIDsToIDs;
};
#endif // hifi_ModelItem_h

View file

@ -0,0 +1,636 @@
//
// 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 "ModelTree.h"
ModelTree::ModelTree(bool shouldReaverage) : Octree(shouldReaverage) {
_rootNode = 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 FindAndUpdateModelArgs {
public:
const ModelItem& searchModel;
bool found;
};
bool ModelTree::findAndUpdateOperation(OctreeElement* element, void* extraData) {
FindAndUpdateModelArgs* args = static_cast<FindAndUpdateModelArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
// Note: updateModel() will only operate on correctly found models
if (modelTreeElement->updateModel(args->searchModel)) {
args->found = true;
return false; // stop searching
}
return true;
}
void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) {
// First, look for the existing model in the tree..
FindAndUpdateModelArgs args = { model, false };
recurseTreeWithOperation(findAndUpdateOperation, &args);
// if we didn't find it in the tree, then store it...
if (!args.found) {
glm::vec3 position = model.getPosition();
float size = std::max(MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE, model.getRadius());
ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size);
element->storeModel(model);
}
// what else do we need to do here to get reaveraging to work
_isDirty = true;
}
class FindAndUpdateModelWithIDandPropertiesArgs {
public:
const ModelItemID& modelID;
const ModelItemProperties& properties;
bool found;
};
bool ModelTree::findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData) {
FindAndUpdateModelWithIDandPropertiesArgs* args = static_cast<FindAndUpdateModelWithIDandPropertiesArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
// Note: updateModel() will only operate on correctly found models
if (modelTreeElement->updateModel(args->modelID, args->properties)) {
args->found = true;
return false; // stop searching
}
// if we've found our model stop searching
if (args->found) {
return false;
}
return true;
}
void ModelTree::updateModel(const ModelItemID& modelID, const ModelItemProperties& properties) {
// First, look for the existing model in the tree..
FindAndUpdateModelWithIDandPropertiesArgs args = { modelID, properties, false };
recurseTreeWithOperation(findAndUpdateWithIDandPropertiesOperation, &args);
// if we found it in the tree, then mark the tree as dirty
if (args.found) {
_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(MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE, model.getRadius());
ModelTreeElement* element = (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);
}
}
// 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->getAABox().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->getAABox().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 FindModelsInBoxArgs {
public:
FindModelsInBoxArgs(const AABox& box)
: _box(box), _foundModels() {
}
AABox _box;
QVector<ModelItem*> _foundModels;
};
bool ModelTree::findInBoxForUpdateOperation(OctreeElement* element, void* extraData) {
FindModelsInBoxArgs* args = static_cast< FindModelsInBoxArgs*>(extraData);
const AABox& elementBox = element->getAABox();
if (elementBox.touches(args->_box)) {
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
modelTreeElement->getModelsForUpdate(args->_box, args->_foundModels);
return true;
}
return false;
}
void ModelTree::findModelsForUpdate(const AABox& box, QVector<ModelItem*> foundModels) {
FindModelsInBoxArgs args(box);
lockForRead();
recurseTreeWithOperation(findInBoxForUpdateOperation, &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 PACKET_TYPE_PARTICLE_ERASE messages
// instead of using PACKET_TYPE_PARTICLE_ADD_OR_EDIT 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 models that moved elements....
int movingModels = args._movingModels.size();
for (int i = 0; i < movingModels; i++) {
bool shouldDie = args._movingModels[i].getShouldDie();
// if the model is still inside our total bounds, then re-add it
AABox treeBounds = getRoot()->getAABox();
if (!shouldDie && treeBounds.contains(args._movingModels[i].getPosition())) {
storeModel(args._movingModels[i]);
} else {
uint32_t modelID = args._movingModels[i].getID();
quint64 deletedAt = usecTimestampNow();
_recentlyDeletedModelsLock.lockForWrite();
_recentlyDeletedModelItemIDs.insert(deletedAt, modelID);
_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(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;
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;
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

@ -0,0 +1,99 @@
//
// 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 NewlyCreatedModelHook {
public:
virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) = 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 (ModelTreeElement*)_rootNode; }
// 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 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 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);
/// finds all models that touch a box
/// \param box the query box
/// \param foundModels[out] vector of non-const ModelItem*
/// \remark Side effect: any initial contents in models will be lost
void findModelsForUpdate(const AABox& box, QVector<ModelItem*> foundModels);
void addNewlyCreatedHook(NewlyCreatedModelHook* hook);
void removeNewlyCreatedHook(NewlyCreatedModelHook* hook);
bool hasAnyDeletedModels() const { return _recentlyDeletedModelItemIDs.size() > 0; }
bool hasModelsDeletedSince(quint64 sinceTime);
bool encodeModelsDeletedSince(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);
private:
static bool updateOperation(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 findInBoxForUpdateOperation(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;
};
#endif // hifi_ModelTree_h

View file

@ -0,0 +1,340 @@
//
// 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 <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;
}
bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const {
bool success = true; // assume the best...
// write our models out...
uint16_t numberOfModels = _modelItems->size();
success = packetData->appendValue(numberOfModels);
if (success) {
for (uint16_t i = 0; i < numberOfModels; i++) {
const ModelItem& model = (*_modelItems)[i];
success = model.appendModelData(packetData);
if (!success) {
break;
}
}
}
return success;
}
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
markWithChangedTime();
// TODO: early exit when _modelItems is empty
// update our contained models
QList<ModelItem>::iterator modelItr = _modelItems->begin();
while(modelItr != _modelItems->end()) {
ModelItem& model = (*modelItr);
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() || !_box.contains(model.getPosition())) {
args._movingModels.push_back(model);
// erase this model
modelItr = _modelItems->erase(modelItr);
} else {
++modelItr;
}
}
// TODO: if _modelItems is empty after while loop consider freeing memory in _modelItems if
// internal array is too big (QList internal array does not decrease size except in dtor and
// assignment operator). Otherwise _modelItems could become a "resource leak" for large
// roaming piles of models.
}
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;
}
// We've considered making "inHand" models not collide, if we want to do that,
// we should change this setting... but now, we do allow inHand models to collide
const bool IN_HAND_PARTICLES_DONT_COLLIDE = false;
if (IN_HAND_PARTICLES_DONT_COLLIDE) {
// don't penetrate if the model is "inHand" -- they don't collide
if (model.getInHand()) {
++modelItr;
continue;
}
}
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);
} 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);
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::getModelsForUpdate(const AABox& box, QVector<ModelItem*>& foundModels) {
QList<ModelItem>::iterator modelItr = _modelItems->begin();
QList<ModelItem>::iterator modelEnd = _modelItems->end();
AABox modelBox;
while(modelItr != modelEnd) {
ModelItem* model = &(*modelItr);
float radius = model->getRadius();
// NOTE: we actually do box-box collision queries here, which is sloppy but good enough for now
// TODO: decide whether to replace modelBox-box query with sphere-box (requires a square root
// but will be slightly more accurate).
modelBox.setBox(model->getPosition() - glm::vec3(radius), 2.f * radius);
if (modelBox.touches(_box)) {
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) {
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

@ -0,0 +1,130 @@
//
// 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:
QList<ModelItem> _movingModels;
};
class FindAndUpdateModelItemIDArgs {
public:
uint32_t modelID;
uint32_t creatorTokenID;
bool creatorTokenFound;
bool viewedModelFound;
bool isViewing;
};
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 isLeaf(); }
/// 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) 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 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->size() > 0; }
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 AABox& box, QVector<ModelItem*>& foundModels);
const ModelItem* getModelWithID(uint32_t id) const;
bool removeModelWithID(uint32_t id);
protected:
virtual void init(unsigned char * octalCode);
void storeModel(const ModelItem& model);
ModelTree* _myTree;
QList<ModelItem>* _modelItems;
};
#endif // hifi_ModelTreeElement_h

View file

@ -0,0 +1,38 @@
//
// 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

@ -0,0 +1,45 @@
//
// ModelTreeHeadlessViewer.h
// 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
//
#ifndef hifi_ModelTreeHeadlessViewer_h
#define hifi_ModelTreeHeadlessViewer_h
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeHeadlessViewer.h>
#include <ViewFrustum.h>
#include "ModelTree.h"
// Generic client side Octree renderer class.
class ModelTreeHeadlessViewer : public OctreeHeadlessViewer {
Q_OBJECT
public:
ModelTreeHeadlessViewer();
virtual ~ModelTreeHeadlessViewer();
virtual Octree* createTree() { return new ModelTree(true); }
virtual NodeType_t getMyNodeType() const { return NodeType::ModelServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; }
void update();
ModelTree* getTree() { return (ModelTree*)_tree; }
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
};
#endif // hifi_ModelTreeHeadlessViewer_h

View file

@ -0,0 +1,176 @@
//
// 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();
const ModelItem* model = _modelTree->findModelByID(identity.id, true);
if (model) {
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) {
_modelTree->lockForRead();
QVector<const ModelItem*> models;
_modelTree->findModels(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, models);
_modelTree->unlock();
foreach (const ModelItem* model, models) {
ModelItemID thisModelItemID(model->getID(), UNKNOWN_MODEL_TOKEN, true);
result << thisModelItemID;
}
}
return result;
}

View file

@ -0,0 +1,73 @@
//
// 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 <OctreeScriptingInterface.h>
#include "ModelEditPacketSender.h"
/// 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;
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);
uint32_t _nextCreatorTokenID;
ModelTree* _modelTree;
};
#endif // hifi_ModelsScriptingInterface_h