Conflicts:
	interface/src/avatar/MyAvatar.h
This commit is contained in:
Andrzej Kapolka 2014-05-01 21:00:32 -07:00
commit 3ebe1df6de
71 changed files with 4268 additions and 189 deletions

View file

@ -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}")

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -0,0 +1,34 @@
//
// ModelNodeData.h
// assignment-client/src/models
//
// Created by Brad Hefta-Gaub on 4/29/14
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelNodeData_h
#define hifi_ModelNodeData_h
#include <PacketHeaders.h>
#include "../octree/OctreeQueryNode.h"
class ModelNodeData : public OctreeQueryNode {
public:
ModelNodeData() :
OctreeQueryNode(),
_lastDeletedModelsSentAt(0) { };
virtual PacketType getMyPacketType() const { return PacketTypeModelData; }
quint64 getLastDeletedModelsSentAt() const { return _lastDeletedModelsSentAt; }
void setLastDeletedModelsSentAt(quint64 sentAt) { _lastDeletedModelsSentAt = sentAt; }
private:
quint64 _lastDeletedModelsSentAt;
};
#endif // hifi_ModelNodeData_h

View file

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

View file

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

View file

@ -0,0 +1,19 @@
//
// ModelServerConsts.h
// assignment-client/src/models
//
// Created by Brad Hefta-Gaub on 4/29/14
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelServerConsts_h
#define hifi_ModelServerConsts_h
extern const char* MODEL_SERVER_NAME;
extern const char* MODEL_SERVER_LOGGING_TARGET_NAME;
extern const char* LOCAL_MODELS_PERSIST_FILE;
#endif // hifi_ModelServerConsts_h

View file

@ -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);

View file

@ -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) {

View file

@ -0,0 +1,7 @@
Window.alert("This is an alert box");
var confirmed = Window.confirm("This is a confirmation dialog")
Window.alert("Your response was: " + confirmed);
var prompt = Window.prompt("This is a prompt dialog", "This is the default text");
Window.alert("Your response was: " + prompt);

View 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);

View file

@ -0,0 +1,13 @@
var file = Window.browse("File Browser Example", "/");
if (file === null) {
Window.alert("No file was selected");
} else {
Window.alert("Selected file: " + file);
}
file = Window.browse("Relative Directory Example", "./images", "PNG or JPG files(*.png *.jpg);;SVG files (*.svg)");
if (file === null) {
Window.alert("No file was selected");
} else {
Window.alert("Selected file: " + file);
}

View file

@ -0,0 +1,11 @@
var goto = Window.prompt("Where would you like to go? (ex. @username, #location, 1.0,500.3,100.2)");
var url = "hifi://" + goto;
print("Going to: " + url);
location = url;
// If the destination location is a user or named location the new location may not be set by the time execution reaches here
// because it requires an asynchronous lookup. Coordinate changes should be be reflected immediately, though. (ex: hifi://0,0,0)
print("URL: " + location.href);
print("Protocol: " + location.protocol);
print("Hostname: " + location.hostname);
print("Pathname: " + location.pathname);

12
examples/windowExample.js Normal file
View file

@ -0,0 +1,12 @@
var width = 0,
height = 0;
function onUpdate(dt) {
if (width != Window.innerWidth || height != Window.innerHeight) {
width = Window.innerWidth;
height = Window.innerHeight;
print("New window dimensions: " + width + ", " + height);
}
}
Script.update.connect(onUpdate);

View file

@ -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}")

View file

