diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4f81c42046..db200c415c 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -43,6 +43,12 @@ void Agent::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& dataByteArray.size()); break; } + } else if (dataByteArray[0] == PACKET_TYPE_PARTICLE_ADD_RESPONSE) { + // this will keep creatorTokenIDs to IDs mapped correctly + Particle::handleAddParticleResponse((unsigned char*) dataByteArray.data(), dataByteArray.size()); + + // also give our local particle tree a chance to remap any internal locally created particles + _particleTree.handleAddParticleResponse((unsigned char*) dataByteArray.data(), dataByteArray.size()); } else { NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size()); } @@ -88,6 +94,9 @@ void Agent::run() { connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); + // tell our script engine about our local particle tree + _scriptEngine.getParticlesScriptingInterface()->setParticleTree(&_particleTree); + // setup an Avatar for the script to use AvatarData scriptedAvatar; diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 980885fcf3..6ab2924ada 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -32,6 +33,7 @@ signals: void willSendVisualDataCallback(); private: ScriptEngine _scriptEngine; + ParticleTree _particleTree; }; #endif /* defined(__hifi__Agent__) */ diff --git a/examples/controllerExample.js b/examples/controllerExample.js new file mode 100644 index 0000000000..c1b33b24a5 --- /dev/null +++ b/examples/controllerExample.js @@ -0,0 +1,105 @@ +// +// controllerExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 1/28/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Controller class +// +// + +// initialize our triggers +var triggerPulled = new Array(); +var numberOfTriggers = Controller.getNumberOfTriggers(); +for (t = 0; t < numberOfTriggers; t++) { + triggerPulled[t] = false; +} + +function checkController() { + var numberOfTriggers = Controller.getNumberOfTriggers(); + var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); + var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + + // this is expected for hydras + if (numberOfTriggers == 2 && controllersPerTrigger == 2) { + for (var t = 0; t < numberOfTriggers; t++) { + var triggerValue = Controller.getTriggerValue(t); + + if (triggerPulled[t]) { + // must release to at least 0.1 + if (triggerValue < 0.1) { + triggerPulled[t] = false; // unpulled + } + } else { + // must pull to at least 0.9 + if (triggerValue > 0.9) { + triggerPulled[t] = true; // pulled + triggerToggled = true; + } + } + + if (triggerToggled) { + print("a trigger was toggled"); + } + } + } +} + +function keyPressEvent(event) { + print("keyPressEvent event.key=" + event.key); + if (event.key == "A".charCodeAt(0)) { + print("the A key was pressed"); + } + if (event.key == " ".charCodeAt(0)) { + print("the key was pressed"); + } +} + +function mouseMoveEvent(event) { + print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y); +} + +function touchBeginEvent(event) { + print("touchBeginEvent event.x,y=" + event.x + ", " + event.y); +} + +function touchUpdateEvent(event) { + print("touchUpdateEvent event.x,y=" + event.x + ", " + event.y); +} + +function touchEndEvent(event) { + print("touchEndEvent event.x,y=" + event.x + ", " + event.y); +} + +// register the call back so it fires before each data send +Agent.willSendVisualDataCallback.connect(checkController); + +// Map keyPress and mouse move events to our callbacks +Controller.keyPressEvent.connect(keyPressEvent); +var AKeyEvent = { + key: "A".charCodeAt(0), + isShifted: false, + isMeta: false +}; + +// prevent the A key from going through to the application +Controller.captureKeyEvents(AKeyEvent); + + +Controller.mouseMoveEvent.connect(mouseMoveEvent); + +// Map touch events to our callbacks +Controller.touchBeginEvent.connect(touchBeginEvent); +Controller.touchUpdateEvent.connect(touchUpdateEvent); +Controller.touchEndEvent.connect(touchEndEvent); + +// disable the standard application for touch events +Controller.captureTouchEvents(); + +function scriptEnding() { + // re-enabled the standard application for touch events + Controller.releaseTouchEvents(); +} + +Agent.scriptEnding.connect(scriptEnding); diff --git a/examples/editParticleExample.js b/examples/editParticleExample.js index 4eb5dfe907..61e32c4d55 100644 --- a/examples/editParticleExample.js +++ b/examples/editParticleExample.js @@ -9,6 +9,9 @@ // var count = 0; +var moveUntil = 2000; +var stopAfter = moveUntil + 100; +var expectedLifetime = (moveUntil/60) + 1; // 1 second after done moving... var originalProperties = { position: { x: 10, @@ -28,7 +31,9 @@ var originalProperties = { color: { red: 0, green: 255, - blue: 0 } + blue: 0 }, + + lifetime: expectedLifetime }; @@ -38,17 +43,17 @@ var positionDelta = { x: 0.05, y: 0, z: 0 }; var particleID = Particles.addParticle(originalProperties); function moveParticle() { - if (count >= 100) { + if (count >= moveUntil) { //Agent.stop(); // delete it... - if (count == 100) { + if (count == moveUntil) { print("calling Particles.deleteParticle()"); Particles.deleteParticle(particleID); } // stop it... - if (count >= 200) { + if (count >= stopAfter) { print("calling Agent.stop()"); Agent.stop(); } @@ -77,16 +82,6 @@ function moveParticle() { print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z); Particles.editParticle(particleID, newProperties); - - // also check to see if we can "find" particles... - var searchAt = { x: 9, y: 0, z: 0}; - var searchRadius = 2; - var foundParticle = Particles.findClosestParticle(searchAt, searchRadius); - if (foundParticle.isKnownID) { - print("found particle:" + foundParticle.id); - } else { - print("could not find particle in or around x=9 to x=11:"); - } } diff --git a/examples/lookWithMouse.js b/examples/lookWithMouse.js new file mode 100644 index 0000000000..79fad76a1b --- /dev/null +++ b/examples/lookWithMouse.js @@ -0,0 +1,74 @@ +// +// lookWithMouse.js +// hifi +// +// Created by Brad Hefta-Gaub on 1/28/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Controller class +// +// + +var isMouseDown = false; +var lastX = 0; +var lastY = 0; +var yawFromMouse = 0; +var pitchFromMouse = 0; + +function mousePressEvent(event) { + print("mousePressEvent event.x,y=" + event.x + ", " + event.y); + isMouseDown = true; + lastX = event.x; + lastY = event.y; +} + +function mouseReleaseEvent(event) { + print("mouseReleaseEvent event.x,y=" + event.x + ", " + event.y); + isMouseDown = false; +} + +function mouseMoveEvent(event) { + print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y); + + if (isMouseDown) { + print("isMouseDown... attempting to change pitch..."); + var MOUSE_YAW_SCALE = -0.25; + var MOUSE_PITCH_SCALE = -12.5; + var FIXED_MOUSE_TIMESTEP = 0.016; + yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP); + pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP); + lastX = event.x; + lastY = event.y; + } +} + +function update() { + // rotate body yaw for yaw received from mouse + MyAvatar.orientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } )); + yawFromMouse = 0; + + // apply pitch from mouse + MyAvatar.headPitch = MyAvatar.headPitch + pitchFromMouse; + pitchFromMouse = 0; +} + +// Map the mouse events to our functions +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + +// disable the standard application for mouse events +Controller.captureMouseEvents(); + +function scriptEnding() { + // re-enabled the standard application for mouse events + Controller.releaseMouseEvents(); +} + +MyAvatar.bodyYaw = 0; +MyAvatar.bodyPitch = 0; +MyAvatar.bodyRoll = 0; + +// would be nice to change to update +Agent.willSendVisualDataCallback.connect(update); +Agent.scriptEnding.connect(scriptEnding); diff --git a/examples/particleModelExample.js b/examples/particleModelExample.js new file mode 100644 index 0000000000..9f19069ee9 --- /dev/null +++ b/examples/particleModelExample.js @@ -0,0 +1,51 @@ +// +// particleModelExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 1/28/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates creating and editing a particle +// + +var count = 0; +var stopAfter = 100; + +var modelProperties = { + position: { x: 1, y: 1, z: 1 }, + velocity: { x: 0.5, y: 0, z: 0.5 }, + gravity: { x: 0, y: 0, z: 0 }, + damping: 0, + radius : 0.25, + modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", + lifetime: 20 +}; + +var ballProperties = { + position: { x: 1, y: 0.5, z: 1 }, + velocity: { x: 0.5, y: 0, z: 0.5 }, + gravity: { x: 0, y: 0, z: 0 }, + damping: 0, + radius : 0.25, + color: { red: 255, green: 0, blue: 0 }, + lifetime: 20 +}; + +var modelParticleID = Particles.addParticle(modelProperties); +var ballParticleID = Particles.addParticle(ballProperties); + +function endAfterAWhile() { + // stop it... + if (count >= stopAfter) { + print("calling Agent.stop()"); + Agent.stop(); + } + + print("count =" + count); + count++; +} + + +// register the call back so it fires before each data send +Agent.willSendVisualDataCallback.connect(endAfterAWhile); + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8bc845ff24..6578618e5f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -112,6 +112,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), + _statsExpanded(false), _nodeThread(new QThread(this)), _datagramProcessor(), _frameCount(0), @@ -155,8 +156,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _swatch(NULL), _pasteMode(false), _logger(new FileLogger()), - _persistThread(NULL), - _statsExpanded(false) + _persistThread(NULL) { _applicationStartupTime = startup_time; @@ -254,7 +254,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _window->setCentralWidget(_glWidget); restoreSizeAndPosition(); - loadScripts(); QFontDatabase fontDatabase; fontDatabase.addApplicationFont("resources/styles/Inconsolata.otf"); @@ -284,6 +283,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); checkVersion(); + + // do this as late as possible so that all required subsystems are inialized + loadScripts(); } Application::~Application() { @@ -679,6 +681,14 @@ void Application::controlledBroadcastToNodes(unsigned char* broadcastData, size_ } void Application::keyPressEvent(QKeyEvent* event) { + + _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isKeyCaptured(event)) { + return; + } + if (activeWindow() == _window) { if (_chatEntryOn) { if (_chatEntry.keyPressEvent(event)) { @@ -1090,6 +1100,15 @@ void Application::keyPressEvent(QKeyEvent* event) { } void Application::keyReleaseEvent(QKeyEvent* event) { + + _controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isKeyCaptured(event)) { + return; + } + + if (activeWindow() == _window) { if (_chatEntryOn) { _myAvatar.setKeyState(NO_KEY_DOWN); @@ -1152,6 +1171,14 @@ void Application::keyReleaseEvent(QKeyEvent* event) { } void Application::mouseMoveEvent(QMouseEvent* event) { + _controllerScriptingInterface.emitMouseMoveEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isMouseCaptured()) { + return; + } + + _lastMouseMove = usecTimestampNow(); if (_mouseHidden) { getGLWidget()->setCursor(Qt::ArrowCursor); @@ -1198,6 +1225,14 @@ const float HOVER_VOXEL_FREQUENCY = 7040.f; const float HOVER_VOXEL_DECAY = 0.999f; void Application::mousePressEvent(QMouseEvent* event) { + _controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isMouseCaptured()) { + return; + } + + if (activeWindow() == _window) { if (event->button() == Qt::LeftButton) { _mouseX = event->x(); @@ -1265,6 +1300,13 @@ void Application::mousePressEvent(QMouseEvent* event) { } void Application::mouseReleaseEvent(QMouseEvent* event) { + _controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isMouseCaptured()) { + return; + } + if (activeWindow() == _window) { if (event->button() == Qt::LeftButton) { _mouseX = event->x(); @@ -1281,6 +1323,13 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { } void Application::touchUpdateEvent(QTouchEvent* event) { + _controllerScriptingInterface.emitTouchUpdateEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isTouchCaptured()) { + return; + } + bool validTouch = false; if (activeWindow() == _window) { const QList& tPoints = event->touchPoints(); @@ -1305,19 +1354,46 @@ void Application::touchUpdateEvent(QTouchEvent* event) { } void Application::touchBeginEvent(QTouchEvent* event) { + _controllerScriptingInterface.emitTouchBeginEvent(event); // send events to any registered scripts + touchUpdateEvent(event); + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isTouchCaptured()) { + return; + } + + // put any application specific touch behavior below here.. _lastTouchAvgX = _touchAvgX; _lastTouchAvgY = _touchAvgY; + } void Application::touchEndEvent(QTouchEvent* event) { + _controllerScriptingInterface.emitTouchEndEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isTouchCaptured()) { + return; + } + + // put any application specific touch behavior below here.. _touchDragStartedAvgX = _touchAvgX; _touchDragStartedAvgY = _touchAvgY; _isTouchPressed = false; + } const bool USE_MOUSEWHEEL = false; void Application::wheelEvent(QWheelEvent* event) { + + _controllerScriptingInterface.emitWheelEvent(event); // send events to any registered scripts + + // if one of our scripts have asked to capture this event, then stop processing it + if (_controllerScriptingInterface.isWheelCaptured()) { + return; + } + // Wheel Events disabled for now because they are also activated by touch look pitch up/down. if (USE_MOUSEWHEEL && (activeWindow() == _window)) { if (!Menu::getInstance()->isVoxelModeActionChecked()) { @@ -2328,10 +2404,7 @@ void Application::updateAvatar(float deltaTime) { _yawFromTouch = 0.f; // apply pitch from touch - _myAvatar.getHead().setMousePitch(_myAvatar.getHead().getMousePitch() + - _myAvatar.getHand().getPitchUpdate() + - _pitchFromTouch); - _myAvatar.getHand().setPitchUpdate(0.f); + _myAvatar.getHead().setPitch(_myAvatar.getHead().getPitch() + _pitchFromTouch); _pitchFromTouch = 0.0f; // Update my avatar's state from gyros diff --git a/interface/src/ControllerScriptingInterface.cpp b/interface/src/ControllerScriptingInterface.cpp index fd27eb2428..4d7540c06c 100644 --- a/interface/src/ControllerScriptingInterface.cpp +++ b/interface/src/ControllerScriptingInterface.cpp @@ -10,6 +10,14 @@ #include "Application.h" #include "ControllerScriptingInterface.h" +ControllerScriptingInterface::ControllerScriptingInterface() : + _mouseCaptured(false), + _touchCaptured(false), + _wheelCaptured(false) +{ +} + + const PalmData* ControllerScriptingInterface::getPrimaryPalm() const { int leftPalmIndex, rightPalmIndex; @@ -179,5 +187,35 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex return glm::vec3(0); // bad index } +bool ControllerScriptingInterface::isKeyCaptured(QKeyEvent* event) const { + return isKeyCaptured(KeyEvent(*event)); +} +bool ControllerScriptingInterface::isKeyCaptured(const KeyEvent& event) const { + // if we've captured some combination of this key it will be in the map + if (_capturedKeys.contains(event.key, event)) { + return true; + } + return false; +} + +void ControllerScriptingInterface::captureKeyEvents(const KeyEvent& event) { + // if it's valid + if (event.isValid) { + // and not already captured + if (!isKeyCaptured(event)) { + // then add this KeyEvent record to the captured combos for this key + _capturedKeys.insert(event.key, event); + } + } +} + +void ControllerScriptingInterface::releaseKeyEvents(const KeyEvent& event) { + if (event.isValid) { + // and not already captured + if (isKeyCaptured(event)) { + _capturedKeys.remove(event.key, event); + } + } +} diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h index 69daefa3fb..e84039bcb0 100644 --- a/interface/src/ControllerScriptingInterface.h +++ b/interface/src/ControllerScriptingInterface.h @@ -1,3 +1,4 @@ + // // ControllerScriptingInterface.h // hifi @@ -18,6 +19,27 @@ class PalmData; class ControllerScriptingInterface : public AbstractControllerScriptingInterface { Q_OBJECT +public: + ControllerScriptingInterface(); + void emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); } + void emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); } + + void emitMouseMoveEvent(QMouseEvent* event) { emit mouseMoveEvent(MouseEvent(*event)); } + void emitMousePressEvent(QMouseEvent* event) { emit mousePressEvent(MouseEvent(*event)); } + void emitMouseReleaseEvent(QMouseEvent* event) { emit mouseReleaseEvent(MouseEvent(*event)); } + + void emitTouchBeginEvent(QTouchEvent* event) { emit touchBeginEvent(*event); } + void emitTouchEndEvent(QTouchEvent* event) { emit touchEndEvent(*event); } + void emitTouchUpdateEvent(QTouchEvent* event) { emit touchUpdateEvent(*event); } + void emitWheelEvent(QWheelEvent* event) { emit wheelEvent(*event); } + + bool isKeyCaptured(QKeyEvent* event) const; + bool isKeyCaptured(const KeyEvent& event) const; + bool isMouseCaptured() const { return _mouseCaptured; } + bool isTouchCaptured() const { return _touchCaptured; } + bool isWheelCaptured() const { return _wheelCaptured; } + + public slots: virtual bool isPrimaryButtonPressed() const; virtual glm::vec2 getPrimaryJoystickPosition() const; @@ -36,11 +58,28 @@ public slots: virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const; virtual glm::vec3 getSpatialControlNormal(int controlIndex) const; + virtual void captureKeyEvents(const KeyEvent& event); + virtual void releaseKeyEvents(const KeyEvent& event); + + virtual void captureMouseEvents() { _mouseCaptured = true; } + virtual void releaseMouseEvents() { _mouseCaptured = false; } + + virtual void captureTouchEvents() { _touchCaptured = true; } + virtual void releaseTouchEvents() { _touchCaptured = false; } + + virtual void captureWheelEvents() { _wheelCaptured = true; } + virtual void releaseWheelEvents() { _wheelCaptured = false; } + private: const PalmData* getPrimaryPalm() const; const PalmData* getPalm(int palmIndex) const; int getNumberOfActivePalms() const; const PalmData* getActivePalm(int palmIndex) const; + + bool _mouseCaptured; + bool _touchCaptured; + bool _wheelCaptured; + QMultiMap _capturedKeys; }; const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index b78bd0c309..29c073fdba 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -57,6 +57,7 @@ void DatagramProcessor::processDatagrams() { case PACKET_TYPE_PARTICLE_ADD_RESPONSE: // this will keep creatorTokenIDs to IDs mapped correctly Particle::handleAddParticleResponse(incomingPacket, bytesReceived); + application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket, bytesReceived); break; case PACKET_TYPE_PARTICLE_DATA: diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/ParticleTreeRenderer.cpp index aa4978891f..7c82f76aab 100644 --- a/interface/src/ParticleTreeRenderer.cpp +++ b/interface/src/ParticleTreeRenderer.cpp @@ -7,6 +7,8 @@ // // +#include + #include "InterfaceConfig.h" #include "ParticleTreeRenderer.h" @@ -16,8 +18,18 @@ ParticleTreeRenderer::ParticleTreeRenderer() : } ParticleTreeRenderer::~ParticleTreeRenderer() { + // delete the models in _particleModels + foreach(Model* model, _particleModels) { + delete model; + } + _particleModels.clear(); } +void ParticleTreeRenderer::init() { + OctreeRenderer::init(); +} + + void ParticleTreeRenderer::update() { if (_tree) { ParticleTree* tree = (ParticleTree*)_tree; @@ -27,6 +39,25 @@ void ParticleTreeRenderer::update() { } } +void ParticleTreeRenderer::render() { + OctreeRenderer::render(); +} + +Model* ParticleTreeRenderer::getModel(const QString& url) { + Model* model = NULL; + + // if we don't already have this model then create it and initialize it + if (_particleModels.find(url) == _particleModels.end()) { + model = new Model(); + model->init(); + model->setURL(QUrl(url)); + _particleModels[url] = model; + } else { + model = _particleModels[url]; + } + return model; +} + void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) { // actually render it here... // we need to iterate the actual particles of the element @@ -36,27 +67,48 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg uint16_t numberOfParticles = particles.size(); - bool drawAsSphere = true; - for (uint16_t i = 0; i < numberOfParticles; i++) { const Particle& particle = particles[i]; // render particle aspoints glm::vec3 position = particle.getPosition() * (float)TREE_SCALE; glColor3ub(particle.getColor()[RED_INDEX],particle.getColor()[GREEN_INDEX],particle.getColor()[BLUE_INDEX]); - float sphereRadius = particle.getRadius() * (float)TREE_SCALE; + float radius = particle.getRadius() * (float)TREE_SCALE; + + bool drawAsModel = particle.hasModel(); args->_renderedItems++; - if (drawAsSphere) { + if (drawAsModel) { glPushMatrix(); - glTranslatef(position.x, position.y, position.z); - glutSolidSphere(sphereRadius, 15, 15); + const float alpha = 1.0f; + + Model* model = getModel(particle.getModelURL()); + + glm::vec3 translationAdjustment = particle.getModelTranslation(); + + // set the position + glm::vec3 translation(position.x, position.y, position.z); + model->setTranslation(translation + translationAdjustment); + + // set the rotation + glm::quat rotation = particle.getModelRotation(); + model->setRotation(rotation); + + // scale + // TODO: need to figure out correct scale adjust, this was arbitrarily set to make a couple models work + const float MODEL_SCALE = 0.00575f; + glm::vec3 scale(1.0f,1.0f,1.0f); + model->setScale(scale * MODEL_SCALE * radius * particle.getModelScale()); + + model->simulate(0.0f); + model->render(alpha); // TODO: should we allow particles to have alpha on their models? + glPopMatrix(); } else { - glPointSize(sphereRadius); - glBegin(GL_POINTS); - glVertex3f(position.x, position.y, position.z); - glEnd(); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glutSolidSphere(radius, 15, 15); + glPopMatrix(); } } } diff --git a/interface/src/ParticleTreeRenderer.h b/interface/src/ParticleTreeRenderer.h index 1c76cf623d..608d701f23 100644 --- a/interface/src/ParticleTreeRenderer.h +++ b/interface/src/ParticleTreeRenderer.h @@ -20,6 +20,7 @@ #include #include #include +#include "renderer/Model.h" // Generic client side Octree renderer class. class ParticleTreeRenderer : public OctreeRenderer { @@ -39,7 +40,13 @@ public: void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode); + virtual void init(); + virtual void render(); + protected: + Model* getModel(const QString& url); + + QMap _particleModels; }; #endif /* defined(__hifi__ParticleTreeRenderer__) */ \ No newline at end of file diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index a2e518b2d7..ef7e049d75 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -105,34 +105,6 @@ glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { return glm::angleAxis(angle, axis); } -// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's -// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde, -// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) -glm::vec3 safeEulerAngles(const glm::quat& q) { - float sy = 2.0f * (q.y * q.w - q.x * q.z); - if (sy < 1.0f - EPSILON) { - if (sy > -1.0f + EPSILON) { - return glm::degrees(glm::vec3( - atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)), - asinf(sy), - atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z)))); - - } else { - // not a unique solution; x + z = atan2(-m21, m11) - return glm::degrees(glm::vec3( - 0.0f, - PIf * -0.5f, - atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z)))); - } - } else { - // not a unique solution; x - z = atan2(-m21, m11) - return glm::degrees(glm::vec3( - 0.0f, - PIf * 0.5f, - -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z)))); - } -} - // Safe version of glm::mix; based on the code in Nick Bobick's article, // http://www.gamasutra.com/features/19980703/quaternions_01.htm (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) diff --git a/interface/src/Util.h b/interface/src/Util.h index 2a812120f0..09d1fa0484 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -54,8 +54,6 @@ float angleBetween(const glm::vec3& v1, const glm::vec3& v2); glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); -glm::vec3 safeEulerAngles(const glm::quat& q); - glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha); glm::vec3 extractTranslation(const glm::mat4& matrix); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 78e93996c8..2269d1d4a6 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -37,8 +37,6 @@ Head::Head(Avatar* owningAvatar) : _leftEyeBlinkVelocity(0.0f), _rightEyeBlinkVelocity(0.0f), _timeWithoutTalking(0.0f), - _cameraPitch(_pitch), - _mousePitch(0.f), _cameraYaw(_yaw), _isCameraMoving(false), _faceModel(this) @@ -52,7 +50,6 @@ void Head::init() { void Head::reset() { _yaw = _pitch = _roll = 0.0f; - _mousePitch = 0.0f; _leanForward = _leanSideways = 0.0f; _faceModel.reset(); } @@ -186,13 +183,6 @@ void Head::setScale (float scale) { _scale = scale; } -void Head::setMousePitch(float mousePitch) { - const float MAX_PITCH = 90.0f; - _mousePitch = glm::clamp(mousePitch, -MAX_PITCH, MAX_PITCH); -} - - - glm::quat Head::getOrientation() const { return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll))); } @@ -200,7 +190,7 @@ glm::quat Head::getOrientation() const { glm::quat Head::getCameraOrientation () const { Avatar* owningAvatar = static_cast(_owningAvatar); return owningAvatar->getWorldAlignedOrientation() - * glm::quat(glm::radians(glm::vec3(_cameraPitch + _mousePitch, _cameraYaw, 0.0f))); + * glm::quat(glm::radians(glm::vec3(_pitch, _cameraYaw, 0.0f))); } glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const { diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 5d9f9d9bbd..94b8bd3dc7 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -46,9 +46,6 @@ public: void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } - float getMousePitch() const { return _mousePitch; } - void setMousePitch(float mousePitch); - glm::quat getOrientation() const; glm::quat getCameraOrientation () const; const glm::vec3& getAngularVelocity() const { return _angularVelocity; } @@ -99,8 +96,6 @@ private: float _leftEyeBlinkVelocity; float _rightEyeBlinkVelocity; float _timeWithoutTalking; - float _cameraPitch; // Used to position the camera differently from the head - float _mousePitch; float _cameraYaw; bool _isCameraMoving; FaceModel _faceModel; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6910826524..7448d33415 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -238,7 +238,7 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) { // Adjust body yaw by yaw from controller setOrientation(glm::angleAxis(-euler.y, glm::vec3(0, 1, 0)) * getOrientation()); // Adjust head pitch from controller - getHead().setMousePitch(getHead().getMousePitch() - euler.x); + getHead().setPitch(getHead().getPitch() - euler.x); _position += _velocity * deltaTime; @@ -284,8 +284,6 @@ void MyAvatar::updateFromGyros(bool turnWithHead) { } } } else { - _head.setPitch(_head.getMousePitch()); - // restore rotation, lean to neutral positions const float RESTORE_RATE = 0.05f; _head.setYaw(glm::mix(_head.getYaw(), 0.0f, RESTORE_RATE)); @@ -434,7 +432,7 @@ void MyAvatar::saveData(QSettings* settings) { settings->setValue("bodyPitch", _bodyPitch); settings->setValue("bodyRoll", _bodyRoll); - settings->setValue("mousePitch", _head.getMousePitch()); + settings->setValue("headPitch", _head.getPitch()); settings->setValue("position_x", _position.x); settings->setValue("position_y", _position.y); @@ -456,7 +454,7 @@ void MyAvatar::loadData(QSettings* settings) { _bodyPitch = loadSetting(settings, "bodyPitch", 0.0f); _bodyRoll = loadSetting(settings, "bodyRoll", 0.0f); - _head.setMousePitch(loadSetting(settings, "mousePitch", 0.0f)); + _head.setPitch(loadSetting(settings, "headPitch", 0.0f)); _position.x = loadSetting(settings, "position_x", 0.0f); _position.y = loadSetting(settings, "position_y", 0.0f); @@ -497,9 +495,10 @@ void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) { setOrientation(orientation); // then vertically - float oldMousePitch = _head.getMousePitch(); - _head.setMousePitch(oldMousePitch + deltaY * -ANGULAR_SCALE); - rotation = glm::angleAxis(_head.getMousePitch() - oldMousePitch, orientation * IDENTITY_RIGHT); + float oldPitch = _head.getPitch(); + _head.setPitch(oldPitch + deltaY * -ANGULAR_SCALE); + rotation = glm::angleAxis(_head.getPitch() - oldPitch, orientation * IDENTITY_RIGHT); + setPosition(position + rotation * (getPosition() - position)); } @@ -549,7 +548,7 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) { _thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up; _bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_MAG * deltaTime; _bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_MAG * deltaTime; - _head.setMousePitch(_head.getMousePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime); + _head.setPitch(_head.getPitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime); // If thrust keys are being held down, slowly increase thrust to allow reaching great speeds if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) { diff --git a/interface/src/world.h b/interface/src/world.h index a226dc228e..8d3bd7322e 100644 --- a/interface/src/world.h +++ b/interface/src/world.h @@ -9,8 +9,9 @@ #ifndef __interface__world__ #define __interface__world__ - +#ifndef PIf #define PIf 3.14159265f +#endif const float GRAVITY_EARTH = 9.80665f; const float EDGE_SIZE_GROUND_PLANE = 20.f; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 8f548869ec..44f71c7aec 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -276,3 +276,10 @@ void AvatarData::setClampedTargetScale(float targetScale) { _targetScale = targetScale; qDebug() << "Changed scale to " << _targetScale; } + +void AvatarData::setOrientation(const glm::quat& orientation) { + glm::vec3 eulerAngles = safeEulerAngles(orientation); + _bodyPitch = eulerAngles.x; + _bodyYaw = eulerAngles.y; + _bodyRoll = eulerAngles.z; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index fc9bad7f02..e3bb8f08d5 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -69,6 +69,10 @@ class AvatarData : public NodeData { Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch) Q_PROPERTY(float bodyRoll READ getBodyRoll WRITE setBodyRoll) Q_PROPERTY(QString chatMessage READ getQStringChatMessage WRITE setChatMessage) + + Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) + Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch) + public: AvatarData(); ~AvatarData(); @@ -91,6 +95,11 @@ public: void setBodyRoll(float bodyRoll) { _bodyRoll = bodyRoll; } glm::quat getOrientation() const { return glm::quat(glm::radians(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll))); } + void setOrientation(const glm::quat& orientation); + + // access to Head().set/getMousePitch + float getHeadPitch() const { return _headData->getPitch(); } + void setHeadPitch(float value) { _headData->setPitch(value); }; // Scale float getTargetScale() const { return _targetScale; } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 5d25f0c164..1fa583b2be 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -43,6 +43,7 @@ Octree::Octree(bool shouldReaverage) : _shouldReaverage(shouldReaverage), _stopImport(false) { _rootNode = NULL; + _isViewing = false; } Octree::~Octree() { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 959c9272cf..b01a8b1d63 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -258,6 +258,9 @@ public: void recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation, const glm::vec3& point, void* extraData, int recursionCount = 0); + bool getIsViewing() const { return _isViewing; } + void setIsViewing(bool isViewing) { _isViewing = isViewing; } + signals: void importSize(float x, float y, float z); void importProgress(int progress); @@ -321,6 +324,9 @@ protected: void emptyDeleteQueue(); QReadWriteLock lock; + + /// This tree is receiving inbound viewer datagrams. + bool _isViewing; }; float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 9e7d88416b..59216918b1 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -305,6 +305,19 @@ bool OctreePacketData::appendValue(const glm::vec3& value) { return success; } +bool OctreePacketData::appendValue(const glm::quat& value) { + const size_t VALUES_PER_QUAT = 4; + const size_t PACKED_QUAT_SIZE = sizeof(uint16_t) * VALUES_PER_QUAT; + unsigned char data[PACKED_QUAT_SIZE]; + int length = packOrientationQuatToBytes(data, value); + bool success = append(data, length); + if (success) { + _bytesOfValues += length; + _totalBytesOfValues += length; + } + return success; +} + bool OctreePacketData::appendValue(bool value) { bool success = append((uint8_t)value); // used unsigned char version if (success) { diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 87b55ace96..27f2ee5090 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -130,6 +130,9 @@ public: /// appends a non-position vector to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const glm::vec3& value); + /// appends a packed quat to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const glm::quat& value); + /// appends a bool value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(bool value); diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index 7bbeb065d6..de0d09ef74 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -42,6 +42,10 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Hifi if(command == expectedType) { PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PACKET_TYPE",showTimingDetails); + + // if we are getting inbound packets, then our tree is also viewing, and we should remember that fact. + _tree->setIsViewing(true); + const unsigned char* dataAt = packetData + numBytesPacketHeader; diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index f321ed2e3e..10d655ba17 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -46,7 +46,7 @@ public: virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode); /// initialize and GPU/rendering related resources - void init(); + virtual void init(); /// render the content of the octree virtual void render(); diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index c48068199a..933bf36830 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -65,19 +65,39 @@ void Particle::handleAddParticleResponse(unsigned char* packetData , int packetL } - -Particle::Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity, - float damping, float lifetime, bool inHand, QString updateScript, uint32_t id) { - - init(position, radius, color, velocity, gravity, damping, lifetime, inHand, updateScript, id); -} - Particle::Particle() { rgbColor noColor = { 0, 0, 0 }; init(glm::vec3(0,0,0), 0, noColor, glm::vec3(0,0,0), DEFAULT_GRAVITY, DEFAULT_DAMPING, DEFAULT_LIFETIME, NOT_IN_HAND, DEFAULT_SCRIPT, NEW_PARTICLE); } +Particle::Particle(const ParticleID& particleID, const ParticleProperties& properties) { + _id = particleID.id; + _creatorTokenID = particleID.creatorTokenID; + + // init values with defaults before calling setProperties + uint64_t now = usecTimestampNow(); + _lastEdited = now; + _lastUpdated = now; + _created = now; // will get updated as appropriate in setAge() + + _position = glm::vec3(0,0,0); + _radius = 0; + _mass = 1.0f; + rgbColor noColor = { 0, 0, 0 }; + memcpy(_color, noColor, sizeof(_color)); + _velocity = glm::vec3(0,0,0); + _damping = DEFAULT_DAMPING; + _lifetime = DEFAULT_LIFETIME; + _gravity = DEFAULT_GRAVITY; + _script = DEFAULT_SCRIPT; + _inHand = NOT_IN_HAND; + _shouldDie = false; + + setProperties(properties); +} + + Particle::~Particle() { } @@ -86,10 +106,8 @@ void Particle::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 if (id == NEW_PARTICLE) { _id = _nextID; _nextID++; - //qDebug() << "Particle::init()... assigning new id... _id=" << _id; } else { _id = id; - //qDebug() << "Particle::init()... assigning id from init... _id=" << _id; } uint64_t now = usecTimestampNow(); _lastEdited = now; @@ -157,7 +175,6 @@ bool Particle::appendParticleData(OctreePacketData* packetData) const { if (success) { success = packetData->appendValue(getShouldDie()); } - if (success) { uint16_t scriptLength = _script.size() + 1; // include NULL success = packetData->appendValue(scriptLength); @@ -165,6 +182,28 @@ bool Particle::appendParticleData(OctreePacketData* packetData) const { success = packetData->appendRawData((const unsigned char*)qPrintable(_script), scriptLength); } } + + // 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); + } + } + // modelTranslation + if (success) { + success = packetData->appendValue(getModelTranslation()); + } + // modelRotation + if (success) { + success = packetData->appendValue(getModelRotation()); + } + // modelScale + if (success) { + success = packetData->appendValue(getModelScale()); + } + return success; } @@ -185,21 +224,6 @@ int Particle::expectedBytes() { return expectedBytes; } -int Particle::expectedEditMessageBytes() { - int expectedBytes = sizeof(uint32_t) // id - + sizeof(uint64_t) // lasted edited - + sizeof(float) // radius - + sizeof(glm::vec3) // position - + sizeof(rgbColor) // color - + sizeof(glm::vec3) // velocity - + sizeof(glm::vec3) // gravity - + sizeof(float) // damping - + sizeof(float) // lifetime - + sizeof(bool); // inhand - // potentially more... - return expectedBytes; -} - int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { int bytesRead = 0; if (bytesLeftToRead >= expectedBytes()) { @@ -286,6 +310,31 @@ int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLef dataAt += scriptLength; bytesRead += scriptLength; + // 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; + + // modelTranslation + memcpy(&_modelTranslation, dataAt, sizeof(_modelTranslation)); + dataAt += sizeof(_modelTranslation); + bytesRead += sizeof(_modelTranslation); + + // modelRotation + int bytes = unpackOrientationQuatFromBytes(dataAt, _modelRotation); + dataAt += bytes; + bytesRead += bytes; + + // modelScale + memcpy(&_modelScale, dataAt, sizeof(_modelScale)); + dataAt += sizeof(_modelScale); + bytesRead += sizeof(_modelScale); + //printf("Particle::readParticleDataFromBuffer()... "); debugDump(); } return bytesRead; @@ -366,49 +415,49 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe // radius - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_RADIUS) == PACKET_CONTAINS_RADIUS)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_RADIUS) == CONTAINS_RADIUS)) { memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius)); dataAt += sizeof(newParticle._radius); processedBytes += sizeof(newParticle._radius); } // position - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_POSITION) == PACKET_CONTAINS_POSITION)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_POSITION) == CONTAINS_POSITION)) { memcpy(&newParticle._position, dataAt, sizeof(newParticle._position)); dataAt += sizeof(newParticle._position); processedBytes += sizeof(newParticle._position); } // color - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_COLOR) == PACKET_CONTAINS_COLOR)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_COLOR) == CONTAINS_COLOR)) { memcpy(newParticle._color, dataAt, sizeof(newParticle._color)); dataAt += sizeof(newParticle._color); processedBytes += sizeof(newParticle._color); } // velocity - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_VELOCITY) == PACKET_CONTAINS_VELOCITY)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_VELOCITY) == CONTAINS_VELOCITY)) { memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity)); dataAt += sizeof(newParticle._velocity); processedBytes += sizeof(newParticle._velocity); } // gravity - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_GRAVITY) == PACKET_CONTAINS_GRAVITY)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_GRAVITY) == CONTAINS_GRAVITY)) { memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity)); dataAt += sizeof(newParticle._gravity); processedBytes += sizeof(newParticle._gravity); } // damping - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_DAMPING) == PACKET_CONTAINS_DAMPING)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_DAMPING) == CONTAINS_DAMPING)) { memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping)); dataAt += sizeof(newParticle._damping); processedBytes += sizeof(newParticle._damping); } // lifetime - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_LIFETIME) == PACKET_CONTAINS_LIFETIME)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_LIFETIME) == CONTAINS_LIFETIME)) { memcpy(&newParticle._lifetime, dataAt, sizeof(newParticle._lifetime)); dataAt += sizeof(newParticle._lifetime); processedBytes += sizeof(newParticle._lifetime); @@ -416,21 +465,21 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe // TODO: make inHand and shouldDie into single bits // inHand - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_INHAND) == PACKET_CONTAINS_INHAND)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_INHAND) == CONTAINS_INHAND)) { memcpy(&newParticle._inHand, dataAt, sizeof(newParticle._inHand)); dataAt += sizeof(newParticle._inHand); processedBytes += sizeof(newParticle._inHand); } // shouldDie - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_SHOULDDIE) == PACKET_CONTAINS_SHOULDDIE)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_SHOULDDIE) == CONTAINS_SHOULDDIE)) { memcpy(&newParticle._shouldDie, dataAt, sizeof(newParticle._shouldDie)); dataAt += sizeof(newParticle._shouldDie); processedBytes += sizeof(newParticle._shouldDie); } // script - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_SCRIPT) == PACKET_CONTAINS_SCRIPT)) { + if (isNewParticle || ((packetContainsBits & CONTAINS_SCRIPT) == CONTAINS_SCRIPT)) { uint16_t scriptLength; memcpy(&scriptLength, dataAt, sizeof(scriptLength)); dataAt += sizeof(scriptLength); @@ -441,6 +490,39 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe processedBytes += scriptLength; } + // modelURL + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_URL) == CONTAINS_MODEL_URL)) { + uint16_t modelURLLength; + memcpy(&modelURLLength, dataAt, sizeof(modelURLLength)); + dataAt += sizeof(modelURLLength); + processedBytes += sizeof(modelURLLength); + QString tempString((const char*)dataAt); + newParticle._modelURL = tempString; + dataAt += modelURLLength; + processedBytes += modelURLLength; + } + + // modelTranslation + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_TRANSLATION) == CONTAINS_MODEL_TRANSLATION)) { + memcpy(&newParticle._modelTranslation, dataAt, sizeof(newParticle._modelTranslation)); + dataAt += sizeof(newParticle._modelTranslation); + processedBytes += sizeof(newParticle._modelTranslation); + } + + // modelRotation + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_ROTATION) == CONTAINS_MODEL_ROTATION)) { + int bytes = unpackOrientationQuatFromBytes(dataAt, newParticle._modelRotation); + dataAt += bytes; + processedBytes += bytes; + } + + // modelScale + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_SCALE) == CONTAINS_MODEL_SCALE)) { + memcpy(&newParticle._modelScale, dataAt, sizeof(newParticle._modelScale)); + dataAt += sizeof(newParticle._modelScale); + processedBytes += sizeof(newParticle._modelScale); + } + const bool wantDebugging = false; if (wantDebugging) { qDebug("Particle::fromEditPacket()..."); @@ -487,138 +569,166 @@ bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, ParticleID int octets = numberOfThreeBitSectionsInCode(octcode); int lengthOfOctcode = bytesRequiredForCodeLength(octets); - int lenfthOfEditData = lengthOfOctcode + expectedEditMessageBytes(); - // make sure we have room to copy this particle - if (sizeOut + lenfthOfEditData > sizeIn) { - success = false; - } else { - // add it to our message - memcpy(copyAt, octcode, lengthOfOctcode); - copyAt += lengthOfOctcode; - sizeOut += lengthOfOctcode; + // add it to our message + memcpy(copyAt, octcode, lengthOfOctcode); + copyAt += lengthOfOctcode; + sizeOut += lengthOfOctcode; - // Now add our edit content details... - bool isNewParticle = (id.id == NEW_PARTICLE); + // Now add our edit content details... + bool isNewParticle = (id.id == NEW_PARTICLE); - // id - memcpy(copyAt, &id.id, sizeof(id.id)); - copyAt += sizeof(id.id); - sizeOut += sizeof(id.id); + // id + memcpy(copyAt, &id.id, sizeof(id.id)); + copyAt += sizeof(id.id); + sizeOut += sizeof(id.id); - // special case for handling "new" particles - if (isNewParticle) { - // If this is a NEW_PARTICLE, 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); - } + // special case for handling "new" particles + if (isNewParticle) { + // If this is a NEW_PARTICLE, 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 - uint64_t lastEdited = properties.getLastEdited(); - memcpy(copyAt, &lastEdited, sizeof(lastEdited)); - copyAt += sizeof(lastEdited); - sizeOut += sizeof(lastEdited); + // lastEdited + uint64_t lastEdited = properties.getLastEdited(); + memcpy(copyAt, &lastEdited, sizeof(lastEdited)); + copyAt += sizeof(lastEdited); + sizeOut += sizeof(lastEdited); - // For new particles, all remaining items are mandatory, for an edited particle, 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 (!isNewParticle) { - memcpy(copyAt, &packetContainsBits, sizeof(packetContainsBits)); - copyAt += sizeof(packetContainsBits); - sizeOut += sizeof(packetContainsBits); - } + // For new particles, all remaining items are mandatory, for an edited particle, 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 (!isNewParticle) { + memcpy(copyAt, &packetContainsBits, sizeof(packetContainsBits)); + copyAt += sizeof(packetContainsBits); + sizeOut += sizeof(packetContainsBits); + } - // radius - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_RADIUS) == PACKET_CONTAINS_RADIUS)) { - float radius = properties.getRadius() / (float) TREE_SCALE; - memcpy(copyAt, &radius, sizeof(radius)); - copyAt += sizeof(radius); - sizeOut += sizeof(radius); - } + // radius + if (isNewParticle || ((packetContainsBits & CONTAINS_RADIUS) == CONTAINS_RADIUS)) { + float radius = properties.getRadius() / (float) TREE_SCALE; + memcpy(copyAt, &radius, sizeof(radius)); + copyAt += sizeof(radius); + sizeOut += sizeof(radius); + } - // position - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_POSITION) == PACKET_CONTAINS_POSITION)) { - glm::vec3 position = properties.getPosition() / (float)TREE_SCALE; - memcpy(copyAt, &position, sizeof(position)); - copyAt += sizeof(position); - sizeOut += sizeof(position); - } + // position + if (isNewParticle || ((packetContainsBits & CONTAINS_POSITION) == CONTAINS_POSITION)) { + glm::vec3 position = properties.getPosition() / (float)TREE_SCALE; + memcpy(copyAt, &position, sizeof(position)); + copyAt += sizeof(position); + sizeOut += sizeof(position); + } - // color - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_COLOR) == 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); - } + // color + if (isNewParticle || ((packetContainsBits & CONTAINS_COLOR) == CONTAINS_COLOR)) { + rgbColor color = { properties.getColor().red, properties.getColor().green, properties.getColor().blue }; + memcpy(copyAt, color, sizeof(color)); + copyAt += sizeof(color); + sizeOut += sizeof(color); + } - // velocity - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_VELOCITY) == PACKET_CONTAINS_VELOCITY)) { - glm::vec3 velocity = properties.getVelocity() / (float)TREE_SCALE; - memcpy(copyAt, &velocity, sizeof(velocity)); - copyAt += sizeof(velocity); - sizeOut += sizeof(velocity); - } + // velocity + if (isNewParticle || ((packetContainsBits & CONTAINS_VELOCITY) == CONTAINS_VELOCITY)) { + glm::vec3 velocity = properties.getVelocity() / (float)TREE_SCALE; + memcpy(copyAt, &velocity, sizeof(velocity)); + copyAt += sizeof(velocity); + sizeOut += sizeof(velocity); + } - // gravity - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_GRAVITY) == PACKET_CONTAINS_GRAVITY)) { - glm::vec3 gravity = properties.getGravity() / (float)TREE_SCALE; - memcpy(copyAt, &gravity, sizeof(gravity)); - copyAt += sizeof(gravity); - sizeOut += sizeof(gravity); - } + // gravity + if (isNewParticle || ((packetContainsBits & CONTAINS_GRAVITY) == CONTAINS_GRAVITY)) { + glm::vec3 gravity = properties.getGravity() / (float)TREE_SCALE; + memcpy(copyAt, &gravity, sizeof(gravity)); + copyAt += sizeof(gravity); + sizeOut += sizeof(gravity); + } - // damping - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_DAMPING) == PACKET_CONTAINS_DAMPING)) { - float damping = properties.getDamping(); - memcpy(copyAt, &damping, sizeof(damping)); - copyAt += sizeof(damping); - sizeOut += sizeof(damping); - } + // damping + if (isNewParticle || ((packetContainsBits & CONTAINS_DAMPING) == CONTAINS_DAMPING)) { + float damping = properties.getDamping(); + memcpy(copyAt, &damping, sizeof(damping)); + copyAt += sizeof(damping); + sizeOut += sizeof(damping); + } - // lifetime - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_LIFETIME) == PACKET_CONTAINS_LIFETIME)) { - float lifetime = properties.getLifetime(); - memcpy(copyAt, &lifetime, sizeof(lifetime)); - copyAt += sizeof(lifetime); - sizeOut += sizeof(lifetime); - } + // lifetime + if (isNewParticle || ((packetContainsBits & CONTAINS_LIFETIME) == CONTAINS_LIFETIME)) { + float lifetime = properties.getLifetime(); + memcpy(copyAt, &lifetime, sizeof(lifetime)); + copyAt += sizeof(lifetime); + sizeOut += sizeof(lifetime); + } - // inHand - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_INHAND) == PACKET_CONTAINS_INHAND)) { - bool inHand = properties.getInHand(); - memcpy(copyAt, &inHand, sizeof(inHand)); - copyAt += sizeof(inHand); - sizeOut += sizeof(inHand); - } + // inHand + if (isNewParticle || ((packetContainsBits & CONTAINS_INHAND) == CONTAINS_INHAND)) { + bool inHand = properties.getInHand(); + memcpy(copyAt, &inHand, sizeof(inHand)); + copyAt += sizeof(inHand); + sizeOut += sizeof(inHand); + } - // shoulDie - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_SHOULDDIE) == PACKET_CONTAINS_SHOULDDIE)) { - bool shouldDie = properties.getShouldDie(); - memcpy(copyAt, &shouldDie, sizeof(shouldDie)); - copyAt += sizeof(shouldDie); - sizeOut += sizeof(shouldDie); - } + // shoulDie + if (isNewParticle || ((packetContainsBits & CONTAINS_SHOULDDIE) == CONTAINS_SHOULDDIE)) { + bool shouldDie = properties.getShouldDie(); + memcpy(copyAt, &shouldDie, sizeof(shouldDie)); + copyAt += sizeof(shouldDie); + sizeOut += sizeof(shouldDie); + } - // script - if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_SCRIPT) == PACKET_CONTAINS_SCRIPT)) { - uint16_t scriptLength = properties.getScript().size() + 1; - memcpy(copyAt, &scriptLength, sizeof(scriptLength)); - copyAt += sizeof(scriptLength); - sizeOut += sizeof(scriptLength); - memcpy(copyAt, qPrintable(properties.getScript()), scriptLength); - copyAt += scriptLength; - sizeOut += scriptLength; - } + // script + if (isNewParticle || ((packetContainsBits & CONTAINS_SCRIPT) == CONTAINS_SCRIPT)) { + uint16_t scriptLength = properties.getScript().size() + 1; + memcpy(copyAt, &scriptLength, sizeof(scriptLength)); + copyAt += sizeof(scriptLength); + sizeOut += sizeof(scriptLength); + memcpy(copyAt, qPrintable(properties.getScript()), scriptLength); + copyAt += scriptLength; + sizeOut += scriptLength; + } - bool wantDebugging = false; - if (wantDebugging) { - printf("encodeParticleEditMessageDetails()....\n"); - printf("Particle id :%u\n", id.id); - printf(" nextID:%u\n", _nextID); - } + // modelURL + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_URL) == 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; + } + + // modelTranslation + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_TRANSLATION) == CONTAINS_MODEL_TRANSLATION)) { + glm::vec3 modelTranslation = properties.getModelTranslation(); // should this be relative to TREE_SCALE?? + memcpy(copyAt, &modelTranslation, sizeof(modelTranslation)); + copyAt += sizeof(modelTranslation); + sizeOut += sizeof(modelTranslation); + } + + // modelRotation + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_ROTATION) == CONTAINS_MODEL_ROTATION)) { + int bytes = packOrientationQuatToBytes(copyAt, properties.getModelRotation()); + copyAt += bytes; + sizeOut += bytes; + } + + // modelScale + if (isNewParticle || ((packetContainsBits & CONTAINS_MODEL_SCALE) == CONTAINS_MODEL_SCALE)) { + float modelScale = properties.getModelScale(); + memcpy(copyAt, &modelScale, sizeof(modelScale)); + copyAt += sizeof(modelScale); + sizeOut += sizeof(modelScale); + } + + bool wantDebugging = false; + if (wantDebugging) { + printf("encodeParticleEditMessageDetails()....\n"); + printf("Particle id :%u\n", id.id); + printf(" nextID:%u\n", _nextID); } // cleanup @@ -875,10 +985,15 @@ ParticleProperties::ParticleProperties() : _script(""), _inHand(false), _shouldDie(false), + _modelURL(""), + _modelTranslation(DEFAULT_MODEL_TRANSLATION), + _modelRotation(DEFAULT_MODEL_ROTATION), + _modelScale(DEFAULT_MODEL_SCALE), _id(UNKNOWN_PARTICLE_ID), _idSet(false), _lastEdited(usecTimestampNow()), + _positionChanged(false), _colorChanged(false), _radiusChanged(false), @@ -889,6 +1004,10 @@ ParticleProperties::ParticleProperties() : _scriptChanged(false), _inHandChanged(false), _shouldDieChanged(false), + _modelURLChanged(false), + _modelTranslationChanged(false), + _modelRotationChanged(false), + _modelScaleChanged(false), _defaultSettings(true) { } @@ -897,44 +1016,59 @@ ParticleProperties::ParticleProperties() : uint16_t ParticleProperties::getChangedBits() const { uint16_t changedBits = 0; if (_radiusChanged) { - changedBits += PACKET_CONTAINS_RADIUS; + changedBits += CONTAINS_RADIUS; } if (_positionChanged) { - changedBits += PACKET_CONTAINS_POSITION; + changedBits += CONTAINS_POSITION; } if (_colorChanged) { - changedBits += PACKET_CONTAINS_COLOR; + changedBits += CONTAINS_COLOR; } if (_velocityChanged) { - changedBits += PACKET_CONTAINS_VELOCITY; + changedBits += CONTAINS_VELOCITY; } if (_gravityChanged) { - changedBits += PACKET_CONTAINS_GRAVITY; + changedBits += CONTAINS_GRAVITY; } if (_dampingChanged) { - changedBits += PACKET_CONTAINS_DAMPING; + changedBits += CONTAINS_DAMPING; } if (_lifetimeChanged) { - changedBits += PACKET_CONTAINS_LIFETIME; + changedBits += CONTAINS_LIFETIME; } if (_inHandChanged) { - changedBits += PACKET_CONTAINS_INHAND; + changedBits += CONTAINS_INHAND; } if (_scriptChanged) { - changedBits += PACKET_CONTAINS_SCRIPT; + changedBits += CONTAINS_SCRIPT; } - // how do we want to handle this? if (_shouldDieChanged) { - changedBits += PACKET_CONTAINS_SHOULDDIE; + changedBits += CONTAINS_SHOULDDIE; + } + + if (_modelURLChanged) { + changedBits += CONTAINS_MODEL_URL; + } + + if (_modelTranslationChanged) { + changedBits += CONTAINS_MODEL_TRANSLATION; + } + + if (_modelRotationChanged) { + changedBits += CONTAINS_MODEL_ROTATION; + } + + if (_modelScaleChanged) { + changedBits += CONTAINS_MODEL_SCALE; } return changedBits; @@ -963,7 +1097,18 @@ QScriptValue ParticleProperties::copyToScriptValue(QScriptEngine* engine) const properties.setProperty("script", _script); properties.setProperty("inHand", _inHand); properties.setProperty("shouldDie", _shouldDie); - + + properties.setProperty("modelURL", _modelURL); + + QScriptValue modelTranslation = vec3toScriptValue(engine, _modelTranslation); + properties.setProperty("modelTranslation", modelTranslation); + + QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation); + properties.setProperty("modelRotation", modelRotation); + + properties.setProperty("modelScale", _modelScale); + + if (_idSet) { properties.setProperty("id", _id); properties.setProperty("isKnownID", (_id == UNKNOWN_PARTICLE_ID)); @@ -1104,48 +1249,147 @@ void ParticleProperties::copyFromScriptValue(const QScriptValue &object) { } } + QScriptValue modelURL = object.property("modelURL"); + if (modelURL.isValid()) { + QString newModelURL; + newModelURL = modelURL.toVariant().toString(); + if (_defaultSettings || newModelURL != _modelURL) { + _modelURL = newModelURL; + _modelURLChanged = true; + } + } + + QScriptValue modelTranslation = object.property("modelTranslation"); + if (modelTranslation.isValid()) { + QScriptValue x = modelTranslation.property("x"); + QScriptValue y = modelTranslation.property("y"); + QScriptValue z = modelTranslation.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + glm::vec3 newModelTranslation; + newModelTranslation.x = x.toVariant().toFloat(); + newModelTranslation.y = y.toVariant().toFloat(); + newModelTranslation.z = z.toVariant().toFloat(); + if (_defaultSettings || newModelTranslation != _modelTranslation) { + _modelTranslation = newModelTranslation; + _modelTranslationChanged = 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; + } + } + } + + QScriptValue modelScale = object.property("modelScale"); + if (modelScale.isValid()) { + float newModelScale; + newModelScale = modelScale.toVariant().toFloat(); + if (_defaultSettings || newModelScale != _modelScale) { + _modelScale = newModelScale; + _modelScaleChanged = true; + } + } + _lastEdited = usecTimestampNow(); } void ParticleProperties::copyToParticle(Particle& particle) const { + bool somethingChanged = false; if (_positionChanged) { particle.setPosition(_position / (float) TREE_SCALE); + somethingChanged = true; } if (_colorChanged) { particle.setColor(_color); + somethingChanged = true; } if (_radiusChanged) { particle.setRadius(_radius / (float) TREE_SCALE); + somethingChanged = true; } if (_velocityChanged) { particle.setVelocity(_velocity / (float) TREE_SCALE); + somethingChanged = true; } if (_gravityChanged) { particle.setGravity(_gravity / (float) TREE_SCALE); + somethingChanged = true; } if (_dampingChanged) { particle.setDamping(_damping); + somethingChanged = true; } if (_lifetimeChanged) { particle.setLifetime(_lifetime); + somethingChanged = true; } if (_scriptChanged) { particle.setScript(_script); + somethingChanged = true; } if (_inHandChanged) { particle.setInHand(_inHand); + somethingChanged = true; } if (_shouldDieChanged) { particle.setShouldDie(_shouldDie); + somethingChanged = true; + } + + if (_modelURLChanged) { + particle.setModelURL(_modelURL); + somethingChanged = true; + } + + if (_modelTranslationChanged) { + particle.setModelTranslation(_modelTranslation); + somethingChanged = true; + } + + if (_modelRotationChanged) { + particle.setModelRotation(_modelRotation); + somethingChanged = true; + } + + if (_modelScaleChanged) { + particle.setModelScale(_modelScale); + somethingChanged = true; + } + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - _lastEdited; + qDebug() << "ParticleProperties::copyToParticle() AFTER update... edited AGO=" << elapsed << + "now=" << now << " _lastEdited=" << _lastEdited; + } + particle.setLastEdited(_lastEdited); } } @@ -1160,6 +1404,10 @@ void ParticleProperties::copyFromParticle(const Particle& particle) { _script = particle.getScript(); _inHand = particle.getInHand(); _shouldDie = particle.getShouldDie(); + _modelURL = particle.getModelURL(); + _modelTranslation = particle.getModelTranslation(); + _modelRotation = particle.getModelRotation(); + _modelScale = particle.getModelScale(); _id = particle.getID(); _idSet = true; @@ -1174,6 +1422,10 @@ void ParticleProperties::copyFromParticle(const Particle& particle) { _scriptChanged = false; _inHandChanged = false; _shouldDieChanged = false; + _modelURLChanged = false; + _modelTranslationChanged = false; + _modelRotationChanged = false; + _modelScaleChanged = false; _defaultSettings = false; } diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index 3e45cfb2b2..456eb7ef4f 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -32,16 +32,20 @@ const uint32_t NEW_PARTICLE = 0xFFFFFFFF; const uint32_t UNKNOWN_TOKEN = 0xFFFFFFFF; const uint32_t UNKNOWN_PARTICLE_ID = 0xFFFFFFFF; -const uint16_t PACKET_CONTAINS_RADIUS = 1; -const uint16_t PACKET_CONTAINS_POSITION = 2; -const uint16_t PACKET_CONTAINS_COLOR = 4; -const uint16_t PACKET_CONTAINS_VELOCITY = 8; -const uint16_t PACKET_CONTAINS_GRAVITY = 16; -const uint16_t PACKET_CONTAINS_DAMPING = 32; -const uint16_t PACKET_CONTAINS_LIFETIME = 64; -const uint16_t PACKET_CONTAINS_INHAND = 128; -const uint16_t PACKET_CONTAINS_SCRIPT = 256; -const uint16_t PACKET_CONTAINS_SHOULDDIE = 512; +const uint16_t CONTAINS_RADIUS = 1; +const uint16_t CONTAINS_POSITION = 2; +const uint16_t CONTAINS_COLOR = 4; +const uint16_t CONTAINS_VELOCITY = 8; +const uint16_t CONTAINS_GRAVITY = 16; +const uint16_t CONTAINS_DAMPING = 32; +const uint16_t CONTAINS_LIFETIME = 64; +const uint16_t CONTAINS_INHAND = 128; +const uint16_t CONTAINS_SCRIPT = 256; +const uint16_t CONTAINS_SHOULDDIE = 512; +const uint16_t CONTAINS_MODEL_URL = 1024; +const uint16_t CONTAINS_MODEL_TRANSLATION = 1024; +const uint16_t CONTAINS_MODEL_ROTATION = 2048; +const uint16_t CONTAINS_MODEL_SCALE = 4096; const float DEFAULT_LIFETIME = 10.0f; // particles live for 10 seconds by default const float DEFAULT_DAMPING = 0.99f; @@ -49,9 +53,13 @@ const float DEFAULT_RADIUS = 0.1f / TREE_SCALE; const float MINIMUM_PARTICLE_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container const glm::vec3 DEFAULT_GRAVITY(0, (-9.8f / TREE_SCALE), 0); const QString DEFAULT_SCRIPT(""); +const glm::vec3 DEFAULT_MODEL_TRANSLATION(0, 0, 0); +const glm::quat DEFAULT_MODEL_ROTATION(0, 0, 0, 0); +const float DEFAULT_MODEL_SCALE = 1.0f; const bool IN_HAND = true; // it's in a hand const bool NOT_IN_HAND = !IN_HAND; // it's not in a hand + /// A collection of properties of a particle used in the scripting API. Translates between the actual properties of a particle /// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of /// particle properties via JavaScript hashes/QScriptValues @@ -76,6 +84,10 @@ public: const QString& getScript() const { return _script; } bool getInHand() const { return _inHand; } bool getShouldDie() const { return _shouldDie; } + const QString& getModelURL() const { return _modelURL; } + const glm::vec3& getModelTranslation() const { return _modelTranslation; } + const glm::quat& getModelRotation() const { return _modelRotation; } + float getModelScale() const { return _modelScale; } uint64_t getLastEdited() const { return _lastEdited; } uint16_t getChangedBits() const; @@ -94,7 +106,14 @@ public: void setDamping(float value) { _damping = value; _dampingChanged = true; } void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; } void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; } - void setScript(QString updateScript) { _script = updateScript; _scriptChanged = true; } + void setScript(const QString& updateScript) { _script = updateScript; _scriptChanged = true; } + + // model related properties + void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; } + void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; + _modelTranslationChanged = true; } + void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; } + void setModelScale(float scale) { _modelScale = scale; _modelScaleChanged = true; } /// used by ParticleScriptingInterface to return ParticleProperties for unknown particles void setIsUnknownID() { _id = UNKNOWN_PARTICLE_ID; _idSet = true; } @@ -110,10 +129,15 @@ private: QString _script; bool _inHand; bool _shouldDie; + QString _modelURL; + glm::vec3 _modelTranslation; + glm::quat _modelRotation; + float _modelScale; uint32_t _id; bool _idSet; uint64_t _lastEdited; + bool _positionChanged; bool _colorChanged; bool _radiusChanged; @@ -124,6 +148,10 @@ private: bool _scriptChanged; bool _inHandChanged; bool _shouldDieChanged; + bool _modelURLChanged; + bool _modelTranslationChanged; + bool _modelRotationChanged; + bool _modelScaleChanged; bool _defaultSettings; }; Q_DECLARE_METATYPE(ParticleProperties); @@ -162,12 +190,9 @@ class Particle { public: Particle(); - - /// all position, velocity, gravity, radius units are in domain units (0.0 to 1.0) - Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, - glm::vec3 gravity = DEFAULT_GRAVITY, float damping = DEFAULT_DAMPING, float lifetime = DEFAULT_LIFETIME, - bool inHand = NOT_IN_HAND, QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE); + Particle(const ParticleID& particleID, const ParticleProperties& properties); + /// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD_OR_EDIT edit data buffer static Particle fromEditPacket(unsigned char* data, int length, int& processedBytes, ParticleTree* tree, bool& valid); @@ -195,6 +220,14 @@ public: bool getInHand() const { return _inHand; } float getDamping() const { return _damping; } float getLifetime() const { return _lifetime; } + + // model related properties + bool hasModel() const { return !_modelURL.isEmpty(); } + const QString& getModelURL() const { return _modelURL; } + const glm::vec3& getModelTranslation() const { return _modelTranslation; } + const glm::quat& getModelRotation() const { return _modelRotation; } + float getModelScale() const { return _modelScale; } + ParticleProperties getProperties() const; /// The last updated/simulated time of this particle from the time perspective of the authoritative server/source @@ -202,11 +235,13 @@ public: /// The last edited time of this particle from the time perspective of the authoritative server/source uint64_t getLastEdited() const { return _lastEdited; } + void setLastEdited(uint64_t lastEdited) { _lastEdited = lastEdited; } /// lifetime of the particle in seconds float getAge() const { return static_cast(usecTimestampNow() - _created) / static_cast(USECS_PER_SECOND); } float getEditedAgo() const { return static_cast(usecTimestampNow() - _lastEdited) / static_cast(USECS_PER_SECOND); } uint32_t getID() const { return _id; } + void setID(uint32_t id) { _id = id; } bool getShouldDie() const { return _shouldDie; } QString getScript() const { return _script; } uint32_t getCreatorTokenID() const { return _creatorTokenID; } @@ -235,12 +270,18 @@ public: void setLifetime(float value) { _lifetime = value; } void setScript(QString updateScript) { _script = updateScript; } void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; } + + // model related properties + void setModelURL(const QString& url) { _modelURL = url; } + void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; } + void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; } + void setModelScale(float scale) { _modelScale = scale; } + void setProperties(const ParticleProperties& properties); bool appendParticleData(OctreePacketData* packetData) const; int readParticleDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); static int expectedBytes(); - static int expectedEditMessageBytes(); static bool encodeParticleEditMessageDetails(PACKET_TYPE command, ParticleID id, const ParticleProperties& details, unsigned char* bufferOut, int sizeIn, int& sizeOut); @@ -299,6 +340,12 @@ protected: QString _script; bool _inHand; + // model related items + QString _modelURL; + glm::vec3 _modelTranslation; + glm::quat _modelRotation; + float _modelScale; + uint32_t _creatorTokenID; bool _newlyCreated; diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index 3e224be48b..15caff2ccf 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -81,8 +81,8 @@ public: bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraData) { FindAndUpdateParticleArgs* args = static_cast(extraData); ParticleTreeElement* particleTreeElement = static_cast(element); - if (particleTreeElement->containsParticle(args->searchParticle)) { - particleTreeElement->updateParticle(args->searchParticle); + // Note: updateParticle() will only operate on correctly found particles + if (particleTreeElement->updateParticle(args->searchParticle)) { args->found = true; return false; // stop searching } @@ -106,6 +106,119 @@ void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) { _isDirty = true; } +class FindAndUpdateParticleWithIDandPropertiesArgs { +public: + const ParticleID& particleID; + const ParticleProperties& properties; + bool found; +}; + +bool ParticleTree::findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData) { + FindAndUpdateParticleWithIDandPropertiesArgs* args = static_cast(extraData); + ParticleTreeElement* particleTreeElement = static_cast(element); + // Note: updateParticle() will only operate on correctly found particles + if (particleTreeElement->updateParticle(args->particleID, args->properties)) { + args->found = true; + return false; // stop searching + } + + // if we've found our particle stop searching + if (args->found) { + return false; + } + + return true; +} + +void ParticleTree::updateParticle(const ParticleID& particleID, const ParticleProperties& properties) { + // First, look for the existing particle in the tree.. + FindAndUpdateParticleWithIDandPropertiesArgs args = { particleID, properties, false }; + recurseTreeWithOperation(findAndUpdateWithIDandPropertiesOperation, &args); + // if we found it in the tree, then mark the tree as dirty + if (args.found) { + _isDirty = true; + } +} + +void ParticleTree::addParticle(const ParticleID& particleID, const ParticleProperties& properties) { + // This only operates on locally created particles + if (particleID.isKnownID) { + return; // not allowed + } + Particle particle(particleID, properties); + glm::vec3 position = particle.getPosition(); + float size = std::max(MINIMUM_PARTICLE_ELEMENT_SIZE, particle.getRadius()); + ParticleTreeElement* element = (ParticleTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); + + element->storeParticle(particle); + + _isDirty = true; +} + +void ParticleTree::deleteParticle(const ParticleID& particleID) { + if (particleID.isKnownID) { + FindAndDeleteParticlesArgs args; + args._idsToDelete.push_back(particleID.id); + recurseTreeWithOperation(findAndDeleteOperation, &args); + } +} + +// scans the tree and handles mapping locally created particles 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 particle from the viewing +// operation. +bool ParticleTree::findAndUpdateParticleIDOperation(OctreeElement* element, void* extraData) { + bool keepSearching = true; + + FindAndUpdateParticleIDArgs* args = static_cast(extraData); + ParticleTreeElement* particleTreeElement = static_cast(element); + + // Note: updateParticleID() will only operate on correctly found particles + particleTreeElement->updateParticleID(args); + + // if we've found and replaced both the creatorTokenID and the viewedParticle, then we + // can stop looking, otherwise we will keep looking + if (args->creatorTokenFound && args->viewedParticleFound) { + keepSearching = false; + } + + return keepSearching; +} + +void ParticleTree::handleAddParticleResponse(unsigned char* packetData , int packetLength) { + unsigned char* dataAt = packetData; + int numBytesPacketHeader = numBytesForPacketHeader(packetData); + dataAt += numBytesPacketHeader; + + uint32_t creatorTokenID; + memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); + dataAt += sizeof(creatorTokenID); + + uint32_t particleID; + memcpy(&particleID, dataAt, sizeof(particleID)); + dataAt += sizeof(particleID); + + // update particles in our tree + bool assumeParticleFound = !getIsViewing(); // if we're not a viewing tree, then we don't have to find the actual particle + FindAndUpdateParticleIDArgs args = { + particleID, + creatorTokenID, + false, + assumeParticleFound, + getIsViewing() + }; + + const bool wantDebug = false; + if (wantDebug) { + qDebug() << "looking for creatorTokenID=" << creatorTokenID << " particleID=" << particleID + << " getIsViewing()=" << getIsViewing(); + } + lockForWrite(); + recurseTreeWithOperation(findAndUpdateParticleIDOperation, &args); + unlock(); +} + + class FindNearPointArgs { public: glm::vec3 position; @@ -413,11 +526,9 @@ bool ParticleTree::encodeParticlesDeletedSince(uint64_t& sinceTime, unsigned cha while (iterator != _recentlyDeletedParticleIDs.constEnd()) { QList values = _recentlyDeletedParticleIDs.values(iterator.key()); for (int valueItem = 0; valueItem < values.size(); ++valueItem) { - //qDebug() << "considering... " << iterator.key() << ": " << values.at(valueItem); // if the timestamp is more recent then out last sent time, include it if (iterator.key() > sinceTime) { - //qDebug() << "including... " << iterator.key() << ": " << values.at(valueItem); uint32_t particleID = values.at(valueItem); memcpy(copyAt, &particleID, sizeof(particleID)); copyAt += sizeof(particleID); @@ -455,16 +566,14 @@ bool ParticleTree::encodeParticlesDeletedSince(uint64_t& sinceTime, unsigned cha // called by the server when it knows all nodes have been sent deleted packets void ParticleTree::forgetParticlesDeletedBefore(uint64_t sinceTime) { - //qDebug() << "forgetParticlesDeletedBefore()"; QSet keysToRemove; _recentlyDeletedParticlesLock.lockForWrite(); QMultiMap::iterator iterator = _recentlyDeletedParticleIDs.begin(); + // First find all the keys in the map that are older and need to be deleted while (iterator != _recentlyDeletedParticleIDs.end()) { - //qDebug() << "considering... time/key:" << iterator.key(); if (iterator.key() <= sinceTime) { - //qDebug() << "YES older... time/key:" << iterator.key(); keysToRemove << iterator.key(); } ++iterator; @@ -472,18 +581,15 @@ void ParticleTree::forgetParticlesDeletedBefore(uint64_t sinceTime) { // Now run through the keysToRemove and remove them foreach (uint64_t value, keysToRemove) { - //qDebug() << "removing the key, _recentlyDeletedParticleIDs.remove(value); time/key:" << value; _recentlyDeletedParticleIDs.remove(value); } _recentlyDeletedParticlesLock.unlock(); - //qDebug() << "DONE forgetParticlesDeletedBefore()"; } void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode) { - //qDebug() << "ParticleTree::processEraseMessage()..."; const unsigned char* packetData = (const unsigned char*)dataByteArray.constData(); const unsigned char* dataAt = packetData; @@ -498,14 +604,11 @@ void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const Hi dataAt += sizeof(numberOfIds); processedBytes += sizeof(numberOfIds); - //qDebug() << "got erase message for numberOfIds:" << numberOfIds; - if (numberOfIds > 0) { FindAndDeleteParticlesArgs args; for (size_t i = 0; i < numberOfIds; i++) { if (processedBytes + sizeof(uint32_t) > packetLength) { - //qDebug() << "bailing?? processedBytes:" << processedBytes << " packetLength:" << packetLength; break; // bail to prevent buffer overflow } @@ -514,12 +617,10 @@ void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const Hi dataAt += sizeof(particleID); processedBytes += sizeof(particleID); - //qDebug() << "got erase message for particleID:" << particleID; args._idsToDelete.push_back(particleID); } // calling recurse to actually delete the particles - //qDebug() << "calling recurse to actually delete the particles"; recurseTreeWithOperation(findAndDeleteOperation, &args); } } diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index a19bad9892..d17be3d33a 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -40,6 +40,9 @@ public: virtual void update(); void storeParticle(const Particle& particle, Node* senderNode = NULL); + void updateParticle(const ParticleID& particleID, const ParticleProperties& properties); + void addParticle(const ParticleID& particleID, const ParticleProperties& properties); + void deleteParticle(const ParticleID& particleID); const Particle* findClosestParticle(glm::vec3 position, float targetRadius); const Particle* findParticleByID(uint32_t id, bool alreadyLocked = false); @@ -65,16 +68,19 @@ public: void forgetParticlesDeletedBefore(uint64_t sinceTime); void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode); + void handleAddParticleResponse(unsigned char* packetData , int packetLength); 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 findAndUpdateParticleIDOperation(OctreeElement* element, void* extraData); void notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode); diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 72d3e1b7b0..48e619da59 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -125,18 +125,6 @@ bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float r return false; } -bool ParticleTreeElement::containsParticle(const Particle& particle) const { - // TODO: remove this method and force callers to use getParticleWithID() instead - uint16_t numberOfParticles = _particles->size(); - uint32_t particleID = particle.getID(); - for (uint16_t i = 0; i < numberOfParticles; i++) { - if ((*_particles)[i].getID() == particleID) { - return true; - } - } - return false; -} - bool ParticleTreeElement::updateParticle(const Particle& particle) { // NOTE: this method must first lookup the particle by ID, hence it is O(N) // and "particle is not found" is worst-case (full N) but maybe we don't care? @@ -172,6 +160,63 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { return false; } +bool ParticleTreeElement::updateParticle(const ParticleID& particleID, const ParticleProperties& properties) { + uint16_t numberOfParticles = _particles->size(); + for (uint16_t i = 0; i < numberOfParticles; i++) { + // note: unlike storeParticle() 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 + Particle& thisParticle = (*_particles)[i]; + + bool found = false; + if (particleID.isKnownID) { + found = thisParticle.getID() == particleID.id; + } else { + found = thisParticle.getCreatorTokenID() == particleID.creatorTokenID; + } + if (found) { + thisParticle.setProperties(properties); + + const bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - thisParticle.getLastEdited(); + + qDebug() << "ParticleTreeElement::updateParticle() AFTER update... edited AGO=" << elapsed << + "now=" << now << " thisParticle.getLastEdited()=" << thisParticle.getLastEdited(); + } + return true; + } + } + return false; +} + +void ParticleTreeElement::updateParticleID(FindAndUpdateParticleIDArgs* args) { + uint16_t numberOfParticles = _particles->size(); + for (uint16_t i = 0; i < numberOfParticles; i++) { + Particle& thisParticle = (*_particles)[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 (thisParticle.getCreatorTokenID() == args->creatorTokenID) { + thisParticle.setID(args->particleID); + args->creatorTokenFound = true; + } + } + + // if we're in an isViewing tree, we also need to look for an kill any viewed particles + if (!args->viewedParticleFound && args->isViewing) { + if (thisParticle.getCreatorTokenID() == UNKNOWN_TOKEN && thisParticle.getID() == args->particleID) { + _particles->removeAt(i); // remove the particle at this index + numberOfParticles--; // this means we have 1 fewer particle in this list + i--; // and we actually want to back up i as well. + args->viewedParticleFound = true; + } + } + } +} + + + const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) const { const Particle* closestParticle = NULL; float closestParticleDistance = FLT_MAX; diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 1d30bfbdfd..59a03f7b41 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -26,6 +26,17 @@ public: QList _movingParticles; }; +class FindAndUpdateParticleIDArgs { +public: + uint32_t particleID; + uint32_t creatorTokenID; + bool creatorTokenFound; + bool viewedParticleFound; + bool isViewing; +}; + + + class ParticleTreeElement : public OctreeElement { friend class ParticleTree; // to allow createElement to new us... @@ -86,8 +97,10 @@ public: void update(ParticleTreeUpdateArgs& args); void setTree(ParticleTree* tree) { _myTree = tree; } - bool containsParticle(const Particle& particle) const; bool updateParticle(const Particle& particle); + bool updateParticle(const ParticleID& particleID, const ParticleProperties& properties); + void updateParticleID(FindAndUpdateParticleIDArgs* args); + const Particle* getClosestParticle(glm::vec3 position) const; /// finds all particles that touch a sphere diff --git a/libraries/particles/src/ParticlesScriptingInterface.cpp b/libraries/particles/src/ParticlesScriptingInterface.cpp index 2bbade964d..0ef8f82b00 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.cpp +++ b/libraries/particles/src/ParticlesScriptingInterface.cpp @@ -31,6 +31,13 @@ ParticleID ParticlesScriptingInterface::addParticle(const ParticleProperties& pr // queue the packet queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, id, properties); + // If we have a local particle tree set, then also update it. + if (_particleTree) { + _particleTree->lockForWrite(); + _particleTree->addParticle(id, properties); + _particleTree->unlock(); + } + return id; } @@ -57,8 +64,10 @@ ParticleProperties ParticlesScriptingInterface::getParticleProperties(ParticleID return results; } if (_particleTree) { + _particleTree->lockForRead(); const Particle* particle = _particleTree->findParticleByID(identity.id); results.copyFromParticle(*particle); + _particleTree->unlock(); } return results; @@ -68,38 +77,26 @@ ParticleProperties ParticlesScriptingInterface::getParticleProperties(ParticleID ParticleID ParticlesScriptingInterface::editParticle(ParticleID particleID, const ParticleProperties& properties) { uint32_t actualID = particleID.id; + + // if the particle is unknown, attempt to look it up if (!particleID.isKnownID) { actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID); - if (actualID == UNKNOWN_PARTICLE_ID) { - return particleID; // bailing early - } } - particleID.id = actualID; - particleID.isKnownID = true; - //qDebug() << "ParticlesScriptingInterface::editParticle()... FOUND IT!!! actualID=" << actualID; - - bool wantDebugging = false; - if (wantDebugging) { - uint16_t containsBits = properties.getChangedBits(); - qDebug() << "ParticlesScriptingInterface::editParticle()... containsBits=" << containsBits; - if ((containsBits & PACKET_CONTAINS_POSITION) == PACKET_CONTAINS_POSITION) { - qDebug() << "ParticlesScriptingInterface::editParticle()... properties.getPositon()=" - << properties.getPosition().x << ", " - << properties.getPosition().y << ", " - << properties.getPosition().z << "..."; - } - if ((containsBits & PACKET_CONTAINS_VELOCITY) == PACKET_CONTAINS_VELOCITY) { - qDebug() << "ParticlesScriptingInterface::editParticle()... properties.getVelocity()=" - << properties.getVelocity().x << ", " - << properties.getVelocity().y << ", " - << properties.getVelocity().z << "..."; - } - if ((containsBits & PACKET_CONTAINS_INHAND) == PACKET_CONTAINS_INHAND) { - qDebug() << "ParticlesScriptingInterface::editParticle()... properties.getInHand()=" << properties.getInHand(); - } + // if at this point, we know the id, send the update to the particle server + if (actualID != UNKNOWN_PARTICLE_ID) { + particleID.id = actualID; + particleID.isKnownID = true; + queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties); + } + + // If we have a local particle 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 particles just with creatorTokenID + if (_particleTree) { + _particleTree->lockForWrite(); + _particleTree->updateParticle(particleID, properties); + _particleTree->unlock(); } - queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties); return particleID; } @@ -130,14 +127,22 @@ void ParticlesScriptingInterface::deleteParticle(ParticleID particleID) { //qDebug() << "ParticlesScriptingInterface::deleteParticle(), queueParticleMessage......"; queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties); + + // If we have a local particle tree set, then also update it. + if (_particleTree) { + _particleTree->lockForWrite(); + _particleTree->deleteParticle(particleID); + _particleTree->unlock(); + } } ParticleID ParticlesScriptingInterface::findClosestParticle(const glm::vec3& center, float radius) const { ParticleID result(UNKNOWN_PARTICLE_ID, UNKNOWN_TOKEN, false); if (_particleTree) { + _particleTree->lockForRead(); const Particle* closestParticle = _particleTree->findClosestParticle(center/(float)TREE_SCALE, radius/(float)TREE_SCALE); - + _particleTree->unlock(); if (closestParticle) { result.id = closestParticle->getID(); result.isKnownID = true; @@ -150,8 +155,10 @@ ParticleID ParticlesScriptingInterface::findClosestParticle(const glm::vec3& cen QVector ParticlesScriptingInterface::findParticles(const glm::vec3& center, float radius) const { QVector result; if (_particleTree) { + _particleTree->lockForRead(); QVector particles; _particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, particles); + _particleTree->unlock(); foreach (const Particle* particle, particles) { ParticleID thisParticleID(particle->getID(), UNKNOWN_TOKEN, true); diff --git a/libraries/script-engine/src/AbstractControllerScriptingInterface.h b/libraries/script-engine/src/AbstractControllerScriptingInterface.h index 5c791af0a4..4fad5f6edc 100644 --- a/libraries/script-engine/src/AbstractControllerScriptingInterface.h +++ b/libraries/script-engine/src/AbstractControllerScriptingInterface.h @@ -10,8 +10,12 @@ #define __hifi__AbstractControllerScriptingInterface__ #include + #include +#include "EventTypes.h" + + /// handles scripting of input controller commands from JS class AbstractControllerScriptingInterface : public QObject { Q_OBJECT @@ -33,6 +37,34 @@ public slots: virtual glm::vec3 getSpatialControlPosition(int controlIndex) const = 0; virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const = 0; virtual glm::vec3 getSpatialControlNormal(int controlIndex) const = 0; + + virtual void captureKeyEvents(const KeyEvent& event) = 0; + virtual void releaseKeyEvents(const KeyEvent& event) = 0; + + virtual void captureMouseEvents() = 0; + virtual void releaseMouseEvents() = 0; + + virtual void captureTouchEvents() = 0; + virtual void releaseTouchEvents() = 0; + + virtual void captureWheelEvents() = 0; + virtual void releaseWheelEvents() = 0; + + +signals: + void keyPressEvent(const KeyEvent& event); + void keyReleaseEvent(const KeyEvent& event); + + void mouseMoveEvent(const MouseEvent& event); + void mousePressEvent(const MouseEvent& event); + void mouseReleaseEvent(const MouseEvent& event); + + void touchBeginEvent(const TouchEvent& event); + void touchEndEvent(const TouchEvent& event); + void touchUpdateEvent(const TouchEvent& event); + + void wheelEvent(const WheelEvent& event); + }; #endif /* defined(__hifi__AbstractControllerScriptingInterface__) */ diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp new file mode 100644 index 0000000000..5c4ab7f2a7 --- /dev/null +++ b/libraries/script-engine/src/EventTypes.cpp @@ -0,0 +1,111 @@ +// +// EventTypes.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 1/28/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// Used to register meta-types with Qt for very various event types so that they can be exposed to our +// scripting engine + +#include "EventTypes.h" + + +KeyEvent::KeyEvent() { + key = 0; + isShifted = false; + isMeta = false; + isValid = false; +} + + +KeyEvent::KeyEvent(const QKeyEvent& event) { + key = event.key(); + isShifted = event.modifiers().testFlag(Qt::ShiftModifier); + isMeta = event.modifiers().testFlag(Qt::ControlModifier); + isValid = true; +} + +MouseEvent::MouseEvent(const QMouseEvent& event) { + x = event.x(); + y = event.y(); +} + +TouchEvent::TouchEvent(const QTouchEvent& event) { + // convert the touch points into an average + const QList& tPoints = event.touchPoints(); + float touchAvgX = 0.0f; + float touchAvgY = 0.0f; + int numTouches = tPoints.count(); + if (numTouches > 1) { + for (int i = 0; i < numTouches; ++i) { + touchAvgX += tPoints[i].pos().x(); + touchAvgY += tPoints[i].pos().y(); + } + touchAvgX /= (float)(numTouches); + touchAvgY /= (float)(numTouches); + } + x = touchAvgX; + y = touchAvgY; +} + +WheelEvent::WheelEvent(const QWheelEvent& event) { + x = event.x(); + y = event.y(); +} + + +void registerEventTypes(QScriptEngine* engine) { + qScriptRegisterMetaType(engine, keyEventToScriptValue, keyEventFromScriptValue); + qScriptRegisterMetaType(engine, mouseEventToScriptValue, mouseEventFromScriptValue); + qScriptRegisterMetaType(engine, touchEventToScriptValue, touchEventFromScriptValue); + qScriptRegisterMetaType(engine, wheelEventToScriptValue, wheelEventFromScriptValue); +} + +QScriptValue keyEventToScriptValue(QScriptEngine* engine, const KeyEvent& event) { + QScriptValue obj = engine->newObject(); + obj.setProperty("key", event.key); + obj.setProperty("isShifted", event.isShifted); + obj.setProperty("isMeta", event.isMeta); + return obj; +} + +void keyEventFromScriptValue(const QScriptValue &object, KeyEvent& event) { + event.key = object.property("key").toVariant().toInt(); + event.isShifted = object.property("isShifted").toVariant().toBool(); + event.isMeta = object.property("isMeta").toVariant().toBool(); + event.isValid = object.property("key").isValid(); +} + +QScriptValue mouseEventToScriptValue(QScriptEngine* engine, const MouseEvent& event) { + QScriptValue obj = engine->newObject(); + obj.setProperty("x", event.x); + obj.setProperty("y", event.y); + return obj; +} + +void mouseEventFromScriptValue(const QScriptValue &object, MouseEvent& event) { + // nothing for now... +} + +QScriptValue touchEventToScriptValue(QScriptEngine* engine, const TouchEvent& event) { + QScriptValue obj = engine->newObject(); + obj.setProperty("x", event.x); + obj.setProperty("y", event.y); + return obj; +} + +void touchEventFromScriptValue(const QScriptValue &object, TouchEvent& event) { + // nothing for now... +} + +QScriptValue wheelEventToScriptValue(QScriptEngine* engine, const WheelEvent& event) { + QScriptValue obj = engine->newObject(); + obj.setProperty("x", event.x); + obj.setProperty("y", event.y); + return obj; +} + +void wheelEventFromScriptValue(const QScriptValue &object, WheelEvent& event) { + // nothing for now... +} diff --git a/libraries/script-engine/src/EventTypes.h b/libraries/script-engine/src/EventTypes.h new file mode 100644 index 0000000000..c3764b2619 --- /dev/null +++ b/libraries/script-engine/src/EventTypes.h @@ -0,0 +1,79 @@ +// +// EventTypes.h +// hifi +// +// Created by Brad Hefta-Gaub on 1/28/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi_EventTypes_h__ +#define __hifi_EventTypes_h__ + +#include + +#include + +#include +#include +#include +#include + + +class KeyEvent { +public: + KeyEvent(); + KeyEvent(const QKeyEvent& event); + inline bool operator==(const KeyEvent& other) const { + return other.key == key && other.isShifted == isShifted && other.isMeta == isMeta; } + int key; + bool isShifted; + bool isMeta; + bool isValid; +}; + + +class MouseEvent { +public: + MouseEvent() : x(0), y(0) { }; + MouseEvent(const QMouseEvent& event); + int x; + int y; +}; + +class TouchEvent { +public: + TouchEvent() : x(0), y(0) { }; + TouchEvent(const QTouchEvent& event); + float x; + float y; +}; + +class WheelEvent { +public: + WheelEvent() : x(0), y(0) { }; + WheelEvent(const QWheelEvent& event); + int x; + int y; +}; + + +Q_DECLARE_METATYPE(KeyEvent) +Q_DECLARE_METATYPE(MouseEvent) +Q_DECLARE_METATYPE(TouchEvent) +Q_DECLARE_METATYPE(WheelEvent) + +void registerEventTypes(QScriptEngine* engine); + +QScriptValue keyEventToScriptValue(QScriptEngine* engine, const KeyEvent& event); +void keyEventFromScriptValue(const QScriptValue &object, KeyEvent& event); + +QScriptValue mouseEventToScriptValue(QScriptEngine* engine, const MouseEvent& event); +void mouseEventFromScriptValue(const QScriptValue &object, MouseEvent& event); + +QScriptValue touchEventToScriptValue(QScriptEngine* engine, const TouchEvent& event); +void touchEventFromScriptValue(const QScriptValue &object, TouchEvent& event); + +QScriptValue wheelEventToScriptValue(QScriptEngine* engine, const WheelEvent& event); +void wheelEventFromScriptValue(const QScriptValue &object, WheelEvent& event); + +#endif // __hifi_EventTypes_h__ diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp new file mode 100644 index 0000000000..12900c29c7 --- /dev/null +++ b/libraries/script-engine/src/Quat.cpp @@ -0,0 +1,20 @@ +// +// Quat.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 1/29/14 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Scriptable Quaternion class library. +// +// + +#include "Quat.h" + +glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) { + return q1 * q2; +} + +glm::quat Quat::fromVec3(const glm::vec3& vec3) { + return glm::quat(vec3); +} diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h new file mode 100644 index 0000000000..6bcd121808 --- /dev/null +++ b/libraries/script-engine/src/Quat.h @@ -0,0 +1,29 @@ +// +// Quat.h +// hifi +// +// Created by Brad Hefta-Gaub on 1/29/14 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Scriptable Quaternion class library. +// +// + +#ifndef __hifi__Quat__ +#define __hifi__Quat__ + +#include +#include + +/// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API +class Quat : public QObject { + Q_OBJECT + +public slots: + glm::quat multiply(const glm::quat& q1, const glm::quat& q2); + glm::quat fromVec3(const glm::vec3& vec3); +}; + + + +#endif /* defined(__hifi__Quat__) */ diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 1bf8dbf696..28cb49f6f7 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -116,19 +116,12 @@ void ScriptEngine::init() { // register meta-type for glm::vec3 conversions registerMetaTypes(&_engine); + + registerEventTypes(&_engine); qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); - QScriptValue agentValue = _engine.newQObject(this); - _engine.globalObject().setProperty("Agent", agentValue); - - QScriptValue voxelScripterValue = _engine.newQObject(&_voxelsScriptingInterface); - _engine.globalObject().setProperty("Voxels", voxelScripterValue); - - QScriptValue particleScripterValue = _engine.newQObject(&_particlesScriptingInterface); - _engine.globalObject().setProperty("Particles", particleScripterValue); - QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor); QScriptValue soundMetaObject = _engine.newQMetaObject(&Sound::staticMetaObject, soundConstructorValue); _engine.globalObject().setProperty("Sound", soundMetaObject); @@ -136,15 +129,14 @@ void ScriptEngine::init() { QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue); - QScriptValue audioScriptingInterfaceValue = _engine.newQObject(&_audioScriptingInterface); - _engine.globalObject().setProperty("Audio", audioScriptingInterfaceValue); - + registerGlobalObject("Agent", this); + registerGlobalObject("Audio", &_audioScriptingInterface); + registerGlobalObject("Controller", _controllerScriptingInterface); registerGlobalObject("Data", &_dataServerScriptingInterface); + registerGlobalObject("Particles", &_particlesScriptingInterface); + registerGlobalObject("Quat", &_quatLibrary); - if (_controllerScriptingInterface) { - QScriptValue controllerScripterValue = _engine.newQObject(_controllerScriptingInterface); - _engine.globalObject().setProperty("Controller", controllerScripterValue); - } + registerGlobalObject("Voxels", &_voxelsScriptingInterface); QScriptValue treeScaleValue = _engine.newVariant(QVariant(TREE_SCALE)); _engine.globalObject().setProperty("TREE_SCALE", treeScaleValue); @@ -157,8 +149,10 @@ void ScriptEngine::init() { } void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { - QScriptValue value = _engine.newQObject(object); - _engine.globalObject().setProperty(name, value); + if (object) { + QScriptValue value = _engine.newQObject(object); + _engine.globalObject().setProperty(name, value); + } } void ScriptEngine::evaluate() { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index b36e2425fe..ef55e14109 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -25,6 +25,7 @@ class ParticlesScriptingInterface; #include "AbstractControllerScriptingInterface.h" #include "DataServerScriptingInterface.h" +#include "Quat.h" const QString NO_SCRIPT(""); @@ -94,6 +95,7 @@ private: QString _fileNameString; AbstractMenuInterface* _menu; static int _scriptNumber; + Quat _quatLibrary; }; #endif /* defined(__hifi__ScriptEngine__) */ diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 24a34f618e..0f81110d01 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -45,10 +45,10 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) { return 1; case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: - return 4; + return 5; case PACKET_TYPE_PARTICLE_DATA: - return 8; + return 9; case PACKET_TYPE_PING_REPLY: return 1; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 9d2eec6b40..4b0f2c403e 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -13,6 +13,7 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, vec3toScriptValue, vec3FromScriptValue); qScriptRegisterMetaType(engine, vec2toScriptValue, vec2FromScriptValue); + qScriptRegisterMetaType(engine, quatToScriptValue, quatFromScriptValue); qScriptRegisterMetaType(engine, xColorToScriptValue, xColorFromScriptValue); } @@ -42,6 +43,21 @@ void vec2FromScriptValue(const QScriptValue &object, glm::vec2 &vec2) { vec2.y = object.property("y").toVariant().toFloat(); } +QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat) { + QScriptValue obj = engine->newObject(); + obj.setProperty("x", quat.x); + obj.setProperty("y", quat.y); + obj.setProperty("z", quat.z); + obj.setProperty("w", quat.w); + return obj; +} + +void quatFromScriptValue(const QScriptValue &object, glm::quat& quat) { + quat.x = object.property("x").toVariant().toFloat(); + quat.y = object.property("y").toVariant().toFloat(); + quat.z = object.property("z").toVariant().toFloat(); + quat.w = object.property("w").toVariant().toFloat(); +} QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) { QScriptValue obj = engine->newObject(); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 4d91a21be7..de667c9ed8 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -19,6 +19,7 @@ Q_DECLARE_METATYPE(glm::vec3) Q_DECLARE_METATYPE(glm::vec2) +Q_DECLARE_METATYPE(glm::quat) Q_DECLARE_METATYPE(xColor) void registerMetaTypes(QScriptEngine* engine); @@ -29,6 +30,9 @@ void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3); QScriptValue vec2toScriptValue(QScriptEngine* engine, const glm::vec2 &vec2); void vec2FromScriptValue(const QScriptValue &object, glm::vec2 &vec2); +QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat); +void quatFromScriptValue(const QScriptValue &object, glm::quat& quat); + QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color); void xColorFromScriptValue(const QScriptValue &object, xColor& color); diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 9433fe1236..54a4291c25 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -607,7 +607,7 @@ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput return sizeof(quatParts); } -int unpackOrientationQuatFromBytes(unsigned char* buffer, glm::quat& quatOutput) { +int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput) { uint16_t quatParts[4]; memcpy(&quatParts, buffer, sizeof(quatParts)); @@ -715,3 +715,31 @@ void debug::checkDeadBeef(void* memoryVoid, int size) { assert(memcmp(memoryAt, DEADBEEF, std::min(size, DEADBEEF_SIZE)) != 0); } +// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's +// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde, +// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) +glm::vec3 safeEulerAngles(const glm::quat& q) { + float sy = 2.0f * (q.y * q.w - q.x * q.z); + if (sy < 1.0f - EPSILON) { + if (sy > -1.0f + EPSILON) { + return glm::degrees(glm::vec3( + atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)), + asinf(sy), + atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z)))); + + } else { + // not a unique solution; x + z = atan2(-m21, m11) + return glm::degrees(glm::vec3( + 0.0f, + PIf * -0.5f, + atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z)))); + } + } else { + // not a unique solution; x - z = atan2(-m21, m11) + return glm::degrees(glm::vec3( + 0.0f, + PIf * 0.5f, + -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z)))); + } +} + diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index a77663bbda..399bf40204 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -152,7 +152,7 @@ int unpackFloatAngleFromTwoByte(uint16_t* byteAnglePointer, float* destinationPo // Orientation Quats are known to have 4 normalized components be between -1.0 and 1.0 // this allows us to encode each component in 16bits with great accuracy int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput); -int unpackOrientationQuatFromBytes(unsigned char* buffer, glm::quat& quatOutput); +int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput); // Ratios need the be highly accurate when less than 10, but not very accurate above 10, and they // are never greater than 1000 to 1, this allows us to encode each component in 16bits @@ -172,8 +172,14 @@ int unpackFloatFromByte(unsigned char* buffer, float& value, float scaleBy); int packFloatScalarToSignedTwoByteFixed(unsigned char* buffer, float scalar, int radix); int unpackFloatScalarFromSignedTwoByteFixed(int16_t* byteFixedPointer, float* destinationPointer, int radix); -// A convenience for sending vec3's as fixed-poimt floats +// A convenience for sending vec3's as fixed-point floats int packFloatVec3ToSignedTwoByteFixed(unsigned char* destBuffer, const glm::vec3& srcVector, int radix); int unpackFloatVec3FromSignedTwoByteFixed(unsigned char* sourceBuffer, glm::vec3& destination, int radix); +#ifndef PIf +#define PIf 3.14159265f +#endif + +glm::vec3 safeEulerAngles(const glm::quat& q); + #endif /* defined(__hifi__SharedUtil__) */