Merge branch 'master' of ssh://github.com/highfidelity/hifi into fix-particle-avatar-collisions

Conflicts:
	interface/src/Application.cpp
This commit is contained in:
Andrew Meadows 2014-01-29 11:41:39 -08:00
commit d757f20d22
45 changed files with 1582 additions and 346 deletions

View file

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

View file

@ -15,6 +15,7 @@
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <ParticleTree.h>
#include <ScriptEngine.h>
#include <ThreadedAssignment.h>
@ -32,6 +33,7 @@ signals:
void willSendVisualDataCallback();
private:
ScriptEngine _scriptEngine;
ParticleTree _particleTree;
};
#endif /* defined(__hifi__Agent__) */

View file

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

View file

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

74
examples/lookWithMouse.js Normal file
View file

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

View file

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

View file

@ -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),
@ -156,8 +157,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_swatch(NULL),
_pasteMode(false),
_logger(new FileLogger()),
_persistThread(NULL),
_statsExpanded(false)
_persistThread(NULL)
{
_myAvatar = _avatarManager.getMyAvatar();
@ -257,7 +257,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_window->setCentralWidget(_glWidget);
restoreSizeAndPosition();
loadScripts();
QFontDatabase fontDatabase;
fontDatabase.addApplicationFont("resources/styles/Inconsolata.otf");
@ -287,6 +286,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() {
@ -685,6 +687,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)) {
@ -1096,6 +1106,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);
@ -1158,6 +1177,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);
@ -1204,6 +1231,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();
@ -1271,6 +1306,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();
@ -1287,6 +1329,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<QTouchEvent::TouchPoint>& tPoints = event->touchPoints();
@ -1311,19 +1360,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()) {
@ -2332,10 +2408,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

View file

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

View file

@ -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<int,KeyEvent> _capturedKeys;
};
const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip

View file

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

View file

@ -7,6 +7,8 @@
//
//
#include <glm/gtx/quaternion.hpp>
#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();
}
}
}

View file

@ -20,6 +20,7 @@
#include <OctreeRenderer.h>
#include <ParticleTree.h>
#include <ViewFrustum.h>
#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<QString, Model*> _particleModels;
};
#endif /* defined(__hifi__ParticleTreeRenderer__) */

View file

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

View file

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

View file

@ -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<Avatar*>(_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 {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -43,6 +43,7 @@ Octree::Octree(bool shouldReaverage) :
_shouldReaverage(shouldReaverage),
_stopImport(false) {
_rootNode = NULL;
_isViewing = false;
}
Octree::~Octree() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<float>(usecTimestampNow() - _created) / static_cast<float>(USECS_PER_SECOND); }
float getEditedAgo() const { return static_cast<float>(usecTimestampNow() - _lastEdited) / static_cast<float>(USECS_PER_SECOND); }
uint32_t getID() const { return _id; }
void setID(uint32_t id) { _id = id; }
bool getShouldDie() const { return _shouldDie; }
QString getScript() const { return _script; }
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
@ -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;

View file

@ -81,8 +81,8 @@ public:
bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraData) {
FindAndUpdateParticleArgs* args = static_cast<FindAndUpdateParticleArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(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<FindAndUpdateParticleWithIDandPropertiesArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(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<FindAndUpdateParticleIDArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(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<uint32_t> 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<uint64_t> keysToRemove;
_recentlyDeletedParticlesLock.lockForWrite();
QMultiMap<uint64_t, uint32_t>::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);
}
}

View file

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

View file

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

View file

@ -26,6 +26,17 @@ public:
QList<Particle> _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

View file

@ -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<ParticleID> ParticlesScriptingInterface::findParticles(const glm::vec3& center, float radius) const {
QVector<ParticleID> result;
if (_particleTree) {
_particleTree->lockForRead();
QVector<const Particle*> 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);

View file

@ -10,8 +10,12 @@
#define __hifi__AbstractControllerScriptingInterface__
#include <QtCore/QObject>
#include <glm/glm.hpp>
#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__) */

View file

@ -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<QTouchEvent::TouchPoint>& 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...
}

View file

@ -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 <glm/glm.hpp>
#include <QtScript/QScriptEngine>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QTouchEvent>
#include <QWheelEvent>
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__

View file

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

View file

@ -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 <glm/gtc/quaternion.hpp>
#include <QtCore/QObject>
/// 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__) */

View file

@ -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<QVector<ParticleID> >(&_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<AudioInjectorOptions>();
_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() {

View file

@ -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__) */

View file

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

View file

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

View file

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

View file

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

View file

@ -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__) */