@ -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>
@ -76,6 +77,8 @@
#include "scripting/ClipboardScriptingInterface.h"
#include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include "scripting/WindowScriptingInterface.h"
#include "scripting/LocationScriptingInterface.h"
#include "ui/InfoView.h"
#include "ui/Snapshot.h"
@ -169,7 +172,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
{
// init GnuTLS for DTLS with domain-servers
DTLSClientSession::globalInit();
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
@ -264,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
@ -345,13 +348,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// clear the scripts, and set out script to our default scripts
clearScriptsBeforeRunning();
loadScript("http://public.highfidelity.io/scripts/defaultScripts.js");
QMutexLocker locker(&_settingsMutex);
_settings->setValue("firstRun",QVariant(false));
} else {
// do this as late as possible so that all required subsystems are inialized
loadScripts();
QMutexLocker locker(&_settingsMutex);
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
}
@ -369,11 +372,11 @@ Application::~Application() {
// make sure we don't call the idle timer any more
delete idleTimer;
_sharedVoxelSystem.changeTree(new VoxelTree);
saveSettings();
delete _voxelImporter;
// let the avatar mixer know we're out
@ -406,14 +409,14 @@ Application::~Application() {
delete _glWidget;
AccountManager::getInstance().destroy();
DTLSClientSession::globalDeinit();
}
void Application::saveSettings() {
Menu::getInstance()->saveSettings();
_rearMirrorTools->saveSettings(_settings);
if (_voxelImporter) {
_voxelImporter->saveSettings(_settings);
}
@ -580,7 +583,7 @@ void Application::paintGL() {
pushback = qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale());
const float BASE_PUSHBACK_FOCAL_LENGTH = 0.5f;
pushbackFocalLength = BASE_PUSHBACK_FOCAL_LENGTH * _myAvatar->getScale();
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
@ -593,7 +596,7 @@ void Application::paintGL() {
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale());
_myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0));
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f)));
// if the head would intersect the near clip plane, we must push the camera out
glm::vec3 relativePosition = glm::inverse(_myCamera.getTargetRotation()) *
(eyePosition - _myCamera.getTargetPosition());
@ -602,7 +605,7 @@ void Application::paintGL() {
pushback = relativePosition.z + pushbackRadius - _myCamera.getDistance();
pushbackFocalLength = _myCamera.getDistance();
}
// handle pushback, if any
if (pushbackFocalLength > 0.0f) {
const float PUSHBACK_DECAY = 0.5f;
@ -764,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:
@ -1270,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);
}
@ -1281,7 +1286,7 @@ void Application::timer() {
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
sendPingPackets();
}
float diffTime = (float)_timerStart.nsecsElapsed() / 1000000000.0f;
_fps = (float)_frameCount / diffTime;
@ -1663,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);
@ -1682,7 +1690,7 @@ void Application::init() {
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
// set up our audio reflector
_audioReflector.setMyAvatar(getAvatar());
_audioReflector.setVoxels(_voxels.getTree());
@ -1691,7 +1699,7 @@ void Application::init() {
connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection);
connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection);
connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector,
connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector,
&AudioReflector::preProcessOriginalInboundAudio,Qt::DirectConnection);
// save settings when avatar changes
@ -1825,7 +1833,7 @@ void Application::updateMyAvatarLookAtPosition() {
if (tracker) {
float eyePitch = tracker->getEstimatedEyePitch();
float eyeYaw = tracker->getEstimatedEyeYaw();
// deflect using Faceshift gaze data
glm::vec3 origin = _myAvatar->getHead()->calculateAverageEyePosition();
float pitchSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? -1.0f : 1.0f;
@ -1912,7 +1920,7 @@ void Application::updateCamera(float deltaTime) {
PerformanceWarning warn(showWarnings, "Application::updateCamera()");
if (!OculusManager::isConnected() && !TV3DManager::isConnected() &&
Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) {
Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) {
FaceTracker* tracker = getActiveFaceTracker();
if (tracker) {
const float EYE_OFFSET_SCALE = 0.025f;
@ -1993,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
@ -2031,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;
}
}
@ -2332,6 +2343,7 @@ void Application::updateShadowMap() {
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
_particles.render();
_models.render();
glPopMatrix();
@ -2467,7 +2479,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
// disable specular lighting for ground and voxels
glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR);
// draw the audio reflector overlay
_audioReflector.render();
@ -2498,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),
@ -2625,7 +2644,7 @@ void Application::displayOverlay() {
const float LOG2_LOUDNESS_FLOOR = 11.f;
float audioLevel = 0.f;
float loudness = _audio.getLastInputLoudness() + 1.f;
_trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.f - AUDIO_METER_AVERAGING) * loudness;
float log2loudness = log(_trailingAudioLoudness) / LOG2;
@ -2638,7 +2657,7 @@ void Application::displayOverlay() {
audioLevel = AUDIO_METER_SCALE_WIDTH;
}
bool isClipping = ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME));
if ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) {
const float MAX_MAGNITUDE = 0.7f;
float magnitude = MAX_MAGNITUDE * (1 - _audio.getTimeSinceLastClip() / CLIPPING_INDICATOR_TIME);
@ -2709,7 +2728,7 @@ void Application::displayOverlay() {
if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) {
_myAvatar->renderHeadMouse();
_myAvatar->renderHeadMouse(_glWidget->width(), _glWidget->height());
}
// Display stats and log text onscreen
@ -3092,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();
}
@ -3168,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));
}
@ -3179,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();
@ -3231,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;
}
@ -3276,7 +3331,7 @@ void Application::loadScripts() {
loadScript(string);
}
}
QMutexLocker locker(&_settingsMutex);
_settings->endArray();
}
@ -3357,7 +3412,7 @@ void Application::uploadFST(bool isHead) {
thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit()));
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
uploader->connect(thread, SIGNAL(started()), SLOT(send()));
thread->start();
}
@ -3392,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
@ -3404,6 +3462,15 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater()));
scriptEngine->registerGlobalObject("Overlays", &_overlays);
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter, windowValue);
// register `location` on the global object.
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
@ -3436,9 +3503,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
return scriptEngine;
}
void Application::loadDialog() {
QString Application::getPreviousScriptLocation() {
QString suggestedName;
if (_previousScriptLocation.isEmpty()) {
QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475
@ -3448,14 +3514,22 @@ void Application::loadDialog() {
} else {
suggestedName = _previousScriptLocation;
}
return suggestedName;
}
QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName,
void Application::setPreviousScriptLocation(const QString& previousScriptLocation) {
_previousScriptLocation = previousScriptLocation;
QMutexLocker locker(&_settingsMutex);
_settings->setValue("LastScriptLocation", _previousScriptLocation);
}
void Application::loadDialog() {
QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"),
getPreviousScriptLocation(),
tr("JavaScript Files (*.js)"));
if (!fileNameString.isEmpty()) {
_previousScriptLocation = fileNameString;
QMutexLocker locker(&_settingsMutex);
_settings->setValue("LastScriptLocation", _previousScriptLocation);
setPreviousScriptLocation(fileNameString);
loadScript(fileNameString);
}
}
@ -3592,7 +3666,7 @@ void Application::urlGoTo(int argc, const char * constArgv[]) {
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
Menu::goToDomain(domain);
// goto either @user, #place, or x-xx,y-yy,z-zz
// style co-ordinate.
Menu::goTo(destination);

View file

@ -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"
@ -125,6 +127,8 @@ public:
void restoreSizeAndPosition();
ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false);
void loadScripts();
QString getPreviousScriptLocation();
void setPreviousScriptLocation(const QString& previousScriptLocation);
void storeSizeAndPosition();
void clearScriptsBeforeRunning();
void saveScripts();
@ -245,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);
@ -415,6 +420,8 @@ private:
ParticleTreeRenderer _particles;
ParticleCollisionSystem _particleCollisionSystem;
ModelTreeRenderer _models;
QByteArray _voxelsFilename;
bool _wantToKillLocalVoxels;
@ -492,6 +499,7 @@ private:
VoxelHideShowThread _voxelHideShowThread;
VoxelEditPacketSender _voxelEditSender;
ParticleEditPacketSender _particleEditSender;
ModelEditPacketSender _modelEditSender;
int _packetsPerSecond;
int _bytesPerSecond;
@ -504,6 +512,7 @@ private:
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
NodeToJurisdictionMap _modelServerJurisdictions;
NodeToOctreeSceneStats _octreeServerSceneStats;
QReadWriteLock _octreeSceneStatsLock;

View file

