mirror of
https://github.com/overte-org/overte.git
synced 2025-04-15 16:02:08 +02:00
first cut at modelserver
This commit is contained in:
parent
aac42058ac
commit
f993f984c9
17 changed files with 3625 additions and 0 deletions
34
assignment-client/src/models/ModelNodeData.h
Normal file
34
assignment-client/src/models/ModelNodeData.h
Normal 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
|
137
assignment-client/src/models/ModelServer.cpp
Normal file
137
assignment-client/src/models/ModelServer.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
50
assignment-client/src/models/ModelServer.h
Normal file
50
assignment-client/src/models/ModelServer.h
Normal 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
|
19
assignment-client/src/models/ModelServerConsts.h
Normal file
19
assignment-client/src/models/ModelServerConsts.h
Normal 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
|
39
libraries/models/CMakeLists.txt
Normal file
39
libraries/models/CMakeLists.txt
Normal 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}")
|
60
libraries/models/src/ModelEditPacketSender.cpp
Normal file
60
libraries/models/src/ModelEditPacketSender.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
37
libraries/models/src/ModelEditPacketSender.h
Normal file
37
libraries/models/src/ModelEditPacketSender.h
Normal 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
|
1350
libraries/models/src/ModelItem.cpp
Normal file
1350
libraries/models/src/ModelItem.cpp
Normal file
File diff suppressed because it is too large
Load diff
362
libraries/models/src/ModelItem.h
Normal file
362
libraries/models/src/ModelItem.h
Normal 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
|
636
libraries/models/src/ModelTree.cpp
Normal file
636
libraries/models/src/ModelTree.cpp
Normal 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);
|
||||
}
|
||||
}
|
99
libraries/models/src/ModelTree.h
Normal file
99
libraries/models/src/ModelTree.h
Normal 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
|
340
libraries/models/src/ModelTreeElement.cpp
Normal file
340
libraries/models/src/ModelTreeElement.cpp
Normal 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();
|
||||
}
|
||||
|
130
libraries/models/src/ModelTreeElement.h
Normal file
130
libraries/models/src/ModelTreeElement.h
Normal 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
|
38
libraries/models/src/ModelTreeHeadlessViewer.cpp
Normal file
38
libraries/models/src/ModelTreeHeadlessViewer.cpp
Normal 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);
|
||||
}
|
45
libraries/models/src/ModelTreeHeadlessViewer.h
Normal file
45
libraries/models/src/ModelTreeHeadlessViewer.h
Normal 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
|
176
libraries/models/src/ModelsScriptingInterface.cpp
Normal file
176
libraries/models/src/ModelsScriptingInterface.cpp
Normal 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;
|
||||
}
|
||||
|
73
libraries/models/src/ModelsScriptingInterface.h
Normal file
73
libraries/models/src/ModelsScriptingInterface.h
Normal 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
|
Loading…
Reference in a new issue