mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #2764 from ZappoMan/modelserver
Model Server, Renderer, Scripting, etc
This commit is contained in:
commit
05e5277084
47 changed files with 3476 additions and 22 deletions
|
@ -30,6 +30,7 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
#include <ResourceCache.h>
|
||||
#include <UUID.h>
|
||||
#include <VoxelConstants.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
|
||||
#include <ParticlesScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||
#include <ModelsScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||
|
||||
#include "Agent.h"
|
||||
|
||||
|
@ -68,6 +70,10 @@ void Agent::readPendingDatagrams() {
|
|||
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->
|
||||
queueReceivedPacket(matchedNode, receivedPacket);
|
||||
break;
|
||||
case NodeType::ModelServer:
|
||||
_scriptEngine.getModelsScriptingInterface()->getJurisdictionListener()->
|
||||
queueReceivedPacket(matchedNode, receivedPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +92,8 @@ void Agent::readPendingDatagrams() {
|
|||
|| datagramPacketType == PacketTypeParticleErase
|
||||
|| datagramPacketType == PacketTypeOctreeStats
|
||||
|| datagramPacketType == PacketTypeVoxelData
|
||||
|| datagramPacketType == PacketTypeModelData
|
||||
|| datagramPacketType == PacketTypeModelErase
|
||||
) {
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
|
@ -117,6 +125,10 @@ void Agent::readPendingDatagrams() {
|
|||
_particleViewer.processDatagram(mutablePacket, sourceNode);
|
||||
}
|
||||
|
||||
if (datagramPacketType == PacketTypeModelData || datagramPacketType == PacketTypeModelErase) {
|
||||
_modelViewer.processDatagram(mutablePacket, sourceNode);
|
||||
}
|
||||
|
||||
if (datagramPacketType == PacketTypeVoxelData) {
|
||||
_voxelViewer.processDatagram(mutablePacket, sourceNode);
|
||||
}
|
||||
|
@ -159,7 +171,9 @@ void Agent::run() {
|
|||
<< NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer
|
||||
<< NodeType::VoxelServer
|
||||
<< NodeType::ParticleServer);
|
||||
<< NodeType::ParticleServer
|
||||
<< NodeType::ModelServer
|
||||
);
|
||||
|
||||
// figure out the URL for the script for this agent assignment
|
||||
QUrl scriptURL;
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
|
||||
#include <AvatarHashMap.h>
|
||||
#include <MixedAudioRingBuffer.h>
|
||||
#include <ModelEditPacketSender.h>
|
||||
#include <ModelTree.h>
|
||||
#include <ModelTreeHeadlessViewer.h>
|
||||
#include <ParticleEditPacketSender.h>
|
||||
#include <ParticleTree.h>
|
||||
#include <ParticleTreeHeadlessViewer.h>
|
||||
|
@ -64,6 +67,7 @@ private:
|
|||
|
||||
ParticleTreeHeadlessViewer _particleViewer;
|
||||
VoxelTreeHeadlessViewer _voxelViewer;
|
||||
ModelTreeHeadlessViewer _modelViewer;
|
||||
|
||||
MixedAudioRingBuffer _receivedAudioBuffer;
|
||||
AvatarHashMap _avatarHashMap;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "audio/AudioMixer.h"
|
||||
#include "avatars/AvatarMixer.h"
|
||||
#include "metavoxels/MetavoxelServer.h"
|
||||
#include "models/ModelServer.h"
|
||||
#include "particles/ParticleServer.h"
|
||||
#include "voxels/VoxelServer.h"
|
||||
|
||||
|
@ -41,6 +42,8 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet
|
|||
return new ParticleServer(packet);
|
||||
case Assignment::MetavoxelServerType:
|
||||
return new MetavoxelServer(packet);
|
||||
case Assignment::ModelServerType:
|
||||
return new ModelServer(packet);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
|
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
|
|
@ -833,7 +833,7 @@ void OctreeServer::readPendingDatagrams() {
|
|||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
if (packetType == getMyQueryMessageType()) {
|
||||
|
||||
// If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we
|
||||
// If we got a query packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
if (matchingNode) {
|
||||
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
|
||||
|
|
|
@ -341,7 +341,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
}
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer
|
||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::MetavoxelServer;
|
||||
|
||||
void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
|
|
92
examples/editModelExample.js
Normal file
92
examples/editModelExample.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// editModelExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/31/13.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates creating and editing a model
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var count = 0;
|
||||
var moveUntil = 2000;
|
||||
var stopAfter = moveUntil + 100;
|
||||
|
||||
var pitch = 90.0;
|
||||
var yaw = 0.0;
|
||||
var roll = 180.0;
|
||||
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll)
|
||||
|
||||
var originalProperties = {
|
||||
position: { x: 10,
|
||||
y: 0,
|
||||
z: 0 },
|
||||
|
||||
radius : 0.1,
|
||||
|
||||
color: { red: 0,
|
||||
green: 255,
|
||||
blue: 0 },
|
||||
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx",
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo",
|
||||
modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx",
|
||||
|
||||
modelRotation: rotation
|
||||
};
|
||||
|
||||
var positionDelta = { x: 0, y: 0, z: 0 };
|
||||
|
||||
|
||||
var modelID = Models.addModel(originalProperties);
|
||||
|
||||
function moveModel(deltaTime) {
|
||||
if (count >= moveUntil) {
|
||||
|
||||
// delete it...
|
||||
if (count == moveUntil) {
|
||||
print("calling Models.deleteModel()");
|
||||
Models.deleteModel(modelID);
|
||||
}
|
||||
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
count++;
|
||||
return; // break early
|
||||
}
|
||||
|
||||
print("count =" + count);
|
||||
count++;
|
||||
|
||||
print("modelID.creatorTokenID = " + modelID.creatorTokenID);
|
||||
|
||||
var newProperties = {
|
||||
position: {
|
||||
x: originalProperties.position.x + (count * positionDelta.x),
|
||||
y: originalProperties.position.y + (count * positionDelta.y),
|
||||
z: originalProperties.position.z + (count * positionDelta.z)
|
||||
},
|
||||
radius : 0.25,
|
||||
|
||||
};
|
||||
|
||||
|
||||
//print("modelID = " + modelID);
|
||||
print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z);
|
||||
|
||||
Models.editModel(modelID, newProperties);
|
||||
}
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(moveModel);
|
||||
|
|
@ -49,7 +49,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe
|
|||
|
||||
# grab the implementation and header files from src dirs
|
||||
file(GLOB INTERFACE_SRCS src/*.cpp src/*.h)
|
||||
foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels)
|
||||
foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles models)
|
||||
file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h)
|
||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}")
|
||||
endforeach(SUBDIR)
|
||||
|
@ -124,6 +124,7 @@ link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <AudioInjector.h>
|
||||
#include <Logging.h>
|
||||
#include <ModelsScriptingInterface.h>
|
||||
#include <OctalCode.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
|
@ -266,7 +267,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
|
||||
// tell the NodeList instance who to tell the domain server we care about
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::VoxelServer << NodeType::ParticleServer
|
||||
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::MetavoxelServer);
|
||||
|
||||
// connect to the packet sent signal of the _voxelEditSender and the _particleEditSender
|
||||
|
@ -766,6 +767,8 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
|
|||
channel = BandwidthMeter::AVATARS;
|
||||
break;
|
||||
case NodeType::VoxelServer:
|
||||
case NodeType::ParticleServer:
|
||||
case NodeType::ModelServer:
|
||||
channel = BandwidthMeter::VOXELS;
|
||||
break;
|
||||
default:
|
||||
|
@ -1272,8 +1275,8 @@ void Application::dropEvent(QDropEvent *event) {
|
|||
|
||||
void Application::sendPingPackets() {
|
||||
QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
|
||||
controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer
|
||||
<< NodeType::ParticleServer
|
||||
controlledBroadcastToNodes(pingPacket, NodeSet()
|
||||
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::MetavoxelServer);
|
||||
}
|
||||
|
@ -1665,6 +1668,9 @@ void Application::init() {
|
|||
_particles.init();
|
||||
_particles.setViewFrustum(getViewFrustum());
|
||||
|
||||
_models.init();
|
||||
_models.setViewFrustum(getViewFrustum());
|
||||
|
||||
_metavoxels.init();
|
||||
|
||||
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
|
||||
|
@ -1995,6 +2001,8 @@ void Application::update(float deltaTime) {
|
|||
_particles.update(); // update the particles...
|
||||
_particleCollisionSystem.update(); // collide the particles...
|
||||
|
||||
_models.update(); // update the models...
|
||||
|
||||
_overlays.update(deltaTime);
|
||||
|
||||
// let external parties know we're updating
|
||||
|
@ -2033,6 +2041,7 @@ void Application::updateMyAvatar(float deltaTime) {
|
|||
_lastQueriedTime = now;
|
||||
queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
|
||||
queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions);
|
||||
queryOctree(NodeType::ModelServer, PacketTypeModelQuery, _modelServerJurisdictions);
|
||||
_lastQueriedViewFrustum = _viewFrustum;
|
||||
}
|
||||
}
|
||||
|
@ -2334,6 +2343,7 @@ void Application::updateShadowMap() {
|
|||
|
||||
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
|
||||
_particles.render();
|
||||
_models.render();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
|
@ -2500,6 +2510,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
_particles.render();
|
||||
}
|
||||
|
||||
// render models...
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... models...");
|
||||
_models.render();
|
||||
}
|
||||
|
||||
// render the ambient occlusion effect if enabled
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
|
@ -3094,6 +3111,9 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
// reset the particle renderer
|
||||
_particles.clear();
|
||||
|
||||
// reset the model renderer
|
||||
_models.clear();
|
||||
|
||||
// reset the voxels renderer
|
||||
_voxels.killLocalVoxels();
|
||||
}
|
||||
|
@ -3170,7 +3190,7 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
_voxelFades.push_back(fade);
|
||||
}
|
||||
|
||||
// If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
||||
// If the particle server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
||||
_particleServerJurisdictions.erase(_particleServerJurisdictions.find(nodeUUID));
|
||||
}
|
||||
|
||||
|
@ -3181,6 +3201,37 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
_octreeSceneStatsLock.unlock();
|
||||
|
||||
} else if (node->getType() == NodeType::ModelServer) {
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
// see if this is the first we've heard of this node...
|
||||
if (_modelServerJurisdictions.find(nodeUUID) != _modelServerJurisdictions.end()) {
|
||||
unsigned char* rootCode = _modelServerJurisdictions[nodeUUID].getRootOctalCode();
|
||||
VoxelPositionSize rootDetails;
|
||||
voxelDetailsForCode(rootCode, rootDetails);
|
||||
|
||||
qDebug("model server going away...... v[%f, %f, %f, %f]",
|
||||
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
|
||||
|
||||
// Add the jurisditionDetails object to the list of "fade outs"
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) {
|
||||
VoxelFade fade(VoxelFade::FADE_OUT, NODE_KILLED_RED, NODE_KILLED_GREEN, NODE_KILLED_BLUE);
|
||||
fade.voxelDetails = rootDetails;
|
||||
const float slightly_smaller = 0.99f;
|
||||
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
|
||||
_voxelFades.push_back(fade);
|
||||
}
|
||||
|
||||
// If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
||||
_modelServerJurisdictions.erase(_modelServerJurisdictions.find(nodeUUID));
|
||||
}
|
||||
|
||||
// also clean up scene stats for that server
|
||||
_octreeSceneStatsLock.lockForWrite();
|
||||
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
|
||||
_octreeServerSceneStats.erase(nodeUUID);
|
||||
}
|
||||
_octreeSceneStatsLock.unlock();
|
||||
|
||||
} else if (node->getType() == NodeType::AvatarMixer) {
|
||||
// our avatar mixer has gone away - clear the hash of avatars
|
||||
_avatarManager.clearOtherAvatars();
|
||||
|
@ -3233,8 +3284,10 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin
|
|||
NodeToJurisdictionMap* jurisdiction = NULL;
|
||||
if (sendingNode->getType() == NodeType::VoxelServer) {
|
||||
jurisdiction = &_voxelServerJurisdictions;
|
||||
} else {
|
||||
} else if (sendingNode->getType() == NodeType::ParticleServer) {
|
||||
jurisdiction = &_particleServerJurisdictions;
|
||||
} else {
|
||||
jurisdiction = &_modelServerJurisdictions;
|
||||
}
|
||||
|
||||
|
||||
|
@ -3394,6 +3447,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
|
||||
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
|
||||
|
||||
scriptEngine->getModelsScriptingInterface()->setPacketSender(&_modelEditSender);
|
||||
scriptEngine->getModelsScriptingInterface()->setModelTree(_models.getTree());
|
||||
|
||||
// hook our avatar object into this script engine
|
||||
scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <QTouchEvent>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include <ModelEditPacketSender.h>
|
||||
#include <NetworkPacket.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
@ -51,7 +52,6 @@
|
|||
#include "Menu.h"
|
||||
#include "MetavoxelSystem.h"
|
||||
#include "PacketHeaders.h"
|
||||
#include "ParticleTreeRenderer.h"
|
||||
#include "Stars.h"
|
||||
#include "avatar/Avatar.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
@ -60,6 +60,8 @@
|
|||
#include "devices/Faceshift.h"
|
||||
#include "devices/SixenseManager.h"
|
||||
#include "devices/Visage.h"
|
||||
#include "models/ModelTreeRenderer.h"
|
||||
#include "particles/ParticleTreeRenderer.h"
|
||||
#include "renderer/AmbientOcclusionEffect.h"
|
||||
#include "renderer/GeometryCache.h"
|
||||
#include "renderer/GlowEffect.h"
|
||||
|
@ -247,6 +249,7 @@ public:
|
|||
glm::vec2 getViewportDimensions() const{ return glm::vec2(_glWidget->width(),_glWidget->height()); }
|
||||
NodeToJurisdictionMap& getVoxelServerJurisdictions() { return _voxelServerJurisdictions; }
|
||||
NodeToJurisdictionMap& getParticleServerJurisdictions() { return _particleServerJurisdictions; }
|
||||
NodeToJurisdictionMap& getModelServerJurisdictions() { return _modelServerJurisdictions; }
|
||||
void pasteVoxelsToOctalCode(const unsigned char* octalCodeDestination);
|
||||
|
||||
void skipVersion(QString latestVersion);
|
||||
|
@ -417,6 +420,8 @@ private:
|
|||
ParticleTreeRenderer _particles;
|
||||
ParticleCollisionSystem _particleCollisionSystem;
|
||||
|
||||
ModelTreeRenderer _models;
|
||||
|
||||
QByteArray _voxelsFilename;
|
||||
bool _wantToKillLocalVoxels;
|
||||
|
||||
|
@ -494,6 +499,7 @@ private:
|
|||
VoxelHideShowThread _voxelHideShowThread;
|
||||
VoxelEditPacketSender _voxelEditSender;
|
||||
ParticleEditPacketSender _particleEditSender;
|
||||
ModelEditPacketSender _modelEditSender;
|
||||
|
||||
int _packetsPerSecond;
|
||||
int _bytesPerSecond;
|
||||
|
@ -506,6 +512,7 @@ private:
|
|||
|
||||
NodeToJurisdictionMap _voxelServerJurisdictions;
|
||||
NodeToJurisdictionMap _particleServerJurisdictions;
|
||||
NodeToJurisdictionMap _modelServerJurisdictions;
|
||||
NodeToOctreeSceneStats _octreeServerSceneStats;
|
||||
QReadWriteLock _octreeSceneStatsLock;
|
||||
|
||||
|
|
|
@ -280,8 +280,9 @@ Menu::Menu() :
|
|||
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true);
|
||||
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
|
||||
|
||||
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options");
|
||||
|
|
|
@ -277,9 +277,6 @@ namespace MenuOption {
|
|||
const QString AudioSpatialProcessingWithDiffusions = "With Diffusions";
|
||||
const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation";
|
||||
const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation";
|
||||
|
||||
|
||||
|
||||
const QString Avatars = "Avatars";
|
||||
const QString Bandwidth = "Bandwidth Display";
|
||||
const QString BandwidthDetails = "Bandwidth Details";
|
||||
|
@ -331,6 +328,7 @@ namespace MenuOption {
|
|||
const QString MetavoxelEditor = "Metavoxel Editor...";
|
||||
const QString Metavoxels = "Metavoxels";
|
||||
const QString Mirror = "Mirror";
|
||||
const QString Models = "Models";
|
||||
const QString MoveWithLean = "Move with Lean";
|
||||
const QString MuteAudio = "Mute Microphone";
|
||||
const QString NameLocation = "Name this location";
|
||||
|
|
123
interface/src/models/ModelTreeRenderer.cpp
Normal file
123
interface/src/models/ModelTreeRenderer.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// ModelTreeRenderer.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/6/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
#include "ModelTreeRenderer.h"
|
||||
|
||||
ModelTreeRenderer::ModelTreeRenderer() :
|
||||
OctreeRenderer() {
|
||||
}
|
||||
|
||||
ModelTreeRenderer::~ModelTreeRenderer() {
|
||||
// delete the models in _modelsItemModels
|
||||
foreach(Model* model, _modelsItemModels) {
|
||||
delete model;
|
||||
}
|
||||
_modelsItemModels.clear();
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::init() {
|
||||
OctreeRenderer::init();
|
||||
}
|
||||
|
||||
|
||||
void ModelTreeRenderer::update() {
|
||||
if (_tree) {
|
||||
ModelTree* tree = static_cast<ModelTree*>(_tree);
|
||||
tree->update();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::render() {
|
||||
OctreeRenderer::render();
|
||||
}
|
||||
|
||||
Model* ModelTreeRenderer::getModel(const QString& url) {
|
||||
Model* model = NULL;
|
||||
|
||||
// if we don't already have this model then create it and initialize it
|
||||
if (_modelsItemModels.find(url) == _modelsItemModels.end()) {
|
||||
model = new Model();
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
_modelsItemModels[url] = model;
|
||||
} else {
|
||||
model = _modelsItemModels[url];
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
|
||||
// actually render it here...
|
||||
// we need to iterate the actual modelItems of the element
|
||||
ModelTreeElement* modelTreeElement = (ModelTreeElement*)element;
|
||||
|
||||
const QList<ModelItem>& modelItems = modelTreeElement->getModels();
|
||||
|
||||
uint16_t numberOfModels = modelItems.size();
|
||||
|
||||
for (uint16_t i = 0; i < numberOfModels; i++) {
|
||||
const ModelItem& modelItem = modelItems[i];
|
||||
// render modelItem aspoints
|
||||
glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE;
|
||||
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
|
||||
float radius = modelItem.getRadius() * (float)TREE_SCALE;
|
||||
//glm::vec3 center = position + glm::vec3(radius, radius, radius); // center it around the position
|
||||
|
||||
bool drawAsModel = modelItem.hasModel();
|
||||
|
||||
args->_renderedItems++;
|
||||
|
||||
if (drawAsModel) {
|
||||
glPushMatrix();
|
||||
const float alpha = 1.0f;
|
||||
|
||||
Model* model = getModel(modelItem.getModelURL());
|
||||
|
||||
model->setScaleToFit(true, radius * 2.0f);
|
||||
model->setSnapModelToCenter(true);
|
||||
|
||||
// set the rotation
|
||||
glm::quat rotation = modelItem.getModelRotation();
|
||||
model->setRotation(rotation);
|
||||
|
||||
// set the position
|
||||
model->setTranslation(position);
|
||||
|
||||
model->simulate(0.0f);
|
||||
|
||||
|
||||
model->render(alpha); // TODO: should we allow modelItems to have alpha on their models?
|
||||
|
||||
const bool wantDebugSphere = false;
|
||||
if (wantDebugSphere) {
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glutWireSphere(radius, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
} else {
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glutSolidSphere(radius, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
|
||||
static_cast<ModelTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
|
||||
}
|
55
interface/src/models/ModelTreeRenderer.h
Normal file
55
interface/src/models/ModelTreeRenderer.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// ModelTreeRenderer.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/6/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelTreeRenderer_h
|
||||
#define hifi_ModelTreeRenderer_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <ModelTree.h>
|
||||
#include <Octree.h>
|
||||
#include <OctreePacketData.h>
|
||||
#include <OctreeRenderer.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "renderer/Model.h"
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class ModelTreeRenderer : public OctreeRenderer {
|
||||
public:
|
||||
ModelTreeRenderer();
|
||||
virtual ~ModelTreeRenderer();
|
||||
|
||||
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; }
|
||||
virtual void renderElement(OctreeElement* element, RenderArgs* args);
|
||||
|
||||
void update();
|
||||
|
||||
ModelTree* getTree() { return (ModelTree*)_tree; }
|
||||
|
||||
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
|
||||
|
||||
virtual void init();
|
||||
virtual void render();
|
||||
|
||||
protected:
|
||||
Model* getModel(const QString& url);
|
||||
|
||||
QMap<QString, Model*> _modelsItemModels;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelTreeRenderer_h
|
|
@ -34,6 +34,11 @@ static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
|
|||
Model::Model(QObject* parent) :
|
||||
QObject(parent),
|
||||
_scale(1.0f, 1.0f, 1.0f),
|
||||
_scaleToFit(false),
|
||||
_scaleToFitLargestDimension(0.0f),
|
||||
_scaledToFit(false),
|
||||
_snapModelToCenter(false),
|
||||
_snappedToCenter(false),
|
||||
_shapesAreDirty(true),
|
||||
_boundingRadius(0.f),
|
||||
_boundingShape(),
|
||||
|
@ -60,6 +65,13 @@ Model::SkinLocations Model::_skinNormalMapLocations;
|
|||
Model::SkinLocations Model::_skinShadowLocations;
|
||||
|
||||
void Model::setScale(const glm::vec3& scale) {
|
||||
setScaleInternal(scale);
|
||||
// if anyone sets scale manually, then we are no longer scaled to fit
|
||||
_scaleToFit = false;
|
||||
_scaledToFit = false;
|
||||
}
|
||||
|
||||
void Model::setScaleInternal(const glm::vec3& scale) {
|
||||
float scaleLength = glm::length(_scale);
|
||||
float relativeDeltaScale = glm::length(_scale - scale) / scaleLength;
|
||||
|
||||
|
@ -70,6 +82,15 @@ void Model::setScale(const glm::vec3& scale) {
|
|||
}
|
||||
}
|
||||
|
||||
void Model::setOffset(const glm::vec3& offset) {
|
||||
_offset = offset;
|
||||
|
||||
// if someone manually sets our offset, then we are no longer snapped to center
|
||||
_snapModelToCenter = false;
|
||||
_snappedToCenter = false;
|
||||
}
|
||||
|
||||
|
||||
void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) {
|
||||
program.bind();
|
||||
locations.clusterMatrices = program.uniformLocation("clusterMatrices");
|
||||
|
@ -357,6 +378,23 @@ Extents Model::getBindExtents() const {
|
|||
return scaledExtents;
|
||||
}
|
||||
|
||||
Extents Model::getMeshExtents() const {
|
||||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
Extents scaledExtents = { extents.minimum * _scale, extents.maximum * _scale };
|
||||
return scaledExtents;
|
||||
}
|
||||
|
||||
Extents Model::getUnscaledMeshExtents() const {
|
||||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
return extents;
|
||||
}
|
||||
|
||||
bool Model::getJointState(int index, glm::quat& rotation) const {
|
||||
if (index == -1 || index >= _jointStates.size()) {
|
||||
return false;
|
||||
|
@ -770,9 +808,52 @@ void Blender::run() {
|
|||
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
|
||||
}
|
||||
|
||||
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
|
||||
if (_scaleToFit != scaleToFit || _scaleToFitLargestDimension != largestDimension) {
|
||||
_scaleToFit = scaleToFit;
|
||||
_scaleToFitLargestDimension = largestDimension;
|
||||
_scaledToFit = false; // force rescaling
|
||||
}
|
||||
}
|
||||
|
||||
void Model::scaleToFit() {
|
||||
Extents modelMeshExtents = getMeshExtents();
|
||||
|
||||
// size is our "target size in world space"
|
||||
// we need to set our model scale so that the extents of the mesh, fit in a cube that size...
|
||||
glm::vec3 dimensions = modelMeshExtents.maximum - modelMeshExtents.minimum;
|
||||
float maxDimension = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z);
|
||||
float maxScale = _scaleToFitLargestDimension / maxDimension;
|
||||
glm::vec3 scale(maxScale, maxScale, maxScale);
|
||||
setScaleInternal(scale);
|
||||
_scaledToFit = true;
|
||||
}
|
||||
|
||||
void Model::setSnapModelToCenter(bool snapModelToCenter) {
|
||||
if (_snapModelToCenter != snapModelToCenter) {
|
||||
_snapModelToCenter = snapModelToCenter;
|
||||
_snappedToCenter = false; // force re-centering
|
||||
}
|
||||
}
|
||||
|
||||
void Model::snapToCenter() {
|
||||
Extents modelMeshExtents = getUnscaledMeshExtents();
|
||||
glm::vec3 halfDimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum) * 0.5f;
|
||||
glm::vec3 offset = -modelMeshExtents.minimum - halfDimensions;
|
||||
_offset = offset;
|
||||
_snappedToCenter = true;
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||
fullUpdate = updateGeometry() || fullUpdate;
|
||||
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) || (_snapModelToCenter && !_snappedToCenter);
|
||||
if (isActive() && fullUpdate) {
|
||||
// check for scale to fit
|
||||
if (_scaleToFit && !_scaledToFit) {
|
||||
scaleToFit();
|
||||
}
|
||||
if (_snapModelToCenter && !_snappedToCenter) {
|
||||
snapToCenter();
|
||||
}
|
||||
simulateInternal(deltaTime);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,19 @@ public:
|
|||
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
/// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
|
||||
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f);
|
||||
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
|
||||
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
|
||||
bool getScaleToFitDimension() const { return _scaleToFitLargestDimension; } /// the dimension model is scaled to
|
||||
|
||||
void setSnapModelToCenter(bool snapModelToCenter);
|
||||
bool getSnapModelToCenter() { return _snapModelToCenter; }
|
||||
|
||||
void setScale(const glm::vec3& scale);
|
||||
const glm::vec3& getScale() const { return _scale; }
|
||||
|
||||
void setOffset(const glm::vec3& offset) { _offset = offset; }
|
||||
void setOffset(const glm::vec3& offset);
|
||||
const glm::vec3& getOffset() const { return _offset; }
|
||||
|
||||
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
||||
|
@ -80,6 +89,12 @@ public:
|
|||
/// Returns the extents of the model in its bind pose.
|
||||
Extents getBindExtents() const;
|
||||
|
||||
/// Returns the extents of the model's mesh
|
||||
Extents getMeshExtents() const;
|
||||
|
||||
/// Returns the unscaled extents of the model's mesh
|
||||
Extents getUnscaledMeshExtents() const;
|
||||
|
||||
/// Returns a reference to the shared geometry.
|
||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||
|
||||
|
@ -203,6 +218,13 @@ protected:
|
|||
glm::quat _rotation;
|
||||
glm::vec3 _scale;
|
||||
glm::vec3 _offset;
|
||||
|
||||
bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents
|
||||
float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use
|
||||
bool _scaledToFit; /// have we scaled to fit
|
||||
|
||||
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
|
||||
bool _snappedToCenter; /// are we currently snapped to center
|
||||
|
||||
class JointState {
|
||||
public:
|
||||
|
@ -229,6 +251,10 @@ protected:
|
|||
|
||||
// returns 'true' if needs fullUpdate after geometry change
|
||||
bool updateGeometry();
|
||||
|
||||
void setScaleInternal(const glm::vec3& scale);
|
||||
void scaleToFit();
|
||||
void snapToCenter();
|
||||
|
||||
void simulateInternal(float deltaTime);
|
||||
|
||||
|
|
|
@ -232,6 +232,9 @@ void OctreeStatsDialog::showAllOctreeServers() {
|
|||
showOctreeServersOfType(serverCount, NodeType::ParticleServer, "Particle",
|
||||
Application::getInstance()->getParticleServerJurisdictions());
|
||||
|
||||
showOctreeServersOfType(serverCount, NodeType::ModelServer, "Model",
|
||||
Application::getInstance()->getModelServerJurisdictions());
|
||||
|
||||
if (_voxelServerLabelsCount > serverCount) {
|
||||
for (int i = serverCount; i < _voxelServerLabelsCount; i++) {
|
||||
int serverLabel = _voxelServerLables[i];
|
||||
|
|
|
@ -237,6 +237,7 @@ void Stats::display(
|
|||
int voxelServerCount = 0;
|
||||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
// TODO: this should also support particles and models
|
||||
if (node->getType() == NodeType::VoxelServer) {
|
||||
totalPingVoxel += node->getPingMs();
|
||||
voxelServerCount++;
|
||||
|
|
45
libraries/models/CMakeLists.txt
Normal file
45
libraries/models/CMakeLists.txt
Normal file
|
@ -0,0 +1,45 @@
|
|||
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}/AutoMTC.cmake)
|
||||
auto_mtc(${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
|
||||
setup_hifi_library(${TARGET_NAME} "${AUTOMTC_SRC}")
|
||||
|
||||
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}")
|
||||
|
||||
# for streamable
|
||||
link_hifi_library(metavoxels ${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/models/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 8/12/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <assert.h>
|
||||
#include <PerfStat.h>
|
||||
#include <OctalCode.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include "ModelEditPacketSender.h"
|
||||
#include "ModelItem.h"
|
||||
|
||||
|
||||
void ModelEditPacketSender::sendEditModelMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties) {
|
||||
// allows app to disable sending if for example voxels have been disabled
|
||||
if (!_shouldSend) {
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
static unsigned char bufferOut[MAX_PACKET_SIZE];
|
||||
int sizeOut = 0;
|
||||
|
||||
// This encodes the voxel edit message into a buffer...
|
||||
if (ModelItem::encodeModelEditMessageDetails(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)){
|
||||
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
|
||||
// jurisdictions for processing
|
||||
if (!serversExist()) {
|
||||
queuePendingPacketToNodes(type, bufferOut, sizeOut);
|
||||
} else {
|
||||
queuePacketToNodes(bufferOut, sizeOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, 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/models/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 8/12/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelEditPacketSender_h
|
||||
#define hifi_ModelEditPacketSender_h
|
||||
|
||||
#include <OctreeEditPacketSender.h>
|
||||
|
||||
#include "ModelItem.h"
|
||||
|
||||
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
|
||||
class ModelEditPacketSender : public OctreeEditPacketSender {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/// Send model add message immediately
|
||||
/// NOTE: ModelItemProperties assumes that all distances are in meter units
|
||||
void sendEditModelMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties);
|
||||
|
||||
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
|
||||
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
|
||||
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
|
||||
/// NOTE: ModelItemProperties assumes that all distances are in meter units
|
||||
void queueModelEditMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties);
|
||||
|
||||
// My server type is the model server
|
||||
virtual unsigned char getMyNodeType() const { return NodeType::ModelServer; }
|
||||
virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew);
|
||||
};
|
||||
#endif // hifi_ModelEditPacketSender_h
|
802
libraries/models/src/ModelItem.cpp
Normal file
802
libraries/models/src/ModelItem.cpp
Normal file
|
@ -0,0 +1,802 @@
|
|||
//
|
||||
// ModelItem.cpp
|
||||
// libraries/models/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/4/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <Octree.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <SharedUtil.h> // usecTimestampNow()
|
||||
#include <VoxelsScriptingInterface.h>
|
||||
#include <VoxelDetail.h>
|
||||
|
||||
|
||||
// This is not ideal, but adding script-engine as a linked library, will cause a circular reference
|
||||
// I'm open to other potential solutions. Could we change cmake to allow libraries to reference each others
|
||||
// headers, but not link to each other, this is essentially what this construct is doing, but would be
|
||||
// better to add includes to the include path, but not link
|
||||
#include "../../script-engine/src/ScriptEngine.h"
|
||||
|
||||
#include "ModelsScriptingInterface.h"
|
||||
#include "ModelItem.h"
|
||||
#include "ModelTree.h"
|
||||
|
||||
uint32_t ModelItem::_nextID = 0;
|
||||
|
||||
// for locally created models
|
||||
std::map<uint32_t,uint32_t> ModelItem::_tokenIDsToIDs;
|
||||
uint32_t ModelItem::_nextCreatorTokenID = 0;
|
||||
|
||||
uint32_t ModelItem::getIDfromCreatorTokenID(uint32_t creatorTokenID) {
|
||||
if (_tokenIDsToIDs.find(creatorTokenID) != _tokenIDsToIDs.end()) {
|
||||
return _tokenIDsToIDs[creatorTokenID];
|
||||
}
|
||||
return UNKNOWN_MODEL_ID;
|
||||
}
|
||||
|
||||
uint32_t ModelItem::getNextCreatorTokenID() {
|
||||
uint32_t creatorTokenID = _nextCreatorTokenID;
|
||||
_nextCreatorTokenID++;
|
||||
return creatorTokenID;
|
||||
}
|
||||
|
||||
void ModelItem::handleAddModelResponse(const QByteArray& packet) {
|
||||
const unsigned char* dataAt = reinterpret_cast<const unsigned char*>(packet.data());
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
||||
dataAt += numBytesPacketHeader;
|
||||
|
||||
uint32_t creatorTokenID;
|
||||
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
|
||||
dataAt += sizeof(creatorTokenID);
|
||||
|
||||
uint32_t modelItemID;
|
||||
memcpy(&modelItemID, dataAt, sizeof(modelItemID));
|
||||
dataAt += sizeof(modelItemID);
|
||||
|
||||
// add our token to id mapping
|
||||
_tokenIDsToIDs[creatorTokenID] = modelItemID;
|
||||
}
|
||||
|
||||
ModelItem::ModelItem() {
|
||||
rgbColor noColor = { 0, 0, 0 };
|
||||
init(glm::vec3(0,0,0), 0, noColor, NEW_MODEL);
|
||||
}
|
||||
|
||||
ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& properties) {
|
||||
_id = modelItemID.id;
|
||||
_creatorTokenID = modelItemID.creatorTokenID;
|
||||
|
||||
// init values with defaults before calling setProperties
|
||||
uint64_t now = usecTimestampNow();
|
||||
_lastEdited = now;
|
||||
_lastUpdated = now;
|
||||
|
||||
_position = glm::vec3(0,0,0);
|
||||
_radius = 0;
|
||||
rgbColor noColor = { 0, 0, 0 };
|
||||
memcpy(_color, noColor, sizeof(_color));
|
||||
_shouldDie = false;
|
||||
_modelURL = MODEL_DEFAULT_MODEL_URL;
|
||||
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
|
||||
|
||||
setProperties(properties);
|
||||
}
|
||||
|
||||
|
||||
ModelItem::~ModelItem() {
|
||||
}
|
||||
|
||||
void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t id) {
|
||||
if (id == NEW_MODEL) {
|
||||
_id = _nextID;
|
||||
_nextID++;
|
||||
} else {
|
||||
_id = id;
|
||||
}
|
||||
quint64 now = usecTimestampNow();
|
||||
_lastEdited = now;
|
||||
_lastUpdated = now;
|
||||
|
||||
_position = position;
|
||||
_radius = radius;
|
||||
memcpy(_color, color, sizeof(_color));
|
||||
_shouldDie = false;
|
||||
_modelURL = MODEL_DEFAULT_MODEL_URL;
|
||||
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
|
||||
}
|
||||
|
||||
bool ModelItem::appendModelData(OctreePacketData* packetData) const {
|
||||
|
||||
bool success = packetData->appendValue(getID());
|
||||
|
||||
//qDebug("ModelItem::appendModelData()... getID()=%d", getID());
|
||||
|
||||
if (success) {
|
||||
success = packetData->appendValue(getLastUpdated());
|
||||
}
|
||||
if (success) {
|
||||
success = packetData->appendValue(getLastEdited());
|
||||
}
|
||||
if (success) {
|
||||
success = packetData->appendValue(getRadius());
|
||||
}
|
||||
if (success) {
|
||||
success = packetData->appendPosition(getPosition());
|
||||
}
|
||||
if (success) {
|
||||
success = packetData->appendColor(getColor());
|
||||
}
|
||||
if (success) {
|
||||
success = packetData->appendValue(getShouldDie());
|
||||
}
|
||||
|
||||
// modelURL
|
||||
if (success) {
|
||||
uint16_t modelURLLength = _modelURL.size() + 1; // include NULL
|
||||
success = packetData->appendValue(modelURLLength);
|
||||
if (success) {
|
||||
success = packetData->appendRawData((const unsigned char*)qPrintable(_modelURL), modelURLLength);
|
||||
}
|
||||
}
|
||||
|
||||
// modelRotation
|
||||
if (success) {
|
||||
success = packetData->appendValue(getModelRotation());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int ModelItem::expectedBytes() {
|
||||
int expectedBytes = sizeof(uint32_t) // id
|
||||
+ sizeof(float) // age
|
||||
+ sizeof(quint64) // last updated
|
||||
+ sizeof(quint64) // lasted edited
|
||||
+ sizeof(float) // radius
|
||||
+ sizeof(glm::vec3) // position
|
||||
+ sizeof(rgbColor); // color
|
||||
// potentially more...
|
||||
return expectedBytes;
|
||||
}
|
||||
|
||||
int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
||||
int bytesRead = 0;
|
||||
if (bytesLeftToRead >= expectedBytes()) {
|
||||
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
|
||||
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
// id
|
||||
memcpy(&_id, dataAt, sizeof(_id));
|
||||
dataAt += sizeof(_id);
|
||||
bytesRead += sizeof(_id);
|
||||
|
||||
// _lastUpdated
|
||||
memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated));
|
||||
dataAt += sizeof(_lastUpdated);
|
||||
bytesRead += sizeof(_lastUpdated);
|
||||
_lastUpdated -= clockSkew;
|
||||
|
||||
// _lastEdited
|
||||
memcpy(&_lastEdited, dataAt, sizeof(_lastEdited));
|
||||
dataAt += sizeof(_lastEdited);
|
||||
bytesRead += sizeof(_lastEdited);
|
||||
_lastEdited -= clockSkew;
|
||||
|
||||
// radius
|
||||
memcpy(&_radius, dataAt, sizeof(_radius));
|
||||
dataAt += sizeof(_radius);
|
||||
bytesRead += sizeof(_radius);
|
||||
|
||||
// position
|
||||
memcpy(&_position, dataAt, sizeof(_position));
|
||||
dataAt += sizeof(_position);
|
||||
bytesRead += sizeof(_position);
|
||||
|
||||
// color
|
||||
memcpy(_color, dataAt, sizeof(_color));
|
||||
dataAt += sizeof(_color);
|
||||
bytesRead += sizeof(_color);
|
||||
|
||||
// shouldDie
|
||||
memcpy(&_shouldDie, dataAt, sizeof(_shouldDie));
|
||||
dataAt += sizeof(_shouldDie);
|
||||
bytesRead += sizeof(_shouldDie);
|
||||
|
||||
// modelURL
|
||||
uint16_t modelURLLength;
|
||||
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
|
||||
dataAt += sizeof(modelURLLength);
|
||||
bytesRead += sizeof(modelURLLength);
|
||||
QString modelURLString((const char*)dataAt);
|
||||
_modelURL = modelURLString;
|
||||
dataAt += modelURLLength;
|
||||
bytesRead += modelURLLength;
|
||||
|
||||
// modelRotation
|
||||
int bytes = unpackOrientationQuatFromBytes(dataAt, _modelRotation);
|
||||
dataAt += bytes;
|
||||
bytesRead += bytes;
|
||||
|
||||
//printf("ModelItem::readModelDataFromBuffer()... "); debugDump();
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid) {
|
||||
|
||||
ModelItem newModelItem; // id and _lastUpdated will get set here...
|
||||
const unsigned char* dataAt = data;
|
||||
processedBytes = 0;
|
||||
|
||||
// the first part of the data is our octcode...
|
||||
int octets = numberOfThreeBitSectionsInCode(data);
|
||||
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
|
||||
|
||||
// we don't actually do anything with this octcode...
|
||||
dataAt += lengthOfOctcode;
|
||||
processedBytes += lengthOfOctcode;
|
||||
|
||||
// id
|
||||
uint32_t editID;
|
||||
memcpy(&editID, dataAt, sizeof(editID));
|
||||
dataAt += sizeof(editID);
|
||||
processedBytes += sizeof(editID);
|
||||
|
||||
bool isNewModelItem = (editID == NEW_MODEL);
|
||||
|
||||
// special case for handling "new" modelItems
|
||||
if (isNewModelItem) {
|
||||
// If this is a NEW_MODEL, then we assume that there's an additional uint32_t creatorToken, that
|
||||
// we want to send back to the creator as an map to the actual id
|
||||
uint32_t creatorTokenID;
|
||||
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
|
||||
dataAt += sizeof(creatorTokenID);
|
||||
processedBytes += sizeof(creatorTokenID);
|
||||
|
||||
newModelItem.setCreatorTokenID(creatorTokenID);
|
||||
newModelItem._newlyCreated = true;
|
||||
|
||||
} else {
|
||||
// look up the existing modelItem
|
||||
const ModelItem* existingModelItem = tree->findModelByID(editID, true);
|
||||
|
||||
// copy existing properties before over-writing with new properties
|
||||
if (existingModelItem) {
|
||||
newModelItem = *existingModelItem;
|
||||
} else {
|
||||
// the user attempted to edit a modelItem that doesn't exist
|
||||
qDebug() << "user attempted to edit a modelItem that doesn't exist...";
|
||||
valid = false;
|
||||
return newModelItem;
|
||||
}
|
||||
newModelItem._id = editID;
|
||||
newModelItem._newlyCreated = false;
|
||||
}
|
||||
|
||||
// if we got this far, then our result will be valid
|
||||
valid = true;
|
||||
|
||||
|
||||
// lastEdited
|
||||
memcpy(&newModelItem._lastEdited, dataAt, sizeof(newModelItem._lastEdited));
|
||||
dataAt += sizeof(newModelItem._lastEdited);
|
||||
processedBytes += sizeof(newModelItem._lastEdited);
|
||||
|
||||
// All of the remaining items are optional, and may or may not be included based on their included values in the
|
||||
// properties included bits
|
||||
uint16_t packetContainsBits = 0;
|
||||
if (!isNewModelItem) {
|
||||
memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits));
|
||||
dataAt += sizeof(packetContainsBits);
|
||||
processedBytes += sizeof(packetContainsBits);
|
||||
}
|
||||
|
||||
|
||||
// radius
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_RADIUS) == MODEL_PACKET_CONTAINS_RADIUS)) {
|
||||
memcpy(&newModelItem._radius, dataAt, sizeof(newModelItem._radius));
|
||||
dataAt += sizeof(newModelItem._radius);
|
||||
processedBytes += sizeof(newModelItem._radius);
|
||||
}
|
||||
|
||||
// position
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_POSITION) == MODEL_PACKET_CONTAINS_POSITION)) {
|
||||
memcpy(&newModelItem._position, dataAt, sizeof(newModelItem._position));
|
||||
dataAt += sizeof(newModelItem._position);
|
||||
processedBytes += sizeof(newModelItem._position);
|
||||
}
|
||||
|
||||
// color
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_COLOR) == MODEL_PACKET_CONTAINS_COLOR)) {
|
||||
memcpy(newModelItem._color, dataAt, sizeof(newModelItem._color));
|
||||
dataAt += sizeof(newModelItem._color);
|
||||
processedBytes += sizeof(newModelItem._color);
|
||||
}
|
||||
|
||||
// shouldDie
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) {
|
||||
memcpy(&newModelItem._shouldDie, dataAt, sizeof(newModelItem._shouldDie));
|
||||
dataAt += sizeof(newModelItem._shouldDie);
|
||||
processedBytes += sizeof(newModelItem._shouldDie);
|
||||
}
|
||||
|
||||
// modelURL
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) {
|
||||
uint16_t modelURLLength;
|
||||
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
|
||||
dataAt += sizeof(modelURLLength);
|
||||
processedBytes += sizeof(modelURLLength);
|
||||
QString tempString((const char*)dataAt);
|
||||
newModelItem._modelURL = tempString;
|
||||
dataAt += modelURLLength;
|
||||
processedBytes += modelURLLength;
|
||||
}
|
||||
|
||||
// modelRotation
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
|
||||
int bytes = unpackOrientationQuatFromBytes(dataAt, newModelItem._modelRotation);
|
||||
dataAt += bytes;
|
||||
processedBytes += bytes;
|
||||
}
|
||||
|
||||
const bool wantDebugging = false;
|
||||
if (wantDebugging) {
|
||||
qDebug("ModelItem::fromEditPacket()...");
|
||||
qDebug() << " ModelItem id in packet:" << editID;
|
||||
newModelItem.debugDump();
|
||||
}
|
||||
|
||||
return newModelItem;
|
||||
}
|
||||
|
||||
void ModelItem::debugDump() const {
|
||||
qDebug("ModelItem id :%u", _id);
|
||||
qDebug(" edited ago:%f", getEditedAgo());
|
||||
qDebug(" should die:%s", debug::valueOf(getShouldDie()));
|
||||
qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z);
|
||||
qDebug(" radius:%f", getRadius());
|
||||
qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]);
|
||||
}
|
||||
|
||||
bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& properties,
|
||||
unsigned char* bufferOut, int sizeIn, int& sizeOut) {
|
||||
|
||||
bool success = true; // assume the best
|
||||
unsigned char* copyAt = bufferOut;
|
||||
sizeOut = 0;
|
||||
|
||||
// get the octal code for the modelItem
|
||||
|
||||
// this could be a problem if the caller doesn't include position....
|
||||
glm::vec3 rootPosition(0);
|
||||
float rootScale = 0.5f;
|
||||
unsigned char* octcode = pointToOctalCode(rootPosition.x, rootPosition.y, rootPosition.z, rootScale);
|
||||
|
||||
// TODO: Consider this old code... including the correct octree for where the modelItem will go matters for
|
||||
// modelItem servers with different jurisdictions, but for now, we'll send everything to the root, since the
|
||||
// tree does the right thing...
|
||||
//
|
||||
//unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
|
||||
// details[i].position.z, details[i].radius);
|
||||
|
||||
int octets = numberOfThreeBitSectionsInCode(octcode);
|
||||
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
|
||||
|
||||
// add it to our message
|
||||
memcpy(copyAt, octcode, lengthOfOctcode);
|
||||
copyAt += lengthOfOctcode;
|
||||
sizeOut += lengthOfOctcode;
|
||||
|
||||
// Now add our edit content details...
|
||||
bool isNewModelItem = (id.id == NEW_MODEL);
|
||||
|
||||
// id
|
||||
memcpy(copyAt, &id.id, sizeof(id.id));
|
||||
copyAt += sizeof(id.id);
|
||||
sizeOut += sizeof(id.id);
|
||||
|
||||
// special case for handling "new" modelItems
|
||||
if (isNewModelItem) {
|
||||
// If this is a NEW_MODEL, then we assume that there's an additional uint32_t creatorToken, that
|
||||
// we want to send back to the creator as an map to the actual id
|
||||
memcpy(copyAt, &id.creatorTokenID, sizeof(id.creatorTokenID));
|
||||
copyAt += sizeof(id.creatorTokenID);
|
||||
sizeOut += sizeof(id.creatorTokenID);
|
||||
}
|
||||
|
||||
// lastEdited
|
||||
quint64 lastEdited = properties.getLastEdited();
|
||||
memcpy(copyAt, &lastEdited, sizeof(lastEdited));
|
||||
copyAt += sizeof(lastEdited);
|
||||
sizeOut += sizeof(lastEdited);
|
||||
|
||||
// For new modelItems, all remaining items are mandatory, for an edited modelItem, All of the remaining items are
|
||||
// optional, and may or may not be included based on their included values in the properties included bits
|
||||
uint16_t packetContainsBits = properties.getChangedBits();
|
||||
if (!isNewModelItem) {
|
||||
memcpy(copyAt, &packetContainsBits, sizeof(packetContainsBits));
|
||||
copyAt += sizeof(packetContainsBits);
|
||||
sizeOut += sizeof(packetContainsBits);
|
||||
}
|
||||
|
||||
// radius
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_RADIUS) == MODEL_PACKET_CONTAINS_RADIUS)) {
|
||||
float radius = properties.getRadius() / (float) TREE_SCALE;
|
||||
memcpy(copyAt, &radius, sizeof(radius));
|
||||
copyAt += sizeof(radius);
|
||||
sizeOut += sizeof(radius);
|
||||
}
|
||||
|
||||
// position
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_POSITION) == MODEL_PACKET_CONTAINS_POSITION)) {
|
||||
glm::vec3 position = properties.getPosition() / (float)TREE_SCALE;
|
||||
memcpy(copyAt, &position, sizeof(position));
|
||||
copyAt += sizeof(position);
|
||||
sizeOut += sizeof(position);
|
||||
}
|
||||
|
||||
// color
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_COLOR) == MODEL_PACKET_CONTAINS_COLOR)) {
|
||||
rgbColor color = { properties.getColor().red, properties.getColor().green, properties.getColor().blue };
|
||||
memcpy(copyAt, color, sizeof(color));
|
||||
copyAt += sizeof(color);
|
||||
sizeOut += sizeof(color);
|
||||
}
|
||||
|
||||
// shoulDie
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) {
|
||||
bool shouldDie = properties.getShouldDie();
|
||||
memcpy(copyAt, &shouldDie, sizeof(shouldDie));
|
||||
copyAt += sizeof(shouldDie);
|
||||
sizeOut += sizeof(shouldDie);
|
||||
}
|
||||
|
||||
// modelURL
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) {
|
||||
uint16_t urlLength = properties.getModelURL().size() + 1;
|
||||
memcpy(copyAt, &urlLength, sizeof(urlLength));
|
||||
copyAt += sizeof(urlLength);
|
||||
sizeOut += sizeof(urlLength);
|
||||
memcpy(copyAt, qPrintable(properties.getModelURL()), urlLength);
|
||||
copyAt += urlLength;
|
||||
sizeOut += urlLength;
|
||||
}
|
||||
|
||||
// modelRotation
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
|
||||
int bytes = packOrientationQuatToBytes(copyAt, properties.getModelRotation());
|
||||
copyAt += bytes;
|
||||
sizeOut += bytes;
|
||||
}
|
||||
|
||||
bool wantDebugging = false;
|
||||
if (wantDebugging) {
|
||||
qDebug("encodeModelItemEditMessageDetails()....");
|
||||
qDebug("ModelItem id :%u", id.id);
|
||||
qDebug(" nextID:%u", _nextID);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
delete[] octcode;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// adjust any internal timestamps to fix clock skew for this server
|
||||
void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) {
|
||||
unsigned char* dataAt = codeColorBuffer;
|
||||
int octets = numberOfThreeBitSectionsInCode(dataAt);
|
||||
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
|
||||
dataAt += lengthOfOctcode;
|
||||
|
||||
// id
|
||||
uint32_t id;
|
||||
memcpy(&id, dataAt, sizeof(id));
|
||||
dataAt += sizeof(id);
|
||||
// special case for handling "new" modelItems
|
||||
if (id == NEW_MODEL) {
|
||||
// If this is a NEW_MODEL, then we assume that there's an additional uint32_t creatorToken, that
|
||||
// we want to send back to the creator as an map to the actual id
|
||||
dataAt += sizeof(uint32_t);
|
||||
}
|
||||
|
||||
// lastEdited
|
||||
quint64 lastEditedInLocalTime;
|
||||
memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime));
|
||||
quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew;
|
||||
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
|
||||
const bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
qDebug("ModelItem::adjustEditPacketForClockSkew()...");
|
||||
qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime;
|
||||
qDebug() << " clockSkew: " << clockSkew;
|
||||
qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime;
|
||||
}
|
||||
}
|
||||
|
||||
void ModelItem::update(const quint64& now) {
|
||||
_lastUpdated = now;
|
||||
setShouldDie(getShouldDie());
|
||||
}
|
||||
|
||||
void ModelItem::copyChangedProperties(const ModelItem& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
ModelItemProperties ModelItem::getProperties() const {
|
||||
ModelItemProperties properties;
|
||||
properties.copyFromModelItem(*this);
|
||||
return properties;
|
||||
}
|
||||
|
||||
void ModelItem::setProperties(const ModelItemProperties& properties) {
|
||||
properties.copyToModelItem(*this);
|
||||
}
|
||||
|
||||
ModelItemProperties::ModelItemProperties() :
|
||||
_position(0),
|
||||
_color(),
|
||||
_radius(MODEL_DEFAULT_RADIUS),
|
||||
_shouldDie(false),
|
||||
_modelURL(""),
|
||||
_modelRotation(MODEL_DEFAULT_MODEL_ROTATION),
|
||||
|
||||
_id(UNKNOWN_MODEL_ID),
|
||||
_idSet(false),
|
||||
_lastEdited(usecTimestampNow()),
|
||||
|
||||
_positionChanged(false),
|
||||
_colorChanged(false),
|
||||
_radiusChanged(false),
|
||||
_shouldDieChanged(false),
|
||||
_modelURLChanged(false),
|
||||
_modelRotationChanged(false),
|
||||
_defaultSettings(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
uint16_t ModelItemProperties::getChangedBits() const {
|
||||
uint16_t changedBits = 0;
|
||||
if (_radiusChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_RADIUS;
|
||||
}
|
||||
|
||||
if (_positionChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_POSITION;
|
||||
}
|
||||
|
||||
if (_colorChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_COLOR;
|
||||
}
|
||||
|
||||
if (_shouldDieChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_SHOULDDIE;
|
||||
}
|
||||
|
||||
if (_modelURLChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_MODEL_URL;
|
||||
}
|
||||
|
||||
if (_modelRotationChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION;
|
||||
}
|
||||
|
||||
return changedBits;
|
||||
}
|
||||
|
||||
|
||||
QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const {
|
||||
QScriptValue properties = engine->newObject();
|
||||
|
||||
QScriptValue position = vec3toScriptValue(engine, _position);
|
||||
properties.setProperty("position", position);
|
||||
|
||||
QScriptValue color = xColorToScriptValue(engine, _color);
|
||||
properties.setProperty("color", color);
|
||||
|
||||
properties.setProperty("radius", _radius);
|
||||
|
||||
properties.setProperty("shouldDie", _shouldDie);
|
||||
|
||||
properties.setProperty("modelURL", _modelURL);
|
||||
|
||||
QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation);
|
||||
properties.setProperty("modelRotation", modelRotation);
|
||||
|
||||
|
||||
if (_idSet) {
|
||||
properties.setProperty("id", _id);
|
||||
properties.setProperty("isKnownID", (_id != UNKNOWN_MODEL_ID));
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) {
|
||||
|
||||
QScriptValue position = object.property("position");
|
||||
if (position.isValid()) {
|
||||
QScriptValue x = position.property("x");
|
||||
QScriptValue y = position.property("y");
|
||||
QScriptValue z = position.property("z");
|
||||
if (x.isValid() && y.isValid() && z.isValid()) {
|
||||
glm::vec3 newPosition;
|
||||
newPosition.x = x.toVariant().toFloat();
|
||||
newPosition.y = y.toVariant().toFloat();
|
||||
newPosition.z = z.toVariant().toFloat();
|
||||
if (_defaultSettings || newPosition != _position) {
|
||||
_position = newPosition;
|
||||
_positionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue color = object.property("color");
|
||||
if (color.isValid()) {
|
||||
QScriptValue red = color.property("red");
|
||||
QScriptValue green = color.property("green");
|
||||
QScriptValue blue = color.property("blue");
|
||||
if (red.isValid() && green.isValid() && blue.isValid()) {
|
||||
xColor newColor;
|
||||
newColor.red = red.toVariant().toInt();
|
||||
newColor.green = green.toVariant().toInt();
|
||||
newColor.blue = blue.toVariant().toInt();
|
||||
if (_defaultSettings || (newColor.red != _color.red ||
|
||||
newColor.green != _color.green ||
|
||||
newColor.blue != _color.blue)) {
|
||||
_color = newColor;
|
||||
_colorChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue radius = object.property("radius");
|
||||
if (radius.isValid()) {
|
||||
float newRadius;
|
||||
newRadius = radius.toVariant().toFloat();
|
||||
if (_defaultSettings || newRadius != _radius) {
|
||||
_radius = newRadius;
|
||||
_radiusChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue shouldDie = object.property("shouldDie");
|
||||
if (shouldDie.isValid()) {
|
||||
bool newShouldDie;
|
||||
newShouldDie = shouldDie.toVariant().toBool();
|
||||
if (_defaultSettings || newShouldDie != _shouldDie) {
|
||||
_shouldDie = newShouldDie;
|
||||
_shouldDieChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue modelURL = object.property("modelURL");
|
||||
if (modelURL.isValid()) {
|
||||
QString newModelURL;
|
||||
newModelURL = modelURL.toVariant().toString();
|
||||
if (_defaultSettings || newModelURL != _modelURL) {
|
||||
_modelURL = newModelURL;
|
||||
_modelURLChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue modelRotation = object.property("modelRotation");
|
||||
if (modelRotation.isValid()) {
|
||||
QScriptValue x = modelRotation.property("x");
|
||||
QScriptValue y = modelRotation.property("y");
|
||||
QScriptValue z = modelRotation.property("z");
|
||||
QScriptValue w = modelRotation.property("w");
|
||||
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
|
||||
glm::quat newModelRotation;
|
||||
newModelRotation.x = x.toVariant().toFloat();
|
||||
newModelRotation.y = y.toVariant().toFloat();
|
||||
newModelRotation.z = z.toVariant().toFloat();
|
||||
newModelRotation.w = w.toVariant().toFloat();
|
||||
if (_defaultSettings || newModelRotation != _modelRotation) {
|
||||
_modelRotation = newModelRotation;
|
||||
_modelRotationChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
||||
void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const {
|
||||
bool somethingChanged = false;
|
||||
if (_positionChanged) {
|
||||
modelItem.setPosition(_position / (float) TREE_SCALE);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_colorChanged) {
|
||||
modelItem.setColor(_color);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_radiusChanged) {
|
||||
modelItem.setRadius(_radius / (float) TREE_SCALE);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_shouldDieChanged) {
|
||||
modelItem.setShouldDie(_shouldDie);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_modelURLChanged) {
|
||||
modelItem.setModelURL(_modelURL);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_modelRotationChanged) {
|
||||
modelItem.setModelRotation(_modelRotation);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - _lastEdited;
|
||||
qDebug() << "ModelItemProperties::copyToModelItem() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " _lastEdited=" << _lastEdited;
|
||||
}
|
||||
modelItem.setLastEdited(_lastEdited);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
|
||||
_position = modelItem.getPosition() * (float) TREE_SCALE;
|
||||
_color = modelItem.getXColor();
|
||||
_radius = modelItem.getRadius() * (float) TREE_SCALE;
|
||||
_shouldDie = modelItem.getShouldDie();
|
||||
_modelURL = modelItem.getModelURL();
|
||||
_modelRotation = modelItem.getModelRotation();
|
||||
|
||||
_id = modelItem.getID();
|
||||
_idSet = true;
|
||||
|
||||
_positionChanged = false;
|
||||
_colorChanged = false;
|
||||
_radiusChanged = false;
|
||||
|
||||
_shouldDieChanged = false;
|
||||
_modelURLChanged = false;
|
||||
_modelRotationChanged = false;
|
||||
_defaultSettings = false;
|
||||
}
|
||||
|
||||
QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties) {
|
||||
return properties.copyToScriptValue(engine);
|
||||
}
|
||||
|
||||
void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties) {
|
||||
properties.copyFromScriptValue(object);
|
||||
}
|
||||
|
||||
|
||||
QScriptValue ModelItemIDtoScriptValue(QScriptEngine* engine, const ModelItemID& id) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
obj.setProperty("id", id.id);
|
||||
obj.setProperty("creatorTokenID", id.creatorTokenID);
|
||||
obj.setProperty("isKnownID", id.isKnownID);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& id) {
|
||||
id.id = object.property("id").toVariant().toUInt();
|
||||
id.creatorTokenID = object.property("creatorTokenID").toVariant().toUInt();
|
||||
id.isKnownID = object.property("isKnownID").toVariant().toBool();
|
||||
}
|
||||
|
||||
|
||||
|
253
libraries/models/src/ModelItem.h
Normal file
253
libraries/models/src/ModelItem.h
Normal file
|
@ -0,0 +1,253 @@
|
|||
//
|
||||
// ModelItem.h
|
||||
// libraries/models/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/4/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelItem_h
|
||||
#define hifi_ModelItem_h
|
||||
|
||||
#include <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_SHOULDDIE = 512;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048;
|
||||
|
||||
const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
|
||||
const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
|
||||
const QString MODEL_DEFAULT_MODEL_URL("");
|
||||
const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0);
|
||||
|
||||
/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model
|
||||
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of
|
||||
/// model item properties via JavaScript hashes/QScriptValues
|
||||
/// all units for position, radius, etc are in meter units
|
||||
class ModelItemProperties {
|
||||
public:
|
||||
ModelItemProperties();
|
||||
|
||||
QScriptValue copyToScriptValue(QScriptEngine* engine) const;
|
||||
void copyFromScriptValue(const QScriptValue& object);
|
||||
|
||||
void copyToModelItem(ModelItem& modelItem) const;
|
||||
void copyFromModelItem(const ModelItem& modelItem);
|
||||
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
xColor getColor() const { return _color; }
|
||||
float getRadius() const { return _radius; }
|
||||
bool getShouldDie() const { return _shouldDie; }
|
||||
|
||||
const QString& getModelURL() const { return _modelURL; }
|
||||
const glm::quat& getModelRotation() const { return _modelRotation; }
|
||||
|
||||
quint64 getLastEdited() const { return _lastEdited; }
|
||||
uint16_t getChangedBits() const;
|
||||
|
||||
/// set position in meter units
|
||||
void setPosition(const glm::vec3& value) { _position = value; _positionChanged = true; }
|
||||
void setColor(const xColor& value) { _color = value; _colorChanged = true; }
|
||||
void setRadius(float value) { _radius = value; _radiusChanged = true; }
|
||||
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; }
|
||||
|
||||
// model related properties
|
||||
void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; }
|
||||
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; }
|
||||
|
||||
/// used by ModelScriptingInterface to return ModelItemProperties for unknown models
|
||||
void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; }
|
||||
|
||||
private:
|
||||
glm::vec3 _position;
|
||||
xColor _color;
|
||||
float _radius;
|
||||
bool _shouldDie; /// to delete it
|
||||
|
||||
QString _modelURL;
|
||||
glm::quat _modelRotation;
|
||||
|
||||
uint32_t _id;
|
||||
bool _idSet;
|
||||
quint64 _lastEdited;
|
||||
|
||||
bool _positionChanged;
|
||||
bool _colorChanged;
|
||||
bool _radiusChanged;
|
||||
bool _shouldDieChanged;
|
||||
|
||||
bool _modelURLChanged;
|
||||
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 model items. Used in ModelItem JS API - When models are created in the JS api, they are given a
|
||||
/// local creatorTokenID, the actual id for the model is not known until the server responds to the creator with the
|
||||
/// correct mapping. This class works with the scripting API an allows the developer to edit models they created.
|
||||
class ModelItemID {
|
||||
public:
|
||||
ModelItemID() :
|
||||
id(NEW_MODEL), creatorTokenID(UNKNOWN_MODEL_TOKEN), isKnownID(false) { };
|
||||
|
||||
ModelItemID(uint32_t id, uint32_t creatorTokenID, bool isKnownID) :
|
||||
id(id), creatorTokenID(creatorTokenID), isKnownID(isKnownID) { };
|
||||
|
||||
ModelItemID(uint32_t id) :
|
||||
id(id), creatorTokenID(UNKNOWN_MODEL_TOKEN), isKnownID(true) { };
|
||||
|
||||
uint32_t id;
|
||||
uint32_t creatorTokenID;
|
||||
bool isKnownID;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ModelItemID);
|
||||
Q_DECLARE_METATYPE(QVector<ModelItemID>);
|
||||
QScriptValue ModelItemIDtoScriptValue(QScriptEngine* engine, const ModelItemID& properties);
|
||||
void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& properties);
|
||||
|
||||
|
||||
|
||||
/// ModelItem class - this is the actual model item class.
|
||||
class ModelItem {
|
||||
|
||||
public:
|
||||
ModelItem();
|
||||
|
||||
ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& properties);
|
||||
|
||||
/// creates an NEW model from an model add or edit message data buffer
|
||||
static ModelItem fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid);
|
||||
|
||||
virtual ~ModelItem();
|
||||
virtual void init(glm::vec3 position, float radius, rgbColor color, uint32_t id = NEW_MODEL);
|
||||
|
||||
/// get position in domain scale units (0.0 - 1.0)
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
|
||||
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; }
|
||||
|
||||
// model related properties
|
||||
bool hasModel() const { return !_modelURL.isEmpty(); }
|
||||
const QString& getModelURL() const { return _modelURL; }
|
||||
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 model from the time perspective of the authoritative server/source
|
||||
quint64 getLastUpdated() const { return _lastUpdated; }
|
||||
|
||||
/// The last edited time of this model from the time perspective of the authoritative server/source
|
||||
quint64 getLastEdited() const { return _lastEdited; }
|
||||
void setLastEdited(quint64 lastEdited) { _lastEdited = lastEdited; }
|
||||
|
||||
/// how long ago was this model edited in seconds
|
||||
float getEditedAgo() const { return static_cast<float>(usecTimestampNow() - _lastEdited) / static_cast<float>(USECS_PER_SECOND); }
|
||||
uint32_t getID() const { return _id; }
|
||||
void setID(uint32_t id) { _id = id; }
|
||||
bool getShouldDie() const { return _shouldDie; }
|
||||
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
|
||||
bool isNewlyCreated() const { return _newlyCreated; }
|
||||
|
||||
/// set position in domain scale units (0.0 - 1.0)
|
||||
void setPosition(const glm::vec3& value) { _position = value; }
|
||||
|
||||
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
|
||||
void setColor(const xColor& value) {
|
||||
_color[RED_INDEX] = value.red;
|
||||
_color[GREEN_INDEX] = value.green;
|
||||
_color[BLUE_INDEX] = value.blue;
|
||||
}
|
||||
/// set radius in domain scale units (0.0 - 1.0)
|
||||
void setRadius(float value) { _radius = value; }
|
||||
|
||||
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; }
|
||||
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
|
||||
|
||||
// model related properties
|
||||
void setModelURL(const QString& url) { _modelURL = url; }
|
||||
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; }
|
||||
|
||||
void 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 update(const quint64& now);
|
||||
|
||||
void debugDump() const;
|
||||
|
||||
// similar to assignment/copy, but it handles keeping lifetime accurate
|
||||
void copyChangedProperties(const ModelItem& other);
|
||||
|
||||
// these methods allow you to create models, and later edit them.
|
||||
static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID);
|
||||
static uint32_t getNextCreatorTokenID();
|
||||
static void handleAddModelResponse(const QByteArray& packet);
|
||||
|
||||
protected:
|
||||
glm::vec3 _position;
|
||||
rgbColor _color;
|
||||
float _radius;
|
||||
uint32_t _id;
|
||||
static uint32_t _nextID;
|
||||
bool _shouldDie;
|
||||
|
||||
// model related items
|
||||
QString _modelURL;
|
||||
glm::quat _modelRotation;
|
||||
|
||||
uint32_t _creatorTokenID;
|
||||
bool _newlyCreated;
|
||||
|
||||
quint64 _lastUpdated;
|
||||
quint64 _lastEdited;
|
||||
|
||||
// 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(MINIMUM_MODEL_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(MINIMUM_MODEL_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 PacketTypeModelErase messages
|
||||
// instead of using PacketTypeModelAddOrEdit messages to delete models
|
||||
case PacketTypeModelErase:
|
||||
processedBytes = 0;
|
||||
break;
|
||||
default:
|
||||
processedBytes = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return processedBytes;
|
||||
}
|
||||
|
||||
void ModelTree::notifyNewlyCreatedModel(const ModelItem& newModel, const SharedNodePointer& senderNode) {
|
||||
_newlyCreatedHooksLock.lockForRead();
|
||||
for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) {
|
||||
_newlyCreatedHooks[i]->modelCreated(newModel, senderNode);
|
||||
}
|
||||
_newlyCreatedHooksLock.unlock();
|
||||
}
|
||||
|
||||
void ModelTree::addNewlyCreatedHook(NewlyCreatedModelHook* hook) {
|
||||
_newlyCreatedHooksLock.lockForWrite();
|
||||
_newlyCreatedHooks.push_back(hook);
|
||||
_newlyCreatedHooksLock.unlock();
|
||||
}
|
||||
|
||||
void ModelTree::removeNewlyCreatedHook(NewlyCreatedModelHook* hook) {
|
||||
_newlyCreatedHooksLock.lockForWrite();
|
||||
for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) {
|
||||
if (_newlyCreatedHooks[i] == hook) {
|
||||
_newlyCreatedHooks.erase(_newlyCreatedHooks.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_newlyCreatedHooksLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
bool ModelTree::updateOperation(OctreeElement* element, void* extraData) {
|
||||
ModelTreeUpdateArgs* args = static_cast<ModelTreeUpdateArgs*>(extraData);
|
||||
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
|
||||
modelTreeElement->update(*args);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModelTree::pruneOperation(OctreeElement* element, void* extraData) {
|
||||
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
ModelTreeElement* childAt = modelTreeElement->getChildAtIndex(i);
|
||||
if (childAt && childAt->isLeaf() && !childAt->hasModels()) {
|
||||
modelTreeElement->deleteChildAtIndex(i);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModelTree::update() {
|
||||
lockForWrite();
|
||||
_isDirty = true;
|
||||
|
||||
ModelTreeUpdateArgs args = { };
|
||||
recurseTreeWithOperation(updateOperation, &args);
|
||||
|
||||
// now add back any of the 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
|
329
libraries/models/src/ModelTreeElement.cpp
Normal file
329
libraries/models/src/ModelTreeElement.cpp
Normal file
|
@ -0,0 +1,329 @@
|
|||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
|
@ -29,6 +29,8 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) {
|
|||
return Assignment::VoxelServerType;
|
||||
case NodeType::ParticleServer:
|
||||
return Assignment::ParticleServerType;
|
||||
case NodeType::ModelServer:
|
||||
return Assignment::ModelServerType;
|
||||
case NodeType::MetavoxelServer:
|
||||
return Assignment::MetavoxelServerType;
|
||||
default:
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
VoxelServerType,
|
||||
ParticleServerType,
|
||||
MetavoxelServerType,
|
||||
ModelServerType,
|
||||
AllTypes
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ void NodeType::init() {
|
|||
TypeNameHash.insert(NodeType::DomainServer, "Domain Server");
|
||||
TypeNameHash.insert(NodeType::VoxelServer, "Voxel Server");
|
||||
TypeNameHash.insert(NodeType::ParticleServer, "Particle Server");
|
||||
TypeNameHash.insert(NodeType::ModelServer, "Model Server");
|
||||
TypeNameHash.insert(NodeType::MetavoxelServer, "Metavoxel Server");
|
||||
TypeNameHash.insert(NodeType::Agent, "Agent");
|
||||
TypeNameHash.insert(NodeType::AudioMixer, "Audio Mixer");
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace NodeType {
|
|||
const NodeType_t DomainServer = 'D';
|
||||
const NodeType_t VoxelServer = 'V';
|
||||
const NodeType_t ParticleServer = 'P';
|
||||
const NodeType_t ModelServer = 'o';
|
||||
const NodeType_t MetavoxelServer = 'm';
|
||||
const NodeType_t EnvironmentServer = 'E';
|
||||
const NodeType_t Agent = 'I';
|
||||
|
|
|
@ -61,6 +61,11 @@ enum PacketType {
|
|||
PacketTypeDomainConnectRequest,
|
||||
PacketTypeDomainServerRequireDTLS,
|
||||
PacketTypeNodeJsonStats,
|
||||
PacketTypeModelQuery,
|
||||
PacketTypeModelData,
|
||||
PacketTypeModelAddOrEdit,
|
||||
PacketTypeModelErase,
|
||||
PacketTypeModelAddResponse,
|
||||
};
|
||||
|
||||
typedef char PacketVersion;
|
||||
|
@ -69,7 +74,7 @@ const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
|||
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
|
||||
<< PacketTypeDomainList << PacketTypeDomainListRequest
|
||||
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
|
||||
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery;
|
||||
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeModelQuery;
|
||||
|
||||
const int NUM_BYTES_MD5_HASH = 16;
|
||||
const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID;
|
||||
|
|
|
@ -320,7 +320,7 @@ public:
|
|||
QVector<Particle*> _foundParticles;
|
||||
};
|
||||
|
||||
bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData) {
|
||||
bool ParticleTree::findInBoxForUpdateOperation(OctreeElement* element, void* extraData) {
|
||||
FindParticlesInBoxArgs* args = static_cast< FindParticlesInBoxArgs*>(extraData);
|
||||
const AABox& elementBox = element->getAABox();
|
||||
if (elementBox.touches(args->_box)) {
|
||||
|
|
|
@ -84,6 +84,7 @@ private:
|
|||
static bool findByIDOperation(OctreeElement* element, void* extraData);
|
||||
static bool findAndDeleteOperation(OctreeElement* element, void* extraData);
|
||||
static bool findAndUpdateParticleIDOperation(OctreeElement* element, void* extraData);
|
||||
static bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData);
|
||||
|
||||
void notifyNewlyCreatedParticle(const Particle& newParticle, const SharedNodePointer& senderNode);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# link ZLIB
|
||||
find_package(ZLIB)
|
||||
|
|
|
@ -21,14 +21,14 @@
|
|||
#include <AudioRingBuffer.h>
|
||||
#include <AvatarData.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <ModelsScriptingInterface.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
#include <Sound.h>
|
||||
#include <UUID.h>
|
||||
#include <VoxelConstants.h>
|
||||
#include <VoxelDetail.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
|
||||
#include <Sound.h>
|
||||
|
||||
#include "AnimationObject.h"
|
||||
#include "MenuItemProperties.h"
|
||||
|
@ -37,6 +37,7 @@
|
|||
|
||||
VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface;
|
||||
ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface;
|
||||
ModelsScriptingInterface ScriptEngine::_modelsScriptingInterface;
|
||||
|
||||
static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
QUrl soundURL = QUrl(context->argument(0).toString());
|
||||
|
@ -204,6 +205,11 @@ void ScriptEngine::init() {
|
|||
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
|
||||
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
|
||||
qScriptRegisterSequenceMetaType<QVector<ParticleID> >(&_engine);
|
||||
|
||||
qScriptRegisterMetaType(&_engine, ModelItemPropertiesToScriptValue, ModelItemPropertiesFromScriptValue);
|
||||
qScriptRegisterMetaType(&_engine, ModelItemIDtoScriptValue, ModelItemIDfromScriptValue);
|
||||
qScriptRegisterSequenceMetaType<QVector<ModelItemID> >(&_engine);
|
||||
|
||||
qScriptRegisterSequenceMetaType<QVector<glm::vec2> >(&_engine);
|
||||
qScriptRegisterSequenceMetaType<QVector<glm::quat> >(&_engine);
|
||||
qScriptRegisterSequenceMetaType<QVector<QString> >(&_engine);
|
||||
|
@ -224,6 +230,7 @@ void ScriptEngine::init() {
|
|||
registerGlobalObject("Script", this);
|
||||
registerGlobalObject("Audio", &_audioScriptingInterface);
|
||||
registerGlobalObject("Controller", _controllerScriptingInterface);
|
||||
registerGlobalObject("Models", &_modelsScriptingInterface);
|
||||
registerGlobalObject("Particles", &_particlesScriptingInterface);
|
||||
registerGlobalObject("Quat", &_quatLibrary);
|
||||
registerGlobalObject("Vec3", &_vec3Library);
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
#include "ScriptUUID.h"
|
||||
#include "Vec3.h"
|
||||
|
||||
class ModelsScriptingInterface;
|
||||
class ParticlesScriptingInterface;
|
||||
class VoxelsScriptingInterface;
|
||||
|
||||
const QString NO_SCRIPT("");
|
||||
|
||||
|
@ -52,6 +54,9 @@ public:
|
|||
/// Access the ParticlesScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||
static ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; }
|
||||
|
||||
/// Access the ModelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||
static ModelsScriptingInterface* getModelsScriptingInterface() { return &_modelsScriptingInterface; }
|
||||
|
||||
/// sets the script contents, will return false if failed, will fail if script is already running
|
||||
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
||||
|
||||
|
@ -128,6 +133,7 @@ private:
|
|||
|
||||
static VoxelsScriptingInterface _voxelsScriptingInterface;
|
||||
static ParticlesScriptingInterface _particlesScriptingInterface;
|
||||
static ModelsScriptingInterface _modelsScriptingInterface;
|
||||
|
||||
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
||||
AudioScriptingInterface _audioScriptingInterface;
|
||||
|
|
Loading…
Reference in a new issue