@ -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");
@ -898,44 +899,53 @@ void Menu::goTo() {
int dialogReturn = gotoDialog.exec();
if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
QString desiredDestination = gotoDialog.textValue();
if (desiredDestination.startsWith(CUSTOM_URL_SCHEME + "//")) {
QStringList urlParts = desiredDestination.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
if (urlParts.count() == 1) {
// location coordinates or place name
QString domain = urlParts[0];
goToDomain(domain);
}
else if (urlParts.count() > 1) {
// if url has 2 or more parts, the first one is domain name
QString domain = urlParts[0];
// second part is either a destination coordinate or
// a place name
QString destination = urlParts[1];
// any third part is an avatar orientation.
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
goToDomain(domain);
// goto either @user, #place, or x-xx,y-yy,z-zz
// style co-ordinate.
goTo(destination);
if (!orientation.isEmpty()) {
// location orientation
goToOrientation(orientation);
}
}
} else {
goToUser(gotoDialog.textValue());
if (!goToURL(desiredDestination)) {;
goTo(desiredDestination);
}
}
sendFakeEnterEvent();
}
bool Menu::goToURL(QString location) {
if (location.startsWith(CUSTOM_URL_SCHEME + "//")) {
QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
if (urlParts.count() > 1) {
// if url has 2 or more parts, the first one is domain name
QString domain = urlParts[0];
// second part is either a destination coordinate or
// a place name
QString destination = urlParts[1];
// any third part is an avatar orientation.
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
goToDomain(domain);
// goto either @user, #place, or x-xx,y-yy,z-zz
// style co-ordinate.
goTo(destination);
if (!orientation.isEmpty()) {
// location orientation
goToOrientation(orientation);
}
} else if (urlParts.count() == 1) {
QString destination = urlParts[0];
// If this starts with # or @, treat it as a user/location, otherwise treat it as a domain
if (destination[0] == '#' || destination[0] == '@') {
goTo(destination);
} else {
goToDomain(destination);
}
}
return true;
}
return false;
}
void Menu::goToUser(const QString& user) {
LocationManager* manager = &LocationManager::getInstance();
manager->goTo(user);

View file

@ -152,6 +152,7 @@ public slots:
void importSettings();
void exportSettings();
void goTo();
bool goToURL(QString location);
void goToUser(const QString& user);
void pasteToVoxel();
void openUrl(const QUrl& url);
@ -276,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";
@ -330,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";

View file

@ -16,12 +16,13 @@ ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) :
QSyntaxHighlighter(parent)
{
_keywordRegex = QRegExp("\\b(break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|this|throw|try|typeof|var|void|while|with)\\b");
_qoutedTextRegex = QRegExp("\".*\"");
_qoutedTextRegex = QRegExp("\"[^\"]*(\"){0,1}");
_multiLineCommentBegin = QRegExp("/\\*");
_multiLineCommentEnd = QRegExp("\\*/");
_numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}");
_singleLineComment = QRegExp("//[^\n]*");
_truefalseRegex = QRegExp("\\b(true|false)\\b");
_alphacharRegex = QRegExp("[A-Za-z]");
}
void ScriptHighlighting::highlightBlock(const QString& text) {
@ -60,7 +61,19 @@ void ScriptHighlighting::formatComments(const QString& text) {
int index = _singleLineComment.indexIn(text);
while (index >= 0) {
int length = _singleLineComment.matchedLength();
setFormat(index, length, Qt::lightGray);
int quoted_index = _qoutedTextRegex.indexIn(text);
bool valid = true;
while (quoted_index >= 0 && valid) {
int quoted_length = _qoutedTextRegex.matchedLength();
if (quoted_index <= index && index <= (quoted_index + quoted_length)) {
valid = false;
}
quoted_index = _qoutedTextRegex.indexIn(text, quoted_index + quoted_length);
}
if (valid) {
setFormat(index, length, Qt::lightGray);
}
index = _singleLineComment.indexIn(text, index + length);
}
}
@ -78,7 +91,9 @@ void ScriptHighlighting::formatNumbers(const QString& text){
int index = _numberRegex.indexIn(text);
while (index >= 0) {
int length = _numberRegex.matchedLength();
setFormat(index, length, Qt::green);
if (index == 0 || _alphacharRegex.indexIn(text, index - 1) != (index - 1)) {
setFormat(index, length, Qt::green);
}
index = _numberRegex.indexIn(text, index + length);
}
}

View file

@ -34,6 +34,7 @@ protected:
void formatTrueFalse(const QString& text);
private:
QRegExp _alphacharRegex;
QRegExp _keywordRegex;
QRegExp _qoutedTextRegex;
QRegExp _multiLineCommentBegin;

View file

@ -185,7 +185,7 @@ protected:
float getPelvisToHeadLength() const;
void renderDisplayName();
virtual void renderBody(RenderMode renderMode, float glowLevel);
virtual void renderBody(RenderMode renderMode, float glowLevel = 0.0f);
virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const;
virtual void updateJointMappings();

View file

@ -82,11 +82,8 @@ MyAvatar::~MyAvatar() {
}
void MyAvatar::reset() {
// TODO? resurrect headMouse stuff?
//_headMouseX = _glWidget->width() / 2;
//_headMouseY = _glWidget->height() / 2;
_skeletonModel.reset();
getHead()->reset();
getHead()->reset();
getHand()->reset();
_oculusYawOffset = 0.0f;
@ -103,23 +100,7 @@ void MyAvatar::update(float deltaTime) {
// Faceshift drive is enabled, set the avatar drive based on the head position
moveWithLean();
}
// Update head mouse from faceshift if active
Faceshift* faceshift = Application::getInstance()->getFaceshift();
if (faceshift->isActive()) {
// TODO? resurrect headMouse stuff?
//glm::vec3 headVelocity = faceshift->getHeadAngularVelocity();
//// sets how quickly head angular rotation moves the head mouse
//const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.0f;
//const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.0f;
//_headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE;
//_headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE;
//
//// Constrain head-driven mouse to edges of screen
//_headMouseX = glm::clamp(_headMouseX, 0, _glWidget->width());
//_headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height());
}
// Get audio loudness data from audio input device
Audio* audio = Application::getInstance()->getAudio();
head->setAudioLoudness(audio->getLastInputLoudness());
@ -422,32 +403,41 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
}
}
void MyAvatar::renderHeadMouse() const {
// TODO? resurrect headMouse stuff?
/*
void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const {
Faceshift* faceshift = Application::getInstance()->getFaceshift();
// Display small target box at center or head mouse target that can also be used to measure LOD
float headPitch = getHead()->getFinalPitch();
float headYaw = getHead()->getFinalYaw();
//
// It should be noted that the following constant is a function
// how far the viewer's head is away from both the screen and the size of the screen,
// which are both things we cannot know without adding a calibration phase.
//
const float PIXELS_PER_VERTICAL_DEGREE = 20.0f;
float aspectRatio = (float) screenWidth / (float) screenHeight;
int headMouseX = screenWidth / 2.f - headYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE;
int headMouseY = screenHeight / 2.f - headPitch * PIXELS_PER_VERTICAL_DEGREE;
glColor3f(1.0f, 1.0f, 1.0f);
glDisable(GL_LINE_SMOOTH);
const int PIXEL_BOX = 16;
glBegin(GL_LINES);
glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY);
glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY);
glVertex2f(_headMouseX, _headMouseY - PIXEL_BOX/2);
glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2);
glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY);
glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY);
glVertex2f(headMouseX, headMouseY - PIXEL_BOX/2);
glVertex2f(headMouseX, headMouseY + PIXEL_BOX/2);
glEnd();
glEnable(GL_LINE_SMOOTH);
glColor3f(1.0f, 0.0f, 0.0f);
glPointSize(3.0f);
glDisable(GL_POINT_SMOOTH);
glBegin(GL_POINTS);
glVertex2f(_headMouseX - 1, _headMouseY + 1);
glEnd();
// If Faceshift is active, show eye pitch and yaw as separate pointer
if (_faceshift.isActive()) {
const float EYE_TARGET_PIXELS_PER_DEGREE = 40.0;
int eyeTargetX = (_glWidget->width() / 2) - _faceshift.getEstimatedEyeYaw() * EYE_TARGET_PIXELS_PER_DEGREE;
int eyeTargetY = (_glWidget->height() / 2) - _faceshift.getEstimatedEyePitch() * EYE_TARGET_PIXELS_PER_DEGREE;
if (faceshift->isActive()) {
float avgEyePitch = faceshift->getEstimatedEyePitch();
float avgEyeYaw = faceshift->getEstimatedEyeYaw();
int eyeTargetX = (screenWidth / 2) - avgEyeYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE;
int eyeTargetY = (screenHeight / 2) - avgEyePitch * PIXELS_PER_VERTICAL_DEGREE;
glColor3f(0.0f, 1.0f, 1.0f);
glDisable(GL_LINE_SMOOTH);
glBegin(GL_LINES);
@ -458,7 +448,6 @@ void MyAvatar::renderHeadMouse() const {
glEnd();
}
*/
}
void MyAvatar::setLocalGravity(glm::vec3 gravity) {

View file

@ -42,10 +42,10 @@ public:
void moveWithLean();
void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE);
void renderBody(RenderMode renderMode, float glowLevel);
void renderBody(RenderMode renderMode, float glowLevel = 0.0f);
bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const;
void renderDebugBodyPoints();
void renderHeadMouse() const;
void renderHeadMouse(int screenWidth, int screenHeight) const;
// setters
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
@ -123,7 +123,7 @@ private:
glm::vec3 _gravity;
glm::vec3 _environmentGravity;
float _distanceToNearestAvatar; // How close is the nearest avatar?
// motion stuff
glm::vec3 _lastCollisionPosition;
bool _speedBrakes;

View 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);
}

View 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

View file

@ -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(),
@ -61,6 +66,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;
@ -71,6 +83,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");
@ -358,6 +379,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;
@ -771,9 +809,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);
}
}

View file

@ -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);

View file

@ -0,0 +1,49 @@
//
// LocationScriptingInterface.cpp
// interface/src/scripting
//
// Created by Ryan Huffman 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 <glm/glm.hpp>
#include "NodeList.h"
#include "LocationScriptingInterface.h"
LocationScriptingInterface* LocationScriptingInterface::getInstance() {
static LocationScriptingInterface sharedInstance;
return &sharedInstance;
}
QString LocationScriptingInterface::getHref() {
return getProtocol() + "//" + getHostname() + getPathname();
}
QString LocationScriptingInterface::getPathname() {
const glm::vec3& position = Application::getInstance()->getAvatar()->getPosition();
QString path;
path.sprintf("/%.4f,%.4f,%.4f", position.x, position.y, position.z);
return path;
}
QString LocationScriptingInterface::getHostname() {
return NodeList::getInstance()->getDomainHandler().getHostname();
}
void LocationScriptingInterface::assign(const QString& url) {
QMetaObject::invokeMethod(Menu::getInstance(), "goToURL", Q_ARG(const QString&, url));
}
QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) {
return engine->newQObject(getInstance());
}
QScriptValue LocationScriptingInterface::locationSetter(QScriptContext* context, QScriptEngine* engine) {
LocationScriptingInterface::getInstance()->assign(context->argument(0).toString());
return QScriptValue::UndefinedValue;
}

View file

@ -0,0 +1,46 @@
//
// LocationScriptingInterface.h
// interface/src/scripting
//
// Created by Ryan Huffman 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_LocationScriptingInterface_h
#define hifi_LocationScriptingInterface_h
#include <QObject>
#include <QScriptContext>
#include <QScriptEngine>
#include <QScriptValue>
#include <QString>
#include "Application.h"
class LocationScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(QString href READ getHref)
Q_PROPERTY(QString protocol READ getProtocol)
Q_PROPERTY(QString hostname READ getHostname)
Q_PROPERTY(QString pathname READ getPathname)
LocationScriptingInterface() { };
public:
static LocationScriptingInterface* getInstance();
QString getHref();
QString getProtocol() { return CUSTOM_URL_SCHEME; };
QString getPathname();
QString getHostname();
static QScriptValue locationGetter(QScriptContext* context, QScriptEngine* engine);
static QScriptValue locationSetter(QScriptContext* context, QScriptEngine* engine);
public slots:
void assign(const QString& url);
};
#endif // hifi_LocationScriptingInterface_h

View file

@ -0,0 +1,119 @@
//
// WindowScriptingInterface.cpp
// interface/src/scripting
//
// Created by Ryan Huffman 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 <QDir>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include "Application.h"
#include "Menu.h"
#include "WindowScriptingInterface.h"
WindowScriptingInterface* WindowScriptingInterface::getInstance() {
static WindowScriptingInterface sharedInstance;
return &sharedInstance;
}
QScriptValue WindowScriptingInterface::alert(const QString& message) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message));
return retVal;
}
QScriptValue WindowScriptingInterface::confirm(const QString& message) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showConfirm", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message));
return retVal;
}
QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showPrompt", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, message), Q_ARG(const QString&, defaultText));
return retVal;
}
QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter));
return retVal;
}
/// Display an alert box
/// \param const QString& message message to display
/// \return QScriptValue::UndefinedValue
QScriptValue WindowScriptingInterface::showAlert(const QString& message) {
QMessageBox::warning(Application::getInstance()->getWindow(), "", message);
return QScriptValue::UndefinedValue;
}
/// Display a confirmation box with the options 'Yes' and 'No'
/// \param const QString& message message to display
/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise
QScriptValue WindowScriptingInterface::showConfirm(const QString& message) {
QMessageBox::StandardButton response = QMessageBox::question(Application::getInstance()->getWindow(), "", message);
return QScriptValue(response == QMessageBox::Yes);
}
/// Display a prompt with a text box
/// \param const QString& message message to display
/// \param const QString& defaultText default text in the text box
/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise.
QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const QString& defaultText) {
QInputDialog promptDialog(Application::getInstance()->getWindow());
promptDialog.setWindowTitle("");
promptDialog.setLabelText(message);
promptDialog.setTextValue(defaultText);
if (promptDialog.exec() == QDialog::Accepted) {
return QScriptValue(promptDialog.textValue());
}
return QScriptValue::NullValue;
}
/// Display a file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.
QString path = "";
QFileInfo fileInfo = QFileInfo(directory);
if (fileInfo.isDir()) {
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
path = fileInfo.filePath();
}
QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter);
fileDialog.setFileMode(QFileDialog::ExistingFile);
if (fileDialog.exec()) {
return QScriptValue(fileDialog.selectedFiles().first());
}
return QScriptValue::NullValue;
}
int WindowScriptingInterface::getInnerWidth() {
return Application::getInstance()->getWindow()->geometry().width();
}
int WindowScriptingInterface::getInnerHeight() {
return Application::getInstance()->getWindow()->geometry().height();
}

View file

@ -0,0 +1,42 @@
//
// WindowScriptingInterface.cpp
// interface/src/scripting
//
// Created by Ryan Huffman 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_WindowScriptingInterface_h
#define hifi_WindowScriptingInterface_h
#include <QObject>
#include <QScriptValue>
#include <QString>
class WindowScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(int innerWidth READ getInnerWidth)
Q_PROPERTY(int innerHeight READ getInnerHeight)
WindowScriptingInterface() { };
public:
static WindowScriptingInterface* getInstance();
int getInnerWidth();
int getInnerHeight();
public slots:
QScriptValue alert(const QString& message = "");
QScriptValue confirm(const QString& message = "");
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
private slots:
QScriptValue showAlert(const QString& message);
QScriptValue showConfirm(const QString& message);
QScriptValue showPrompt(const QString& message, const QString& defaultText);
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter);
};
#endif // hifi_WindowScriptingInterface_h

View file

@ -36,6 +36,7 @@ LoginDialog::LoginDialog(QWidget* parent) :
_ui->errorLabel->setVisible(false);
setModal(true);
setWindowModality(Qt::WindowModal);
setHideOnBlur(false);
connect(&AccountManager::getInstance(), &AccountManager::loginComplete,

View file

@ -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];

View file

@ -0,0 +1,108 @@
//
// ScriptEditBox.cpp
// interface/src/ui
//
// Created by Thijs Wenker on 4/30/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 "ScriptEditBox.h"
#include "ScriptLineNumberArea.h"
#include "Application.h"
ScriptEditBox::ScriptEditBox(QWidget* parent) :
QPlainTextEdit(parent)
{
_scriptLineNumberArea = new ScriptLineNumberArea(this);
connect(this, &ScriptEditBox::blockCountChanged, this, &ScriptEditBox::updateLineNumberAreaWidth);
connect(this, &ScriptEditBox::updateRequest, this, &ScriptEditBox::updateLineNumberArea);
connect(this, &ScriptEditBox::cursorPositionChanged, this, &ScriptEditBox::highlightCurrentLine);
updateLineNumberAreaWidth(0);
highlightCurrentLine();
}
int ScriptEditBox::lineNumberAreaWidth() {
int digits = 1;
const int SPACER_PIXELS = 3;
const int BASE_TEN = 10;
int max = qMax(1, blockCount());
while (max >= BASE_TEN) {
max /= BASE_TEN;
digits++;
}
return SPACER_PIXELS + fontMetrics().width(QLatin1Char('H')) * digits;
}
void ScriptEditBox::updateLineNumberAreaWidth(int blockCount) {
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
void ScriptEditBox::updateLineNumberArea(const QRect& rect, int deltaY) {
if (deltaY) {
_scriptLineNumberArea->scroll(0, deltaY);
} else {
_scriptLineNumberArea->update(0, rect.y(), _scriptLineNumberArea->width(), rect.height());
}
if (rect.contains(viewport()->rect())) {
updateLineNumberAreaWidth(0);
}
}
void ScriptEditBox::resizeEvent(QResizeEvent* event) {
QPlainTextEdit::resizeEvent(event);
QRect localContentsRect = contentsRect();
_scriptLineNumberArea->setGeometry(QRect(localContentsRect.left(), localContentsRect.top(), lineNumberAreaWidth(),
localContentsRect.height()));
}
void ScriptEditBox::highlightCurrentLine() {
QList<QTextEdit::ExtraSelection> extraSelections;
if (!isReadOnly()) {
QTextEdit::ExtraSelection selection;
QColor lineColor = QColor(Qt::gray).lighter();
selection.format.setBackground(lineColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
}
setExtraSelections(extraSelections);
}
void ScriptEditBox::lineNumberAreaPaintEvent(QPaintEvent* event)
{
QPainter painter(_scriptLineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
QTextBlock block = firstVisibleBlock();
int blockNumber = block.blockNumber();
int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
int bottom = top + (int) blockBoundingRect(block).height();
while (block.isValid() && top <= event->rect().bottom()) {
if (block.isVisible() && bottom >= event->rect().top()) {
QFont font = painter.font();
font.setBold(this->textCursor().blockNumber() == block.blockNumber());
painter.setFont(font);
QString number = QString::number(blockNumber + 1);
painter.setPen(Qt::black);
painter.drawText(0, top, _scriptLineNumberArea->width(), fontMetrics().height(),
Qt::AlignRight, number);
}
block = block.next();
top = bottom;
bottom = top + (int) blockBoundingRect(block).height();
blockNumber++;
}
}

View file

@ -0,0 +1,38 @@
//
// ScriptEditBox.h
// interface/src/ui
//
// Created by Thijs Wenker on 4/30/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_ScriptEditBox_h
#define hifi_ScriptEditBox_h
#include <QPlainTextEdit>
class ScriptEditBox : public QPlainTextEdit {
Q_OBJECT
public:
ScriptEditBox(QWidget* parent = NULL);
void lineNumberAreaPaintEvent(QPaintEvent* event);
int lineNumberAreaWidth();
protected:
void resizeEvent(QResizeEvent* event);
private slots:
void updateLineNumberAreaWidth(int blockCount);
void highlightCurrentLine();
void updateLineNumberArea(const QRect& rect, int deltaY);
private:
QWidget* _scriptLineNumberArea;
};
#endif // hifi_ScriptEditBox_h

View file

@ -32,8 +32,10 @@ ScriptEditorWidget::ScriptEditorWidget() :
{
_scriptEditorWidgetUI->setupUi(this);
connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(modificationChanged(bool)), this, SIGNAL(scriptModified()));
connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(contentsChanged()), this, SLOT(onScriptModified()));
connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::modificationChanged, this,
&ScriptEditorWidget::scriptModified);
connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::contentsChanged, this,
&ScriptEditorWidget::onScriptModified);
// remove the title bar (see the Qt docs on setTitleBarWidget)
setTitleBarWidget(new QWidget());
@ -68,16 +70,19 @@ bool ScriptEditorWidget::setRunning(bool run) {
return false;
}
// Clean-up old connections.
disconnect(this, SLOT(onScriptError(const QString&)));
disconnect(this, SLOT(onScriptPrint(const QString&)));
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
}
if (run) {
_scriptEngine = Application::getInstance()->loadScript(_currentScript, true);
connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged()));
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
// Make new connections.
connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&)));
connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&)));
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
} else {
Application::getInstance()->stopScript(_currentScript);
_scriptEngine = NULL;
@ -101,25 +106,44 @@ bool ScriptEditorWidget::saveFile(const QString &scriptPath) {
}
void ScriptEditorWidget::loadFile(const QString& scriptPath) {
QFile file(scriptPath);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath).arg(file.errorString()));
return;
QUrl url(scriptPath);
// if the scheme length is one or lower, maybe they typed in a file, let's try
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
QFile file(scriptPath);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath)
.arg(file.errorString()));
return;
}
QTextStream in(&file);
_scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll());
setScriptFile(scriptPath);
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
}
} else {
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
QNetworkReply* reply = networkManager->get(QNetworkRequest(url));
qDebug() << "Downloading included script at" << scriptPath;
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
_scriptEditorWidgetUI->scriptEdit->setPlainText(reply->readAll());
if (!saveAs()) {
static_cast<ScriptEditorWindow*>(this->parent()->parent()->parent())->terminateCurrentTab();
}
}
QTextStream in(&file);
_scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll());
setScriptFile(scriptPath);
disconnect(this, SLOT(onScriptError(const QString&)));
disconnect(this, SLOT(onScriptPrint(const QString&)));
_scriptEngine = Application::getInstance()->getScriptEngine(scriptPath);
_scriptEngine = Application::getInstance()->getScriptEngine(_currentScript);
if (_scriptEngine != NULL) {
connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged()));
connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&)));
connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&)));
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
}
}
@ -128,8 +152,15 @@ bool ScriptEditorWidget::save() {
}
bool ScriptEditorWidget::saveAs() {
QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), QString(), tr("Javascript (*.js)"));
return !fileName.isEmpty() ? saveFile(fileName) : false;
QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"),
Application::getInstance()->getPreviousScriptLocation(),
tr("JavaScript Files (*.js)"));
if (!fileName.isEmpty()) {
Application::getInstance()->setPreviousScriptLocation(fileName);
return saveFile(fileName);
} else {
return false;
}
}
void ScriptEditorWidget::setScriptFile(const QString& scriptPath) {

View file

@ -13,7 +13,6 @@
#define hifi_ScriptEditorWidget_h
#include <QDockWidget>
#include "ScriptEditorWidget.h"
#include "ScriptEngine.h"
namespace Ui {

View file

@ -34,19 +34,20 @@ ScriptEditorWindow::ScriptEditorWindow() :
_saveMenu(new QMenu)
{
_ScriptEditorWindowUI->setupUi(this);
this->setWindowFlags(Qt::Tool);
show();
addScriptEditorWidget("New script");
connect(_loadMenu, SIGNAL(aboutToShow()), this, SLOT(loadMenuAboutToShow()));
connect(_loadMenu, &QMenu::aboutToShow, this, &ScriptEditorWindow::loadMenuAboutToShow);
_ScriptEditorWindowUI->loadButton->setMenu(_loadMenu);
_saveMenu->addAction("Save as..", this, SLOT(saveScriptAsClicked()), Qt::CTRL | Qt::SHIFT | Qt::Key_S);
_ScriptEditorWindowUI->saveButton->setMenu(_saveMenu);
connect(new QShortcut(QKeySequence("Ctrl+N"), this), SIGNAL(activated()), this, SLOT(newScriptClicked()));
connect(new QShortcut(QKeySequence("Ctrl+S"), this), SIGNAL(activated()), this, SLOT(saveScriptClicked()));
connect(new QShortcut(QKeySequence("Ctrl+O"), this), SIGNAL(activated()), this, SLOT(loadScriptClicked()));
connect(new QShortcut(QKeySequence("F5"), this), SIGNAL(activated()), this, SLOT(toggleRunScriptClicked()));
connect(new QShortcut(QKeySequence("Ctrl+N"), this), &QShortcut::activated, this, &ScriptEditorWindow::newScriptClicked);
connect(new QShortcut(QKeySequence("Ctrl+S"), this), &QShortcut::activated, this,&ScriptEditorWindow::saveScriptClicked);
connect(new QShortcut(QKeySequence("Ctrl+O"), this), &QShortcut::activated, this, &ScriptEditorWindow::loadScriptClicked);
connect(new QShortcut(QKeySequence("F5"), this), &QShortcut::activated, this, &ScriptEditorWindow::toggleRunScriptClicked);
}
ScriptEditorWindow::~ScriptEditorWindow() {
@ -73,8 +74,11 @@ void ScriptEditorWindow::loadScriptMenu(const QString& scriptName) {
}
void ScriptEditorWindow::loadScriptClicked() {
QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), QString(), tr("Javascript (*.js)"));
QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"),
Application::getInstance()->getPreviousScriptLocation(),
tr("JavaScript Files (*.js)"));
if (!scriptName.isEmpty()) {
Application::getInstance()->setPreviousScriptLocation(scriptName);
addScriptEditorWidget("loading...")->loadFile(scriptName);
updateButtons();
}
@ -126,9 +130,9 @@ void ScriptEditorWindow::saveScriptAsClicked() {
ScriptEditorWidget* ScriptEditorWindow::addScriptEditorWidget(QString title) {
ScriptEditorWidget* newScriptEditorWidget = new ScriptEditorWidget();
connect(newScriptEditorWidget, SIGNAL(scriptnameChanged()), this, SLOT(updateScriptNameOrStatus()));
connect(newScriptEditorWidget, SIGNAL(scriptModified()), this, SLOT(updateScriptNameOrStatus()));
connect(newScriptEditorWidget, SIGNAL(runningStateChanged()), this, SLOT(updateButtons()));
connect(newScriptEditorWidget, &ScriptEditorWidget::scriptnameChanged, this, &ScriptEditorWindow::updateScriptNameOrStatus);
connect(newScriptEditorWidget, &ScriptEditorWidget::scriptModified, this, &ScriptEditorWindow::updateScriptNameOrStatus);
connect(newScriptEditorWidget, &ScriptEditorWidget::runningStateChanged, this, &ScriptEditorWindow::updateButtons);
_ScriptEditorWindowUI->tabWidget->addTab(newScriptEditorWidget, title);
_ScriptEditorWindowUI->tabWidget->setCurrentWidget(newScriptEditorWidget);
newScriptEditorWidget->setUpdatesEnabled(true);
@ -198,3 +202,10 @@ void ScriptEditorWindow::updateScriptNameOrStatus() {
}
}
}
void ScriptEditorWindow::terminateCurrentTab() {
if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) {
_ScriptEditorWindowUI->tabWidget->removeTab(_ScriptEditorWindowUI->tabWidget->currentIndex());
this->raise();
}
}

View file

@ -25,6 +25,8 @@ public:
ScriptEditorWindow();
~ScriptEditorWindow();
void terminateCurrentTab();
protected:
void closeEvent(QCloseEvent* event);

View file

@ -0,0 +1,28 @@
//
// ScriptLineNumberArea.cpp
// interface/src/ui
//
// Created by Thijs Wenker on 4/30/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 "ScriptLineNumberArea.h"
#include "Application.h"
ScriptLineNumberArea::ScriptLineNumberArea(ScriptEditBox* scriptEditBox) :
QWidget(scriptEditBox)
{
_scriptEditBox = scriptEditBox;
}
QSize ScriptLineNumberArea::sizeHint() {
return QSize(_scriptEditBox->lineNumberAreaWidth(), 0);
}
void ScriptLineNumberArea::paintEvent(QPaintEvent* event) {
_scriptEditBox->lineNumberAreaPaintEvent(event);
}

View file

@ -0,0 +1,31 @@
//
// ScriptLineNumberArea.h
// interface/src/ui
//
// Created by Thijs Wenker on 4/30/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_ScriptLineNumberArea_h
#define hifi_ScriptLineNumberArea_h
#include <QWidget>
#include "ScriptEditBox.h"
class ScriptLineNumberArea : public QWidget {
public:
ScriptLineNumberArea(ScriptEditBox* scriptEditBox);
QSize sizeHint();
protected:
void paintEvent(QPaintEvent* event);
private:
ScriptEditBox* _scriptEditBox;
};
#endif // hifi_ScriptLineNumberArea_h

View file

@ -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++;

View file

@ -18,8 +18,8 @@
</property>
<property name="minimumSize">
<size>
<width>541</width>
<height>238</height>
<width>690</width>
<height>328</height>
</size>
</property>
<property name="styleSheet">
@ -35,7 +35,7 @@
<string>Edit Script</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout" stretch="5,0,1">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
<property name="spacing">
<number>0</number>
</property>
@ -52,18 +52,18 @@
<number>0</number>
</property>
<item>
<widget class="QTextEdit" name="scriptEdit">
<widget class="ScriptEditBox" name="scriptEdit">
<property name="font">
<font>
<family>Courier</family>
<pointsize>9</pointsize>
<pointsize>-1</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">font: 9pt &quot;Courier&quot;;</string>
<string notr="true">font: 16px &quot;Courier&quot;;</string>
</property>
</widget>
</item>
@ -86,6 +86,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font: 13px &quot;Helvetica&quot;,&quot;Arial&quot;,&quot;sans-serif&quot;;</string>
</property>
<property name="text">
<string>Debug Log:</string>
</property>
@ -93,8 +96,20 @@
</item>
<item>
<widget class="QCheckBox" name="onTheFlyCheckBox">
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>-1</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">font: 13px &quot;Helvetica&quot;,&quot;Arial&quot;,&quot;sans-serif&quot;;</string>
</property>
<property name="text">
<string>Run on the fly (Careful: Any valid change made to the code will run immediately)</string>
<string>Run on the fly (Careful: Any valid change made to the code will run immediately) </string>
</property>
</widget>
</item>
@ -115,17 +130,33 @@
</item>
<item>
<widget class="QPlainTextEdit" name="debugText">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font: 8pt &quot;Courier&quot;;</string>
<string notr="true">font: 15px &quot;Courier&quot;;</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>ScriptEditBox</class>
<extends>QTextEdit</extends>
<header>ui/ScriptEditBox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>

View file

@ -3,14 +3,14 @@
<class>ScriptEditorWindow</class>
<widget class="QWidget" name="ScriptEditorWindow">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>706</width>
<height>682</height>
<width>780</width>
<height>717</height>
</rect>
</property>
<property name="minimumSize">
@ -29,16 +29,7 @@
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>0</number>
</property>
<item>

View 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}")

View 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);
}
}

View 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

View 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();
}

View 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

View file

@ -0,0 +1,636 @@
//
// ModelTree.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ModelTree.h"
ModelTree::ModelTree(bool shouldReaverage) : Octree(shouldReaverage) {
_rootNode = createNewElement();
}
ModelTreeElement* ModelTree::createNewElement(unsigned char * octalCode) {
ModelTreeElement* newElement = new ModelTreeElement(octalCode);
newElement->setTree(this);
return newElement;
}
bool ModelTree::handlesEditPacketType(PacketType packetType) const {
// we handle these types of "edit" packets
switch (packetType) {
case PacketTypeModelAddOrEdit:
case PacketTypeModelErase:
return true;
default:
return false;
}
}
class FindAndDeleteModelsArgs {
public:
QList<uint32_t> _idsToDelete;
};
bool ModelTree::findAndDeleteOperation(OctreeElement* element, void* extraData) {
//qDebug() << "findAndDeleteOperation()";
FindAndDeleteModelsArgs* args = static_cast< FindAndDeleteModelsArgs*>(extraData);
// if we've found and deleted all our target models, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
//qDebug() << "findAndDeleteOperation() args->_idsToDelete.size():" << args->_idsToDelete.size();
for (QList<uint32_t>::iterator it = args->_idsToDelete.begin(); it != args->_idsToDelete.end(); it++) {
uint32_t modelID = *it;
//qDebug() << "findAndDeleteOperation() modelID:" << modelID;
if (modelTreeElement->removeModelWithID(modelID)) {
// if the model was in this element, then remove it from our search list.
//qDebug() << "findAndDeleteOperation() it = args->_idsToDelete.erase(it)";
it = args->_idsToDelete.erase(it);
}
if (it == args->_idsToDelete.end()) {
//qDebug() << "findAndDeleteOperation() breaking";
break;
}
}
// if we've found and deleted all our target models, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
return true;
}
class FindAndUpdateModelArgs {
public:
const ModelItem& searchModel;
bool found;
};
bool ModelTree::findAndUpdateOperation(OctreeElement* element, void* extraData) {
FindAndUpdateModelArgs* args = static_cast<FindAndUpdateModelArgs*>(extraData);
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
// Note: updateModel() will only operate on correctly found models
if (modelTreeElement->updateModel(args->searchModel)) {
args->found = true;
return false; // stop searching
}
return true;
}
void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) {
// First, look for the existing model in the tree..
FindAndUpdateModelArgs args = { model, false };
recurseTreeWithOperation(findAndUpdateOperation, &args);
// if we didn't find it in the tree, then store it...
if (!args.found) {
glm::vec3 position = model.getPosition();
float size = std::max(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);
}
}

View file

@ -0,0 +1,99 @@
//
// ModelTree.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelTree_h
#define hifi_ModelTree_h
#include <Octree.h>
#include "ModelTreeElement.h"
class NewlyCreatedModelHook {
public:
virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) = 0;
};
class ModelTree : public Octree {
Q_OBJECT
public:
ModelTree(bool shouldReaverage = false);
/// Implements our type specific root element factory
virtual ModelTreeElement* createNewElement(unsigned char * octalCode = NULL);
/// Type safe version of getRoot()
ModelTreeElement* getRoot() { return (ModelTreeElement*)_rootNode; }
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return true; }
virtual PacketType expectedDataPacketType() const { return PacketTypeModelData; }
virtual bool handlesEditPacketType(PacketType packetType) const;
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
virtual void update();
void storeModel(const ModelItem& model, const SharedNodePointer& senderNode = SharedNodePointer());
void updateModel(const ModelItemID& modelID, const ModelItemProperties& properties);
void addModel(const ModelItemID& modelID, const ModelItemProperties& properties);
void deleteModel(const ModelItemID& modelID);
const ModelItem* findClosestModel(glm::vec3 position, float targetRadius);
const ModelItem* findModelByID(uint32_t id, bool alreadyLocked = false);
/// finds all models that touch a sphere
/// \param center the center of the sphere
/// \param radius the radius of the sphere
/// \param foundModels[out] vector of const ModelItem*
/// \remark Side effect: any initial contents in foundModels will be lost
void findModels(const glm::vec3& center, float radius, QVector<const ModelItem*>& foundModels);
/// finds all models that touch a box
/// \param box the query box
/// \param foundModels[out] vector of non-const ModelItem*
/// \remark Side effect: any initial contents in models will be lost
void findModelsForUpdate(const AABox& box, QVector<ModelItem*> foundModels);
void addNewlyCreatedHook(NewlyCreatedModelHook* hook);
void removeNewlyCreatedHook(NewlyCreatedModelHook* hook);
bool hasAnyDeletedModels() const { return _recentlyDeletedModelItemIDs.size() > 0; }
bool hasModelsDeletedSince(quint64 sinceTime);
bool encodeModelsDeletedSince(quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength);
void forgetModelsDeletedBefore(quint64 sinceTime);
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
void handleAddModelResponse(const QByteArray& packet);
private:
static bool updateOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData);
static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool findInSphereOperation(OctreeElement* element, void* extraData);
static bool pruneOperation(OctreeElement* element, void* extraData);
static bool findByIDOperation(OctreeElement* element, void* extraData);
static bool findAndDeleteOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateModelItemIDOperation(OctreeElement* element, void* extraData);
static bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData);
void notifyNewlyCreatedModel(const ModelItem& newModel, const SharedNodePointer& senderNode);
QReadWriteLock _newlyCreatedHooksLock;
std::vector<NewlyCreatedModelHook*> _newlyCreatedHooks;
QReadWriteLock _recentlyDeletedModelsLock;
QMultiMap<quint64, uint32_t> _recentlyDeletedModelItemIDs;
};
#endif // hifi_ModelTree_h

View file

@ -0,0 +1,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();
}

View file

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

View file

@ -0,0 +1,38 @@
//
// ModelTreeHeadlessViewer.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 2/26/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ModelTreeHeadlessViewer.h"
ModelTreeHeadlessViewer::ModelTreeHeadlessViewer() :
OctreeHeadlessViewer() {
}
ModelTreeHeadlessViewer::~ModelTreeHeadlessViewer() {
}
void ModelTreeHeadlessViewer::init() {
OctreeHeadlessViewer::init();
}
void ModelTreeHeadlessViewer::update() {
if (_tree) {
ModelTree* tree = static_cast<ModelTree*>(_tree);
if (tree->tryLockForWrite()) {
tree->update();
tree->unlock();
}
}
}
void ModelTreeHeadlessViewer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
static_cast<ModelTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
}

View file

@ -0,0 +1,45 @@
//
// ModelTreeHeadlessViewer.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 2/26/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelTreeHeadlessViewer_h
#define hifi_ModelTreeHeadlessViewer_h
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeHeadlessViewer.h>
#include <ViewFrustum.h>
#include "ModelTree.h"
// Generic client side Octree renderer class.
class ModelTreeHeadlessViewer : public OctreeHeadlessViewer {
Q_OBJECT
public:
ModelTreeHeadlessViewer();
virtual ~ModelTreeHeadlessViewer();
virtual Octree* createTree() { return new ModelTree(true); }
virtual NodeType_t getMyNodeType() const { return NodeType::ModelServer; }
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; }
void update();
ModelTree* getTree() { return (ModelTree*)_tree; }
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
};
#endif // hifi_ModelTreeHeadlessViewer_h

View file

@ -0,0 +1,176 @@
//
// ModelsScriptingInterface.cpp
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/6/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ModelsScriptingInterface.h"
#include "ModelTree.h"
ModelsScriptingInterface::ModelsScriptingInterface() :
_nextCreatorTokenID(0),
_modelTree(NULL)
{
}
void ModelsScriptingInterface::queueModelMessage(PacketType packetType,
ModelItemID modelID, const ModelItemProperties& properties) {
getModelPacketSender()->queueModelEditMessage(packetType, modelID, properties);
}
ModelItemID ModelsScriptingInterface::addModel(const ModelItemProperties& properties) {
// The application will keep track of creatorTokenID
uint32_t creatorTokenID = ModelItem::getNextCreatorTokenID();
ModelItemID id(NEW_MODEL, creatorTokenID, false );
// queue the packet
queueModelMessage(PacketTypeModelAddOrEdit, id, properties);
// If we have a local model tree set, then also update it.
if (_modelTree) {
_modelTree->lockForWrite();
_modelTree->addModel(id, properties);
_modelTree->unlock();
}
return id;
}
ModelItemID ModelsScriptingInterface::identifyModel(ModelItemID modelID) {
uint32_t actualID = modelID.id;
if (!modelID.isKnownID) {
actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID);
if (actualID == UNKNOWN_MODEL_ID) {
return modelID; // bailing early
}
// found it!
modelID.id = actualID;
modelID.isKnownID = true;
}
return modelID;
}
ModelItemProperties ModelsScriptingInterface::getModelProperties(ModelItemID modelID) {
ModelItemProperties results;
ModelItemID identity = identifyModel(modelID);
if (!identity.isKnownID) {
results.setIsUnknownID();
return results;
}
if (_modelTree) {
_modelTree->lockForRead();
const ModelItem* model = _modelTree->findModelByID(identity.id, true);
if (model) {
results.copyFromModelItem(*model);
} else {
results.setIsUnknownID();
}
_modelTree->unlock();
}
return results;
}
ModelItemID ModelsScriptingInterface::editModel(ModelItemID modelID, const ModelItemProperties& properties) {
uint32_t actualID = modelID.id;
// if the model is unknown, attempt to look it up
if (!modelID.isKnownID) {
actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID);
}
// if at this point, we know the id, send the update to the model server
if (actualID != UNKNOWN_MODEL_ID) {
modelID.id = actualID;
modelID.isKnownID = true;
queueModelMessage(PacketTypeModelAddOrEdit, modelID, properties);
}
// If we have a local model tree set, then also update it. We can do this even if we don't know
// the actual id, because we can edit out local models just with creatorTokenID
if (_modelTree) {
_modelTree->lockForWrite();
_modelTree->updateModel(modelID, properties);
_modelTree->unlock();
}
return modelID;
}
// TODO: This deleteModel() method uses the PacketType_MODEL_ADD_OR_EDIT message to send
// a changed model with a shouldDie() property set to true. This works and is currently the only
// way to tell the model server to delete a model. But we should change this to use the PacketType_MODEL_ERASE
// message which takes a list of model id's to delete.
void ModelsScriptingInterface::deleteModel(ModelItemID modelID) {
// setup properties to kill the model
ModelItemProperties properties;
properties.setShouldDie(true);
uint32_t actualID = modelID.id;
// if the model is unknown, attempt to look it up
if (!modelID.isKnownID) {
actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID);
}
// if at this point, we know the id, send the update to the model server
if (actualID != UNKNOWN_MODEL_ID) {
modelID.id = actualID;
modelID.isKnownID = true;
queueModelMessage(PacketTypeModelAddOrEdit, modelID, properties);
}
// If we have a local model tree set, then also update it.
if (_modelTree) {
_modelTree->lockForWrite();
_modelTree->deleteModel(modelID);
_modelTree->unlock();
}
}
ModelItemID ModelsScriptingInterface::findClosestModel(const glm::vec3& center, float radius) const {
ModelItemID result(UNKNOWN_MODEL_ID, UNKNOWN_MODEL_TOKEN, false);
if (_modelTree) {
_modelTree->lockForRead();
const ModelItem* closestModel = _modelTree->findClosestModel(center/(float)TREE_SCALE,
radius/(float)TREE_SCALE);
_modelTree->unlock();
if (closestModel) {
result.id = closestModel->getID();
result.isKnownID = true;
}
}
return result;
}
QVector<ModelItemID> ModelsScriptingInterface::findModels(const glm::vec3& center, float radius) const {
QVector<ModelItemID> result;
if (_modelTree) {
_modelTree->lockForRead();
QVector<const ModelItem*> models;
_modelTree->findModels(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, models);
_modelTree->unlock();
foreach (const ModelItem* model, models) {
ModelItemID thisModelItemID(model->getID(), UNKNOWN_MODEL_TOKEN, true);
result << thisModelItemID;
}
}
return result;
}

View file

@ -0,0 +1,73 @@
//
// ModelsScriptingInterface.h
// libraries/models/src
//
// Created by Brad Hefta-Gaub on 12/6/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelsScriptingInterface_h
#define hifi_ModelsScriptingInterface_h
#include <QtCore/QObject>
#include <CollisionInfo.h>
#include <OctreeScriptingInterface.h>
#include "ModelEditPacketSender.h"
/// handles scripting of Model commands from JS passed to assigned clients
class ModelsScriptingInterface : public OctreeScriptingInterface {
Q_OBJECT
public:
ModelsScriptingInterface();
ModelEditPacketSender* getModelPacketSender() const { return (ModelEditPacketSender*)getPacketSender(); }
virtual NodeType_t getServerNodeType() const { return NodeType::ModelServer; }
virtual OctreeEditPacketSender* createPacketSender() { return new ModelEditPacketSender(); }
void setModelTree(ModelTree* modelTree) { _modelTree = modelTree; }
ModelTree* getModelTree(ModelTree*) { return _modelTree; }
public slots:
/// adds a model with the specific properties
ModelItemID addModel(const ModelItemProperties& properties);
/// identify a recently created model to determine its true ID
ModelItemID identifyModel(ModelItemID modelID);
/// gets the current model properties for a specific model
/// this function will not find return results in script engine contexts which don't have access to models
ModelItemProperties getModelProperties(ModelItemID modelID);
/// edits a model updating only the included properties, will return the identified ModelItemID in case of
/// successful edit, if the input modelID is for an unknown model this function will have no effect
ModelItemID editModel(ModelItemID modelID, const ModelItemProperties& properties);
/// deletes a model
void deleteModel(ModelItemID modelID);
/// finds the closest model to the center point, within the radius
/// will return a ModelItemID.isKnownID = false if no models are in the radius
/// this function will not find any models in script engine contexts which don't have access to models
ModelItemID findClosestModel(const glm::vec3& center, float radius) const;
/// finds models within the search sphere specified by the center point and radius
/// this function will not find any models in script engine contexts which don't have access to models
QVector<ModelItemID> findModels(const glm::vec3& center, float radius) const;
signals:
void modelCollisionWithVoxel(const ModelItemID& modelID, const VoxelDetail& voxel, const CollisionInfo& collision);
void modelCollisionWithModel(const ModelItemID& idA, const ModelItemID& idB, const CollisionInfo& collision);
private:
void queueModelMessage(PacketType packetType, ModelItemID modelID, const ModelItemProperties& properties);
uint32_t _nextCreatorTokenID;
ModelTree* _modelTree;
};
#endif // hifi_ModelsScriptingInterface_h

View file

@ -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:

View file

@ -32,6 +32,7 @@ public:
VoxelServerType,
ParticleServerType,
MetavoxelServerType,
ModelServerType,
AllTypes
};

View file

@ -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");

View file

@ -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';

View file

@ -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;

View file

@ -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)) {

View file

@ -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);

View file

@ -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)

View file

@ -16,18 +16,19 @@
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <QScriptEngine>
#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"
@ -36,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());
@ -203,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);
@ -223,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);
@ -247,10 +255,26 @@ void ScriptEngine::init() {
_particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS);
}
void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
if (object) {
QScriptValue value = _engine.newQObject(object);
_engine.globalObject().setProperty(name, value);
return value;
}
return QScriptValue::NullValue;
}
void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, QScriptValue object) {
QScriptValue setterFunction = _engine.newFunction(setter, 1);
QScriptValue getterFunction = _engine.newFunction(getter);
if (!object.isNull()) {
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
} else {
_engine.globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
_engine.globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);
}
}

View file

@ -30,7 +30,9 @@
#include "ScriptUUID.h"
#include "Vec3.h"
class ModelsScriptingInterface;
class ParticlesScriptingInterface;
class VoxelsScriptingInterface;
const QString NO_SCRIPT("");
@ -52,13 +54,18 @@ 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(""));
const QString& getScriptName() const { return _scriptName; }
void cleanupMenuItems();
void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue);
Q_INVOKABLE void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
@ -126,6 +133,7 @@ private:
static VoxelsScriptingInterface _voxelsScriptingInterface;
static ParticlesScriptingInterface _particlesScriptingInterface;
static ModelsScriptingInterface _modelsScriptingInterface;
AbstractControllerScriptingInterface* _controllerScriptingInterface;
AudioScriptingInterface _audioScriptingInterface;
@ -136,6 +144,7 @@ private:
Vec3 _vec3Library;
ScriptUUID _uuidLibrary;
AnimationCache _animationCache;
};
#endif // hifi_ScriptEngine_h