Merge pull request #1620 from ZappoMan/new_particle_scripting_interface

New particle scripting interface
This commit is contained in:
Philip Rosedale 2014-01-22 14:14:33 -08:00
commit 135f96a18c
46 changed files with 2062 additions and 889 deletions

View file

@ -18,14 +18,14 @@ var numberParticlesAdded = 0;
var MAX_PARTICLES = 1;
var velocity = {
x: 1/TREE_SCALE,
y: 0/TREE_SCALE,
z: 1/TREE_SCALE };
x: 1,
y: 0,
z: 1 };
var gravity = {
x: 0/TREE_SCALE,
y: 0/TREE_SCALE,
z: 0/TREE_SCALE };
x: 0,
y: 0,
z: 0 };
var damping = 0.1;
@ -65,16 +65,27 @@ function draw() {
if (currentIteration == 0) {
var colorGreen = { red: 0, green: 255, blue: 0 };
var startPosition = {
x: 2/TREE_SCALE,
y: 0/TREE_SCALE,
z: 2/TREE_SCALE };
var largeRadius = 0.5/TREE_SCALE;
x: 2,
y: 0,
z: 2 };
var largeRadius = 0.5;
var verySlow = {
x: 0.01/TREE_SCALE,
y: 0/TREE_SCALE,
z: 0.01/TREE_SCALE };
x: 0.01,
y: 0,
z: 0.01 };
Particles.queueParticleAdd(startPosition, largeRadius, colorGreen, verySlow, gravity, damping, false, scriptA);
var properties = {
position: startPosition,
radius: largeRadius,
color: colorGreen,
velocity: verySlow,
gravity: gravity,
damping: damping,
inHand: false,
script: scriptA
};
Particles.addParticle(properties);
print("hello... added particle... script=\n");
print(scriptA);
numberParticlesAdded++;
@ -84,15 +95,15 @@ function draw() {
print("draw()... sending another... currentIteration=" +currentIteration + "\n");
var center = {
x: 0/TREE_SCALE,
y: 0/TREE_SCALE,
z: 0/TREE_SCALE };
x: 0,
y: 0,
z: 0 };
var particleSize = 0.1 / TREE_SCALE;
var particleSize = 0.1;
print("number of particles=" + numberParticlesAdded +"\n");
var velocityStep = 0.1/TREE_SCALE;
var velocityStep = 0.1;
if (velocity.x > 0) {
velocity.x -= velocityStep;
velocity.z += velocityStep;
@ -106,7 +117,17 @@ function draw() {
}
if (numberParticlesAdded <= MAX_PARTICLES) {
Particles.queueParticleAdd(center, particleSize, color, velocity, gravity, damping, false, scriptB);
var properties = {
position: center,
radius: particleSize,
color: color,
velocity: velocity,
gravity: gravity,
damping: damping,
inHand: false,
script: scriptB
};
Particles.addParticle(properties);
print("hello... added particle... script=\n");
print(scriptB);
numberParticlesAdded++;

View file

@ -0,0 +1,95 @@
//
// editParticleExample.js
// hifi
//
// Created by Brad Hefta-Gaub on 12/31/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates creating and editing a particle
//
var count = 0;
var originalProperties = {
position: { x: 10,
y: 0,
z: 0 },
velocity: { x: 0,
y: 0,
z: 0 },
gravity: { x: 0,
y: 0,
z: 0 },
radius : 0.1,
color: { red: 0,
green: 255,
blue: 0 }
};
var positionDelta = { x: 0.05, y: 0, z: 0 };
var particleID = Particles.addParticle(originalProperties);
function moveParticle() {
if (count >= 100) {
//Agent.stop();
// delete it...
if (count == 100) {
print("calling Particles.deleteParticle()");
Particles.deleteParticle(particleID);
}
// stop it...
if (count >= 200) {
print("calling Agent.stop()");
Agent.stop();
}
count++;
return; // break early
}
print("count =" + count);
count++;
print("particleID.creatorTokenID = " + particleID.creatorTokenID);
var newProperties = {
position: {
x: originalProperties.position.x + (count * positionDelta.x),
y: originalProperties.position.y + (count * positionDelta.y),
z: originalProperties.position.z + (count * positionDelta.z)
},
radius : 0.25,
};
//print("particleID = " + particleID);
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:");
}
}
// register the call back so it fires before each data send
Agent.willSendVisualDataCallback.connect(moveParticle);

View file

@ -37,25 +37,30 @@ function vInterpolate(a, b, fraction) {
return rval;
}
var position = { x: 5.0 / TREE_SCALE, y: 5.0 / TREE_SCALE, z: 5.0 / TREE_SCALE };
Voxels.queueDestructiveVoxelAdd(position.x, position.y - (1.0 / TREE_SCALE), position.z, 0.5 / TREE_SCALE, 255, 255, 1);
var position = { x: 5.0, y: 0.6, z: 5.0 };
Voxels.setVoxel(position.x, 0, position.z, 0.5, 0, 0, 255);
var totalParticles = 0;
function makeFountain() {
if (Math.random() < 0.06) {
//print("Made particle!\n");
var properties = {
position: position,
radius: (0.02 + (Math.random() * 0.05)),
color: { red: 0, green: 0, blue: 128 },
velocity: { x: (Math.random() * 1.0 - 0.5),
y: (1.0 + (Math.random() * 2.0)),
z: (Math.random() * 1.0 - 0.5) },
gravity: { x: 0, y: -0.5, z: 0 },
damping: 0.25,
lifetime: 2
}
var size = (0.02 + (Math.random() * 0.05)) / TREE_SCALE;
var velocity = { x: (Math.random() * 1.0 - 0.5) / TREE_SCALE,
y: (1.0 + (Math.random() * 2.0)) / TREE_SCALE,
z: (Math.random() * 1.0 - 0.5) / TREE_SCALE };
var gravity = { x: 0, y: -0.5 / TREE_SCALE, z: 0 }; // gravity has no effect on these bullets
var color = { red: 0, green: 0, blue: 128 };
var damping = 0.25; // no damping
var inHand = false;
var script = "";
Particles.queueParticleAdd(position, size, color, velocity, gravity, damping, inHand, script);
Particles.addParticle(properties);
totalParticles++;
}
if (totalParticles > 100) {
Agent.stop();
}
}
// register the call back so it fires before each data send

View file

@ -51,39 +51,22 @@ function checkController() {
var fingerTipController = palmController + 1;
var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController);
var bulletSize = 0.05/TREE_SCALE;
var palmInParticleSpace =
{ x: palmPosition.x/TREE_SCALE,
y: palmPosition.y/TREE_SCALE,
z: palmPosition.z/TREE_SCALE };
var tipInParticleSpace =
{ x: fingerTipPosition.x/TREE_SCALE,
y: fingerTipPosition.y/TREE_SCALE,
z: fingerTipPosition.z/TREE_SCALE };
var palmToFingerTipVector =
{ x: (tipInParticleSpace.x - palmInParticleSpace.x),
y: (tipInParticleSpace.y - palmInParticleSpace.y),
z: (tipInParticleSpace.z - palmInParticleSpace.z) };
{ x: (fingerTipPosition.x - palmPosition.x),
y: (fingerTipPosition.y - palmPosition.y),
z: (fingerTipPosition.z - palmPosition.z) };
// just off the front of the finger tip
var position = { x: tipInParticleSpace.x + palmToFingerTipVector.x/2,
y: tipInParticleSpace.y + palmToFingerTipVector.y/2,
z: tipInParticleSpace.z + palmToFingerTipVector.z/2};
var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2,
y: fingerTipPosition.y + palmToFingerTipVector.y/2,
z: fingerTipPosition.z + palmToFingerTipVector.z/2};
var linearVelocity = 5;
var linearVelocity = 25;
var velocity = { x: palmToFingerTipVector.x * linearVelocity,
y: palmToFingerTipVector.y * linearVelocity,
z: palmToFingerTipVector.z * linearVelocity };
var gravity = { x: 0, y: -0.1/TREE_SCALE, z: 0 }; // gravity has no effect on these bullets
var color = { red: 128, green: 128, blue: 128 };
var damping = 0; // no damping
var inHand = false;
// This is the script for the particles that this gun shoots.
var script =
" function collisionWithVoxel(voxel) { " +
@ -96,12 +79,19 @@ function checkController() {
" Particle.setColor(voxelColor); " +
" var voxelAt = voxel.getPosition();" +
" var voxelScale = voxel.getScale();" +
" Voxels.queueVoxelDelete(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale); " +
" print('Voxels.queueVoxelDelete(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" Voxels.eraseVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale); " +
" print('Voxels.eraseVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" } " +
" Particle.collisionWithVoxel.connect(collisionWithVoxel); ";
Particles.queueParticleAdd(position, bulletSize, color, velocity, gravity, damping, inHand, script);
Particles.addParticle(
{ position: position,
radius: 0.05,
color: { red: 128, green: 128, blue: 128 },
velocity: velocity,
gravity: { x: 0, y: -0.1, z: 0 },
damping: 0,
script: script });
}
}
}

View file

@ -28,9 +28,9 @@ function moveVoxel() {
thisColor = colorEdge;
}
// Create a new voxel
Voxels.queueDestructiveVoxelAdd(position.x / TREE_SCALE, position.y / TREE_SCALE, position.z / TREE_SCALE, size / TREE_SCALE, thisColor.r, thisColor.g, thisColor.b);
Voxels.setVoxel(position.x, position.y, position.z, size, thisColor.r, thisColor.g, thisColor.b);
// delete old voxel
Voxels.queueVoxelDelete(oldPosition.x / TREE_SCALE, oldPosition.y / TREE_SCALE, oldPosition.z / TREE_SCALE, size / TREE_SCALE);
Voxels.eraseVoxel(oldPosition.x, oldPosition.y, oldPosition.z, size);
// Copy old location to new
oldPosition.x = position.x;
oldPosition.y = position.y;

View file

@ -1,17 +1,10 @@
//
// gun.js
// paintGun.js
// hifi
//
// Created by Brad Hefta-Gaub on 12/31/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
// This is an example script that turns the hydra controllers into a particle gun.
// It reads the controller, watches for trigger pulls, and launches particles.
// The particles it creates have a script that when they collide with Voxels, the
// particle will change it's color to match the voxel it hits, and then delete the
// voxel.
//
//
// initialize our triggers
var triggerPulled = new Array();
@ -51,27 +44,15 @@ function checkController() {
var fingerTipController = palmController + 1;
var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController);
var bulletSize = 0.01/TREE_SCALE;
var palmInParticleSpace =
{ x: palmPosition.x/TREE_SCALE,
y: palmPosition.y/TREE_SCALE,
z: palmPosition.z/TREE_SCALE };
var tipInParticleSpace =
{ x: fingerTipPosition.x/TREE_SCALE,
y: fingerTipPosition.y/TREE_SCALE,
z: fingerTipPosition.z/TREE_SCALE };
var palmToFingerTipVector =
{ x: (tipInParticleSpace.x - palmInParticleSpace.x),
y: (tipInParticleSpace.y - palmInParticleSpace.y),
z: (tipInParticleSpace.z - palmInParticleSpace.z) };
{ x: (fingerTipPosition.x - palmPosition.x),
y: (fingerTipPosition.y - palmPosition.y),
z: (fingerTipPosition.z - palmPosition.z) };
// just off the front of the finger tip
var position = { x: tipInParticleSpace.x + palmToFingerTipVector.x/2,
y: tipInParticleSpace.y + palmToFingerTipVector.y/2,
z: tipInParticleSpace.z + palmToFingerTipVector.z/2};
var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2,
y: fingerTipPosition.y + palmToFingerTipVector.y/2,
z: fingerTipPosition.z + palmToFingerTipVector.z/2};
var linearVelocity = 25;
@ -79,11 +60,6 @@ function checkController() {
y: palmToFingerTipVector.y * linearVelocity,
z: palmToFingerTipVector.z * linearVelocity };
var gravity = { x: 0, y: -0.1/TREE_SCALE, z: 0 }; // gravity has no effect on these bullets
var color = { red: 128, green: 128, blue: 128 };
var damping = 0; // no damping
var inHand = false;
// This is the script for the particles that this gun shoots.
var script =
" function collisionWithVoxel(voxel) { " +
@ -96,12 +72,20 @@ function checkController() {
" Particle.setColor(voxelColor); " +
" var voxelAt = voxel.getPosition();" +
" var voxelScale = voxel.getScale();" +
" Voxels.queueVoxelAdd(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale, 255, 255, 0); " +
" print('Voxels.queueVoxelDelete(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" Voxels.setVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale, 255, 255, 0); " +
" print('Voxels.setVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" } " +
" Particle.collisionWithVoxel.connect(collisionWithVoxel); ";
Particles.queueParticleAdd(position, bulletSize, color, velocity, gravity, damping, inHand, script);
Particles.addParticle(
{ position: position,
radius: 0.01,
color: { red: 128, green: 128, blue: 128 },
velocity: velocity,
gravity: { x: 0, y: -0.1, z: 0 },
damping: 0,
script: script }
);
}
}
}

227
examples/toyball.js Normal file
View file

@ -0,0 +1,227 @@
//
// toyball.js
// hifi
//
// Created by Brad Hefta-Gaub on 1/20/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that turns the hydra controllers into a toy ball catch and throw game.
// It reads the controller, watches for button presses and trigger pulls, and launches particles.
//
// The particles it creates have a script that when they collide with Voxels, the
// particle will change it's color to match the voxel it hits.
//
//
// maybe we should make these constants...
var LEFT_PALM = 0;
var LEFT_TIP = 1;
var LEFT_BUTTON_FWD = 5;
var LEFT_BUTTON_3 = 3;
var RIGHT_PALM = 2;
var RIGHT_TIP = 3;
var RIGHT_BUTTON_FWD = 11;
var RIGHT_BUTTON_3 = 9;
var leftBallAlreadyInHand = false;
var rightBallAlreadyInHand = false;
var leftHandParticle;
var rightHandParticle;
var throwSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
var targetRadius = 0.25;
var wantDebugging = false;
function debugPrint(message) {
if (wantDebugging) {
print(message);
}
}
function getBallHoldPosition(whichSide) {
var normal;
var tipPosition;
if (whichSide == LEFT_PALM) {
normal = Controller.getSpatialControlNormal(LEFT_PALM);
tipPosition = Controller.getSpatialControlPosition(LEFT_TIP);
} else {
normal = Controller.getSpatialControlNormal(RIGHT_PALM);
tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP);
}
var BALL_FORWARD_OFFSET = 0.08; // put the ball a bit forward of fingers
position = { x: BALL_FORWARD_OFFSET * normal.x,
y: BALL_FORWARD_OFFSET * normal.y,
z: BALL_FORWARD_OFFSET * normal.z };
position.x += tipPosition.x;
position.y += tipPosition.y;
position.z += tipPosition.z;
return position;
}
function checkControllerSide(whichSide) {
var BUTTON_FWD;
var BUTTON_3;
var palmPosition;
var ballAlreadyInHand;
var handMessage;
if (whichSide == LEFT_PALM) {
BUTTON_FWD = LEFT_BUTTON_FWD;
BUTTON_3 = LEFT_BUTTON_3;
palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
ballAlreadyInHand = leftBallAlreadyInHand;
handMessage = "LEFT";
} else {
BUTTON_FWD = RIGHT_BUTTON_FWD;
BUTTON_3 = RIGHT_BUTTON_3;
palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
ballAlreadyInHand = rightBallAlreadyInHand;
handMessage = "RIGHT";
}
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3));
// If I don't currently have a ball in my hand, then try to catch closest one
if (!ballAlreadyInHand && grabButtonPressed) {
var closestParticle = Particles.findClosestParticle(palmPosition, targetRadius);
if (closestParticle.isKnownID) {
debugPrint(handMessage + " HAND- CAUGHT SOMETHING!!");
if (whichSide == LEFT_PALM) {
leftBallAlreadyInHand = true;
leftHandParticle = closestParticle;
} else {
rightBallAlreadyInHand = true;
rightHandParticle = closestParticle;
}
var ballPosition = getBallHoldPosition(whichSide);
var properties = { position: { x: ballPosition.x,
y: ballPosition.y,
z: ballPosition.z },
velocity : { x: 0, y: 0, z: 0}, inHand: true };
Particles.editParticle(closestParticle, properties);
var options = new AudioInjectionOptions();
options.position = ballPosition;
options.volume = 1.0;
Audio.playSound(catchSound, options);
return; // exit early
}
}
// change ball color logic...
//
//if (wasButtonJustPressed()) {
// rotateColor();
//}
// If '3' is pressed, and not holding a ball, make a new one
if (Controller.isButtonPressed(BUTTON_3) && !ballAlreadyInHand) {
var ballPosition = getBallHoldPosition(whichSide);
var properties = { position: { x: ballPosition.x,
y: ballPosition.y,
z: ballPosition.z },
velocity: { x: 0, y: 0, z: 0},
gravity: { x: 0, y: 0, z: 0},
inHand: true,
radius: 0.05,
color: { red: 255, green: 0, blue: 0 },
lifetime: 10 // 10 seconds
};
newParticle = Particles.addParticle(properties);
if (whichSide == LEFT_PALM) {
leftBallAlreadyInHand = true;
leftHandParticle = newParticle;
} else {
rightBallAlreadyInHand = true;
rightHandParticle = newParticle;
}
// Play a new ball sound
var options = new AudioInjectionOptions();
options.position = ballPosition;
options.volume = 1.0;
Audio.playSound(catchSound, options);
return; // exit early
}
if (ballAlreadyInHand) {
if (whichSide == LEFT_PALM) {
handParticle = leftHandParticle;
whichTip = LEFT_TIP;
} else {
handParticle = rightHandParticle;
whichTip = RIGHT_TIP;
}
// If holding the ball keep it in the palm
if (grabButtonPressed) {
debugPrint(">>>>> " + handMessage + "-BALL IN HAND, grabbing, hold and move");
var ballPosition = getBallHoldPosition(whichSide);
var properties = { position: { x: ballPosition.x,
y: ballPosition.y,
z: ballPosition.z },
};
Particles.editParticle(handParticle, properties);
} else {
debugPrint(">>>>> " + handMessage + "-BALL IN HAND, not grabbing, THROW!!!");
// If toy ball just released, add velocity to it!
var tipVelocity = Controller.getSpatialControlVelocity(whichTip);
var THROWN_VELOCITY_SCALING = 1.5;
var properties = {
velocity: { x: tipVelocity.x * THROWN_VELOCITY_SCALING,
y: tipVelocity.y * THROWN_VELOCITY_SCALING,
z: tipVelocity.z * THROWN_VELOCITY_SCALING } ,
inHand: false,
gravity: { x: 0, y: -2, z: 0},
};
Particles.editParticle(handParticle, properties);
if (whichSide == LEFT_PALM) {
leftBallAlreadyInHand = false;
leftHandParticle = false;
} else {
rightBallAlreadyInHand = false;
rightHandParticle = false;
}
var options = new AudioInjectionOptions();
options.position = ballPosition;
options.volume = 1.0;
Audio.playSound(throwSound, options);
}
}
}
function checkController() {
var numberOfButtons = Controller.getNumberOfButtons();
var numberOfTriggers = Controller.getNumberOfTriggers();
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
// this is expected for hydras
if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) {
debugPrint("no hydra connected?");
return; // bail if no hydra
}
checkControllerSide(LEFT_PALM);
checkControllerSide(RIGHT_PALM);
}
// register the call back so it fires before each data send
Agent.willSendVisualDataCallback.connect(checkController);

View file

@ -110,17 +110,17 @@ function moveBird() {
if (tweeting > 0) {
// Change color of voxel to blinky red a bit while playing the sound
var blinkColor = { r: Math.random() * 255, g: 0, b: 0 };
Voxels.queueDestructiveVoxelAdd(position.x / TREE_SCALE,
position.y / TREE_SCALE,
position.z / TREE_SCALE,
size / TREE_SCALE,
Voxels.setVoxel(position.x,
position.y,
position.z,
size,
blinkColor.r, blinkColor.g, blinkColor.b);
}
if (moved) {
Voxels.queueDestructiveVoxelAdd(position.x / TREE_SCALE, position.y / TREE_SCALE, position.z / TREE_SCALE, size / TREE_SCALE, thisColor.r, thisColor.g, thisColor.b);
Voxels.setVoxel(position.x, position.y, position.z, size, thisColor.r, thisColor.g, thisColor.b);
// delete old voxel
Voxels.queueVoxelDelete(oldPosition.x / TREE_SCALE, oldPosition.y / TREE_SCALE, oldPosition.z / TREE_SCALE, size / TREE_SCALE);
Voxels.eraseVoxel(oldPosition.x, oldPosition.y, oldPosition.z, size);
// Copy old location to new
vCopy(oldPosition, position);
moved = false;

View file

@ -661,8 +661,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier);
bool isMeta = event->modifiers().testFlag(Qt::ControlModifier);
switch (event->key()) {
case Qt::Key_N:
shootParticle();
break;
case Qt::Key_Shift:
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) {
@ -1467,60 +1465,6 @@ void Application::removeVoxel(glm::vec3 position,
_voxels.deleteVoxelAt(voxel.x, voxel.y, voxel.z, voxel.s);
}
void Application::shootParticle() {
glm::vec3 position = _viewFrustum.getPosition();
glm::vec3 direction = _viewFrustum.getDirection();
const float LINEAR_VELOCITY = 5.f;
glm::vec3 lookingAt = position + (direction * LINEAR_VELOCITY);
const float radius = 0.125 / TREE_SCALE;
xColor color = { 0, 255, 255};
glm::vec3 velocity = lookingAt - position;
glm::vec3 gravity = DEFAULT_GRAVITY * 0.f;
float damping = DEFAULT_DAMPING * 0.01f;
QString script(
" function collisionWithVoxel(voxel) { "
" print('collisionWithVoxel(voxel)... '); "
" print('myID=' + Particle.getID() + '\\n'); "
" var voxelColor = voxel.getColor();"
" print('voxelColor=' + voxelColor.red + ', ' + voxelColor.green + ', ' + voxelColor.blue + '\\n'); "
" var myColor = Particle.getColor();"
" print('myColor=' + myColor.red + ', ' + myColor.green + ', ' + myColor.blue + '\\n'); "
" Particle.setColor(voxelColor); "
" var voxelAt = voxel.getPosition();"
" var voxelScale = voxel.getScale();"
" Voxels.queueVoxelDelete(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale); "
" print('Voxels.queueVoxelDelete(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); "
" } "
" Particle.collisionWithVoxel.connect(collisionWithVoxel); " );
ParticleEditHandle* particleEditHandle = makeParticle(position / (float)TREE_SCALE, radius, color,
velocity / (float)TREE_SCALE, gravity, damping, NOT_IN_HAND, script);
// If we wanted to be able to edit this particle after shooting, then we could store this value
// and use it for editing later. But we don't care about that for "shooting" and therefore we just
// clean up our memory now. deleting a ParticleEditHandle does not effect the underlying particle,
// it just removes your ability to edit that particle later.
delete particleEditHandle;
}
// Caller is responsible for managing this EditableParticle
ParticleEditHandle* Application::newParticleEditHandle(uint32_t id) {
ParticleEditHandle* particleEditHandle = new ParticleEditHandle(&_particleEditSender, _particles.getTree(), id);
return particleEditHandle;
}
// Caller is responsible for managing this EditableParticle
ParticleEditHandle* Application::makeParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, bool inHand, QString updateScript) {
ParticleEditHandle* particleEditHandle = newParticleEditHandle();
particleEditHandle->createParticle(position, radius, color, velocity, gravity, damping, inHand, updateScript);
return particleEditHandle;
}
void Application::makeVoxel(glm::vec3 position,
float scale,
@ -4137,11 +4081,12 @@ void Application::processDatagrams() {
break;
case PACKET_TYPE_PARTICLE_ADD_RESPONSE:
// look up our ParticleEditHanders....
ParticleEditHandle::handleAddResponse(_incomingPacket, bytesReceived);
// this will keep creatorTokenIDs to IDs mapped correctly
Particle::handleAddParticleResponse(_incomingPacket, bytesReceived);
break;
case PACKET_TYPE_PARTICLE_DATA:
case PACKET_TYPE_PARTICLE_ERASE:
case PACKET_TYPE_VOXEL_DATA:
case PACKET_TYPE_VOXEL_ERASE:
case PACKET_TYPE_OCTREE_STATS:
@ -4261,6 +4206,7 @@ void Application::loadScript(const QString& fileNameString){
// we can use the same ones from the application.
scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
// hook our avatar object into this script engine
scriptEngine->setAvatarData(&_myAvatar, "MyAvatar");

View file

@ -68,7 +68,6 @@
#include "ui/UpdateDialog.h"
#include "FileLogger.h"
#include "ParticleTreeRenderer.h"
#include "ParticleEditHandle.h"
#include "ControllerScriptingInterface.h"
@ -128,11 +127,6 @@ public:
void wheelEvent(QWheelEvent* event);
void shootParticle(); // shoots a particle in the direction you're looking
ParticleEditHandle* newParticleEditHandle(uint32_t id = NEW_PARTICLE);
ParticleEditHandle* makeParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, bool inHand, QString updateScript);
void makeVoxel(glm::vec3 position,
float scale,
unsigned char red,

View file

@ -73,7 +73,7 @@ void BuckyBalls::grab(PalmData& palm, const glm::vec3& fingerTipPosition, glm::q
diff = _bballPosition[_bballIsGrabbed[palm.getSixenseID()]] - fingerTipPosition;
penetration = glm::length(diff) - (_bballRadius[_bballIsGrabbed[palm.getSixenseID()]] + COLLISION_RADIUS);
_bballPosition[_bballIsGrabbed[palm.getSixenseID()]] -= glm::normalize(diff) * penetration;
glm::vec3 fingerTipVelocity = avatarOrientation * palm.getTipVelocity();
glm::vec3 fingerTipVelocity = palm.getTipVelocity();
if (_bballElement[_bballIsGrabbed[palm.getSixenseID()]] != 1) {
_bballVelocity[_bballIsGrabbed[palm.getSixenseID()]] = fingerTipVelocity;
}

View file

@ -60,3 +60,8 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
}
}
}
void ParticleTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr,
Node* sourceNode) {
static_cast<ParticleTree*>(_tree)->processEraseMessage(dataByteArray, senderSockAddr, sourceNode);
}

View file

@ -37,6 +37,8 @@ public:
ParticleTree* getTree() { return (ParticleTree*)_tree; }
void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode);
protected:
};

View file

@ -61,6 +61,11 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
if (serverNode && serverNode->getActiveSocket() && *serverNode->getActiveSocket() == senderSockAddr) {
switch(packetData[0]) {
case PACKET_TYPE_PARTICLE_ERASE: {
app->_particles.processEraseMessage(QByteArray((char*) packetData, messageLength),
senderSockAddr, serverNode.data());
} break;
case PACKET_TYPE_PARTICLE_DATA: {
app->_particles.processDatagram(QByteArray((char*) packetData, messageLength),
senderSockAddr, serverNode.data());

View file

@ -21,25 +21,7 @@ using namespace std;
const float FINGERTIP_COLLISION_RADIUS = 0.01f;
const float FINGERTIP_VOXEL_SIZE = 0.05f;
const int TOY_BALL_HAND = 1;
const float TOY_BALL_RADIUS = 0.05f;
const float TOY_BALL_DAMPING = 0.1f;
const glm::vec3 NO_VELOCITY = glm::vec3(0,0,0);
const glm::vec3 NO_GRAVITY = glm::vec3(0,0,0);
const float NO_DAMPING = 0.f;
const glm::vec3 TOY_BALL_GRAVITY = glm::vec3(0,-2.0f,0);
const QString TOY_BALL_UPDATE_SCRIPT("");
const float PALM_COLLISION_RADIUS = 0.03f;
const float CATCH_RADIUS = 0.3f;
const xColor TOY_BALL_ON_SERVER_COLOR[] =
{
{ 255, 0, 0 },
{ 0, 255, 0 },
{ 0, 0, 255 },
{ 255, 255, 0 },
{ 0, 255, 255 },
{ 255, 0, 255 },
};
Hand::Hand(Avatar* owningAvatar) :
@ -55,16 +37,8 @@ Hand::Hand(Avatar* owningAvatar) :
_grabDelta(0, 0, 0),
_grabDeltaVelocity(0, 0, 0),
_grabStartRotation(0, 0, 0, 1),
_grabCurrentRotation(0, 0, 0, 1),
_throwSound(QUrl("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw")),
_catchSound(QUrl("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw"))
_grabCurrentRotation(0, 0, 0, 1)
{
for (int i = 0; i < MAX_HANDS; i++) {
_toyBallInHand[i] = false;
_ballParticleEditHandles[i] = NULL;
_whichBallColor[i] = 0;
}
_lastControllerButtons = 0;
}
void Hand::init() {
@ -80,157 +54,7 @@ void Hand::init() {
void Hand::reset() {
}
void Hand::simulateToyBall(PalmData& palm, const glm::vec3& fingerTipPosition, float deltaTime) {
Application* app = Application::getInstance();
ParticleTree* particles = app->getParticles()->getTree();
int handID = palm.getSixenseID();
const int NEW_BALL_BUTTON = BUTTON_3;
bool grabButtonPressed = ((palm.getControllerButtons() & BUTTON_FWD) ||
(palm.getControllerButtons() & BUTTON_3));
bool ballAlreadyInHand = _toyBallInHand[handID];
glm::vec3 targetPosition;
palm.getBallHoldPosition(targetPosition);
float targetRadius = CATCH_RADIUS / (float)TREE_SCALE;
// If I don't currently have a ball in my hand, then try to catch closest one
if (!ballAlreadyInHand && grabButtonPressed) {
const Particle* closestParticle = particles->findClosestParticle(targetPosition, targetRadius);
if (closestParticle) {
ParticleEditHandle* caughtParticle = app->newParticleEditHandle(closestParticle->getID());
glm::vec3 newPosition = targetPosition;
glm::vec3 newVelocity = NO_VELOCITY;
// update the particle with it's new state...
caughtParticle->updateParticle(newPosition,
closestParticle->getRadius(),
closestParticle->getXColor(),
newVelocity,
NO_GRAVITY,
NO_DAMPING,
IN_HAND, // we just grabbed it!
closestParticle->getScript());
// now tell our hand about us having caught it...
_toyBallInHand[handID] = true;
//printf(">>>>>>> caught... handID:%d particle ID:%d _toyBallInHand[handID] = true\n", handID, closestParticle->getID());
_ballParticleEditHandles[handID] = caughtParticle;
caughtParticle = NULL;
// use the threadSound static method to inject the catch sound
// pass an AudioInjectorOptions struct to set position and disable loopback
AudioInjectorOptions injectorOptions;
injectorOptions.setPosition(newPosition);
injectorOptions.setLoopbackAudioInterface(app->getAudio());
AudioScriptingInterface::playSound(&_catchSound, &injectorOptions);
}
}
// If there's a ball in hand, and the user presses the skinny button, then change the color of the ball
int currentControllerButtons = palm.getControllerButtons();
if (currentControllerButtons != _lastControllerButtons && (currentControllerButtons & BUTTON_0)) {
_whichBallColor[handID]++;
if (_whichBallColor[handID] >= sizeof(TOY_BALL_ON_SERVER_COLOR)/sizeof(TOY_BALL_ON_SERVER_COLOR[0])) {
_whichBallColor[handID] = 0;
}
}
// If '3' is pressed, and not holding a ball, make a new one
if ((palm.getControllerButtons() & NEW_BALL_BUTTON) && (_toyBallInHand[handID] == false)) {
_toyBallInHand[handID] = true;
// Create a particle on the particle server
glm::vec3 ballPosition;
palm.getBallHoldPosition(ballPosition);
_ballParticleEditHandles[handID] = app->makeParticle(
ballPosition / (float)TREE_SCALE,
TOY_BALL_RADIUS / (float) TREE_SCALE,
TOY_BALL_ON_SERVER_COLOR[_whichBallColor[handID]],
NO_VELOCITY / (float)TREE_SCALE,
TOY_BALL_GRAVITY / (float) TREE_SCALE,
TOY_BALL_DAMPING,
IN_HAND,
TOY_BALL_UPDATE_SCRIPT);
// Play a new ball sound
app->getAudio()->startDrumSound(1.0f, 2000, 0.5f, 0.02f);
}
if (grabButtonPressed) {
// If we don't currently have a ball in hand, then create it...
if (_toyBallInHand[handID]) {
// Update ball that is in hand
uint32_t particleInHandID = _ballParticleEditHandles[handID]->getID();
const Particle* particleInHand = particles->findParticleByID(particleInHandID);
xColor colorForParticleInHand = particleInHand ? particleInHand->getXColor()
: TOY_BALL_ON_SERVER_COLOR[_whichBallColor[handID]];
glm::vec3 ballPosition;
palm.getBallHoldPosition(ballPosition);
_ballParticleEditHandles[handID]->updateParticle(ballPosition / (float)TREE_SCALE,
TOY_BALL_RADIUS / (float) TREE_SCALE,
colorForParticleInHand,
NO_VELOCITY / (float)TREE_SCALE,
TOY_BALL_GRAVITY / (float) TREE_SCALE,
TOY_BALL_DAMPING,
IN_HAND,
TOY_BALL_UPDATE_SCRIPT);
}
} else {
// If toy ball just released, add velocity to it!
if (_toyBallInHand[handID]) {
const float THROWN_VELOCITY_SCALING = 1.5f;
_toyBallInHand[handID] = false;
palm.updateCollisionlessPaddleExpiry();
glm::vec3 ballPosition;
palm.getBallHoldPosition(ballPosition);
glm::vec3 ballVelocity = palm.getTipVelocity();
glm::quat avatarRotation = _owningAvatar->getOrientation();
ballVelocity = avatarRotation * ballVelocity;
ballVelocity *= THROWN_VELOCITY_SCALING;
uint32_t particleInHandID = _ballParticleEditHandles[handID]->getID();
const Particle* particleInHand = particles->findParticleByID(particleInHandID);
xColor colorForParticleInHand = particleInHand ? particleInHand->getXColor()
: TOY_BALL_ON_SERVER_COLOR[_whichBallColor[handID]];
_ballParticleEditHandles[handID]->updateParticle(ballPosition / (float)TREE_SCALE,
TOY_BALL_RADIUS / (float) TREE_SCALE,
colorForParticleInHand,
ballVelocity / (float)TREE_SCALE,
TOY_BALL_GRAVITY / (float) TREE_SCALE,
TOY_BALL_DAMPING,
NOT_IN_HAND,
TOY_BALL_UPDATE_SCRIPT);
// after releasing the ball, we free our ParticleEditHandle so we can't edit it further
// note: deleting the edit handle doesn't effect the actual particle
delete _ballParticleEditHandles[handID];
_ballParticleEditHandles[handID] = NULL;
// use the threadSound static method to inject the throw sound
// pass an AudioInjectorOptions struct to set position and disable loopback
AudioInjectorOptions injectorOptions;
injectorOptions.setPosition(ballPosition);
injectorOptions.setLoopbackAudioInterface(app->getAudio());
AudioScriptingInterface::playSound(&_throwSound, &injectorOptions);
}
}
// remember the last pressed button state
if (currentControllerButtons != 0) {
_lastControllerButtons = currentControllerButtons;
}
}
glm::vec3 Hand::getAndResetGrabDelta() {
const float HAND_GRAB_SCALE_DISTANCE = 2.f;
@ -277,8 +101,6 @@ void Hand::simulate(float deltaTime, bool isMine) {
FingerData& finger = palm.getFingers()[0]; // Sixense has only one finger
glm::vec3 fingerTipPosition = finger.getTipPosition();
simulateToyBall(palm, fingerTipPosition, deltaTime);
_buckyBalls.grab(palm, fingerTipPosition, _owningAvatar->getOrientation(), deltaTime);
if (palm.getControllerButtons() & BUTTON_4) {
@ -513,7 +335,6 @@ void Hand::render(bool isMine) {
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayLeapHands)) {
renderLeapHands(isMine);
}
@ -541,7 +362,6 @@ void Hand::render(bool isMine) {
void Hand::renderLeapHands(bool isMine) {
const float alpha = 1.0f;
const float TARGET_ALPHA = 0.5f;
//const glm::vec3 handColor = _ballColor;
const glm::vec3 handColor(1.0, 0.84, 0.66); // use the skin color
@ -559,19 +379,6 @@ void Hand::renderLeapHands(bool isMine) {
palm.getBallHoldPosition(targetPosition);
glPushMatrix();
ParticleTree* particles = Application::getInstance()->getParticles()->getTree();
const Particle* closestParticle = particles->findClosestParticle(targetPosition / (float)TREE_SCALE,
CATCH_RADIUS / (float)TREE_SCALE);
// If we are hitting a particle then draw the target green, otherwise yellow
if (closestParticle) {
glColor4f(0,1,0, TARGET_ALPHA);
} else {
glColor4f(1,1,0, TARGET_ALPHA);
}
glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z);
glutWireSphere(CATCH_RADIUS, 10.f, 10.f);
const float collisionRadius = 0.05f;
glColor4f(0.5f,0.5f,0.5f, alpha);
glutWireSphere(collisionRadius, 10.f, 10.f);

View file

@ -19,7 +19,6 @@
#include <AvatarData.h>
#include <AudioScriptingInterface.h>
#include <HandData.h>
#include <ParticleEditHandle.h>
#include "BuckyBalls.h"
#include "InterfaceConfig.h"
@ -102,24 +101,12 @@ private:
void handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime);
void simulateToyBall(PalmData& palm, const glm::vec3& fingerTipPosition, float deltaTime);
#define MAX_HANDS 2
bool _toyBallInHand[MAX_HANDS];
int _whichBallColor[MAX_HANDS];
ParticleEditHandle* _ballParticleEditHandles[MAX_HANDS];
int _lastControllerButtons;
float _pitchUpdate;
glm::vec3 _grabDelta;
glm::vec3 _grabDeltaVelocity;
glm::quat _grabStartRotation;
glm::quat _grabCurrentRotation;
Sound _throwSound;
Sound _catchSound;
};
#endif

View file

@ -169,10 +169,11 @@ public:
void setTipPosition(const glm::vec3& position) { _tipPosition = position; }
const glm::vec3 getTipPosition() const { return _owningHandData->leapPositionToWorldPosition(_tipPosition); }
const glm::vec3 getTipRawPosition() const { return _tipPosition; }
const glm::vec3& getTipRawPosition() const { return _tipPosition; }
const glm::vec3& getTipVelocity() const { return _tipVelocity; }
void setTipVelocity(const glm::vec3& velocity) { _tipVelocity = velocity; }
const glm::vec3 getTipVelocity() const { return _owningHandData->leapDirectionToWorldDirection(_tipVelocity); }
const glm::vec3& getTipRawVelocity() const { return _tipVelocity; }
void incrementFramesWithoutData() { _numFramesWithoutData++; }
void resetFramesWithoutData() { _numFramesWithoutData = 0; }

View file

@ -525,7 +525,8 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
// Here's where we can/should allow the server to send other data...
// send the environment packet
if (_myServer->hasSpecialPacketToSend()) {
// TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketToSend(node)) {
trueBytesSent += _myServer->sendSpecialPacket(node);
truePacketsSent++;
packetsSentThisInterval++;

View file

@ -58,7 +58,7 @@ public:
// subclass may implement these method
virtual void beforeRun() { };
virtual bool hasSpecialPacketToSend() { return false; }
virtual bool hasSpecialPacketToSend(Node* node) { return false; }
virtual int sendSpecialPacket(Node* node) { return 0; }
static void attachQueryNodeToNode(Node* newNode);

View file

@ -1367,7 +1367,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) {
PACKET_TYPE expectedType = expectedDataPacketType();
PACKET_VERSION expectedVersion = versionForPacketType(expectedType);
file.write(&expectedType, sizeof(expectedType));
file.write(&expectedVersion, sizeof(expectedType));
file.write(&expectedVersion, sizeof(expectedVersion));
}
OctreeElementBag nodeBag;

View file

@ -190,6 +190,7 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
// NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header!
void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length) {
if (!_shouldSend) {
return; // bail early
}
@ -212,6 +213,8 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned c
return; // bail early
}
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
// We want to filter out edit messages for servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined
// for a different server... So we need to actually manage multiple queued packets... one
@ -229,14 +232,18 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned c
if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) {
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__ << " isMyJurisdiction=" << isMyJurisdiction;
} else {
isMyJurisdiction = false;
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
}
}
if (isMyJurisdiction) {
EditPacketBuffer& packetBuffer = _pendingEditPackets[nodeUUID];
packetBuffer._nodeUUID = nodeUUID;
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
// If we're switching type, then we send the last one and start over
if ((type != packetBuffer._currentType && packetBuffer._currentSize > 0) ||
(packetBuffer._currentSize + length >= _maxPacketSize)) {
@ -255,8 +262,11 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned c
// fixup the buffer for any clock skew
if (node->getClockSkewUsec() != 0) {
adjustEditPacketForClockSkew(codeColorBuffer, length, node->getClockSkewUsec());
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
}
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length);
packetBuffer._currentSize += length;
}
@ -273,12 +283,14 @@ void OctreeEditPacketSender::releaseQueuedMessages() {
} else {
for (std::map<QUuid, EditPacketBuffer>::iterator i = _pendingEditPackets.begin(); i != _pendingEditPackets.end(); i++) {
releaseQueuedPacket(i->second);
//qDebug() << "releaseQueuedMessages() line:" << __LINE__;
}
}
}
void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) {
if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PACKET_TYPE_UNKNOWN) {
//qDebug() << "OctreeEditPacketSender::releaseQueuedPacket() line:" << __LINE__;
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize);
}
packetBuffer._currentSize = 0;

View file

@ -15,8 +15,17 @@
class ParticleNodeData : public OctreeQueryNode {
public:
ParticleNodeData(Node* owningNode) : OctreeQueryNode(owningNode) { };
ParticleNodeData(Node* owningNode) :
OctreeQueryNode(owningNode),
_lastDeletedParticlesSentAt(0) { };
virtual PACKET_TYPE getMyPacketType() const { return PACKET_TYPE_PARTICLE_DATA; }
uint64_t getLastDeletedParticlesSentAt() const { return _lastDeletedParticlesSentAt; }
void setLastDeletedParticlesSentAt(uint64_t sentAt) { _lastDeletedParticlesSentAt = sentAt; }
private:
uint64_t _lastDeletedParticlesSentAt;
};
#endif /* defined(__hifi__ParticleNodeData__) */

View file

@ -6,6 +6,7 @@
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QTimer>
#include <ParticleTree.h>
#include "ParticleServer.h"
@ -36,7 +37,10 @@ Octree* ParticleServer::createTree() {
}
void ParticleServer::beforeRun() {
// nothing special to do...
QTimer* pruneDeletedParticlesTimer = new QTimer(this);
connect(pruneDeletedParticlesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedParticles()));
const int PRUNE_DELETED_PARTICLES_INTERVAL_MSECS = 1 * 1000; // once every second
pruneDeletedParticlesTimer->start(PRUNE_DELETED_PARTICLES_INTERVAL_MSECS);
}
void ParticleServer::particleCreated(const Particle& newParticle, Node* node) {
@ -63,3 +67,72 @@ void ParticleServer::particleCreated(const Particle& newParticle, Node* node) {
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
}
// ParticleServer will use the "special packets" to send list of recently deleted particles
bool ParticleServer::hasSpecialPacketToSend(Node* node) {
bool shouldSendDeletedParticles = false;
// check to see if any new particles have been added since we last sent to this node...
ParticleNodeData* nodeData = static_cast<ParticleNodeData*>(node->getLinkedData());
if (nodeData) {
uint64_t deletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt();
ParticleTree* tree = static_cast<ParticleTree*>(_tree);
shouldSendDeletedParticles = tree->hasParticlesDeletedSince(deletedParticlesSentAt);
}
return shouldSendDeletedParticles;
}
int ParticleServer::sendSpecialPacket(Node* node) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
size_t packetLength = 0;
ParticleNodeData* nodeData = static_cast<ParticleNodeData*>(node->getLinkedData());
if (nodeData) {
uint64_t deletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt();
uint64_t deletePacketSentAt = usecTimestampNow();
ParticleTree* tree = static_cast<ParticleTree*>(_tree);
bool hasMoreToSend = true;
// TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 particles?
while (hasMoreToSend) {
hasMoreToSend = tree->encodeParticlesDeletedSince(deletedParticlesSentAt,
outputBuffer, MAX_PACKET_SIZE, packetLength);
//qDebug() << "sending PACKET_TYPE_PARTICLE_ERASE packetLength:" << packetLength;
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
}
nodeData->setLastDeletedParticlesSentAt(deletePacketSentAt);
}
// TODO: caller is expecting a packetLength, what if we send more than one packet??
return packetLength;
}
void ParticleServer::pruneDeletedParticles() {
ParticleTree* tree = static_cast<ParticleTree*>(_tree);
if (tree->hasAnyDeletedParticles()) {
//qDebug() << "there are some deleted particles to consider...";
uint64_t earliestLastDeletedParticlesSent = usecTimestampNow() + 1; // in the future
foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) {
if (otherNode->getLinkedData()) {
ParticleNodeData* nodeData = static_cast<ParticleNodeData*>(otherNode->getLinkedData());
uint64_t nodeLastDeletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt();
if (nodeLastDeletedParticlesSentAt < earliestLastDeletedParticlesSent) {
earliestLastDeletedParticlesSent = nodeLastDeletedParticlesSentAt;
}
}
}
//qDebug() << "earliestLastDeletedParticlesSent=" << earliestLastDeletedParticlesSent;
tree->forgetParticlesDeletedBefore(earliestLastDeletedParticlesSent);
}
}

View file

@ -18,6 +18,7 @@
/// Handles assignments of type ParticleServer - sending particles to various clients.
class ParticleServer : public OctreeServer, public NewlyCreatedParticleHook {
Q_OBJECT
public:
ParticleServer(const unsigned char* dataBuffer, int numBytes);
~ParticleServer();
@ -33,9 +34,14 @@ public:
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend(Node* node);
virtual int sendSpecialPacket(Node* node);
virtual void particleCreated(const Particle& newParticle, Node* senderNode);
public slots:
void pruneDeletedParticles();
private:
};

View file

@ -22,39 +22,79 @@
#include "ParticlesScriptingInterface.h"
#include "Particle.h"
#include "ParticleTree.h"
uint32_t Particle::_nextID = 0;
VoxelEditPacketSender* Particle::_voxelEditSender = NULL;
ParticleEditPacketSender* Particle::_particleEditSender = NULL;
// for locally created particles
std::map<uint32_t,uint32_t> Particle::_tokenIDsToIDs;
uint32_t Particle::_nextCreatorTokenID = 0;
uint32_t Particle::getIDfromCreatorTokenID(uint32_t creatorTokenID) {
if (_tokenIDsToIDs.find(creatorTokenID) != _tokenIDsToIDs.end()) {
return _tokenIDsToIDs[creatorTokenID];
}
return UNKNOWN_PARTICLE_ID;
}
uint32_t Particle::getNextCreatorTokenID() {
uint32_t creatorTokenID = _nextCreatorTokenID;
_nextCreatorTokenID++;
return creatorTokenID;
}
void Particle::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);
//qDebug() << "handleAddParticleResponse()... particleID=" << particleID << " creatorTokenID=" << creatorTokenID;
// add our token to id mapping
_tokenIDsToIDs[creatorTokenID] = particleID;
}
Particle::Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity,
float damping, bool inHand, QString updateScript, uint32_t id) {
float damping, float lifetime, bool inHand, QString updateScript, uint32_t id) {
init(position, radius, color, velocity, gravity, damping, inHand, updateScript, 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, NOT_IN_HAND, DEFAULT_SCRIPT, NEW_PARTICLE);
DEFAULT_GRAVITY, DEFAULT_DAMPING, DEFAULT_LIFETIME, NOT_IN_HAND, DEFAULT_SCRIPT, NEW_PARTICLE);
}
Particle::~Particle() {
}
void Particle::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity,
float damping, bool inHand, QString updateScript, uint32_t id) {
float damping, float lifetime, bool inHand, QString updateScript, uint32_t id) {
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;
_lastUpdated = now;
_created = now; // will get updated as appropriate in setLifetime()
_created = now; // will get updated as appropriate in setAge()
_position = position;
_radius = radius;
@ -62,6 +102,7 @@ void Particle::init(glm::vec3 position, float radius, rgbColor color, glm::vec3
memcpy(_color, color, sizeof(_color));
_velocity = velocity;
_damping = damping;
_lifetime = lifetime;
_gravity = gravity;
_script = updateScript;
_inHand = inHand;
@ -81,7 +122,7 @@ bool Particle::appendParticleData(OctreePacketData* packetData) const {
//printf("Particle::appendParticleData()... getID()=%d\n", getID());
if (success) {
success = packetData->appendValue(getLifetime());
success = packetData->appendValue(getAge());
}
if (success) {
success = packetData->appendValue(getLastUpdated());
@ -107,9 +148,16 @@ bool Particle::appendParticleData(OctreePacketData* packetData) const {
if (success) {
success = packetData->appendValue(getDamping());
}
if (success) {
success = packetData->appendValue(getLifetime());
}
if (success) {
success = packetData->appendValue(getInHand());
}
if (success) {
success = packetData->appendValue(getShouldDie());
}
if (success) {
uint16_t scriptLength = _script.size() + 1; // include NULL
success = packetData->appendValue(scriptLength);
@ -122,7 +170,7 @@ bool Particle::appendParticleData(OctreePacketData* packetData) const {
int Particle::expectedBytes() {
int expectedBytes = sizeof(uint32_t) // id
+ sizeof(float) // lifetime
+ sizeof(float) // age
+ sizeof(uint64_t) // last updated
+ sizeof(uint64_t) // lasted edited
+ sizeof(float) // radius
@ -131,6 +179,7 @@ int Particle::expectedBytes() {
+ sizeof(glm::vec3) // velocity
+ sizeof(glm::vec3) // gravity
+ sizeof(float) // damping
+ sizeof(float) // lifetime
+ sizeof(bool); // inhand
// potentially more...
return expectedBytes;
@ -145,6 +194,7 @@ int Particle::expectedEditMessageBytes() {
+ sizeof(glm::vec3) // velocity
+ sizeof(glm::vec3) // gravity
+ sizeof(float) // damping
+ sizeof(float) // lifetime
+ sizeof(bool); // inhand
// potentially more...
return expectedBytes;
@ -162,12 +212,12 @@ int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLef
dataAt += sizeof(_id);
bytesRead += sizeof(_id);
// lifetime
float lifetime;
memcpy(&lifetime, dataAt, sizeof(lifetime));
dataAt += sizeof(lifetime);
bytesRead += sizeof(lifetime);
setLifetime(lifetime);
// age
float age;
memcpy(&age, dataAt, sizeof(age));
dataAt += sizeof(age);
bytesRead += sizeof(age);
setAge(age);
// _lastUpdated
memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated));
@ -211,11 +261,21 @@ int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLef
dataAt += sizeof(_damping);
bytesRead += sizeof(_damping);
// lifetime
memcpy(&_lifetime, dataAt, sizeof(_lifetime));
dataAt += sizeof(_lifetime);
bytesRead += sizeof(_lifetime);
// inHand
memcpy(&_inHand, dataAt, sizeof(_inHand));
dataAt += sizeof(_inHand);
bytesRead += sizeof(_inHand);
// shouldDie
memcpy(&_shouldDie, dataAt, sizeof(_shouldDie));
dataAt += sizeof(_shouldDie);
bytesRead += sizeof(_shouldDie);
// script
uint16_t scriptLength;
memcpy(&scriptLength, dataAt, sizeof(scriptLength));
@ -232,7 +292,10 @@ int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLef
}
Particle Particle::fromEditPacket(unsigned char* data, int length, int& processedBytes) {
Particle Particle::fromEditPacket(unsigned char* data, int length, int& processedBytes, ParticleTree* tree) {
//qDebug() << "Particle::fromEditPacket() length=" << length;
Particle newParticle; // id and _lastUpdated will get set here...
unsigned char* dataAt = data;
processedBytes = 0;
@ -241,6 +304,9 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
int octets = numberOfThreeBitSectionsInCode(data);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
//qDebug() << "Particle::fromEditPacket() lengthOfOctcode=" << lengthOfOctcode;
//printOctalCode(data);
// we don't actually do anything with this octcode...
dataAt += lengthOfOctcode;
processedBytes += lengthOfOctcode;
@ -251,22 +317,43 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
dataAt += sizeof(editID);
processedBytes += sizeof(editID);
//qDebug() << "editID:" << editID;
bool isNewParticle = (editID == NEW_PARTICLE);
// special case for handling "new" particles
if (editID == NEW_PARTICLE) {
if (isNewParticle) {
//qDebug() << "editID == NEW_PARTICLE";
// 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
uint32_t creatorTokenID;
memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID));
dataAt += sizeof(creatorTokenID);
processedBytes += sizeof(creatorTokenID);
//qDebug() << "creatorTokenID:" << creatorTokenID;
newParticle.setCreatorTokenID(creatorTokenID);
newParticle._newlyCreated = true;
newParticle.setLifetime(0); // this guy is new!
newParticle.setAge(0); // this guy is new!
} else {
// look up the existing particle
const Particle* existingParticle = tree->findParticleByID(editID, true);
// copy existing properties before over-writing with new properties
if (existingParticle) {
newParticle = *existingParticle;
//qDebug() << "newParticle = *existingParticle... calling debugDump()...";
//existingParticle->debugDump();
}
newParticle._id = editID;
newParticle._newlyCreated = false;
}
// lastEdited
@ -274,42 +361,83 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
dataAt += sizeof(newParticle._lastEdited);
processedBytes += sizeof(newParticle._lastEdited);
// All of the remaining items are optional, and may or may not be included based on their included values in the
// properties included bits
uint16_t packetContainsBits = 0;
if (!isNewParticle) {
memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits));
dataAt += sizeof(packetContainsBits);
processedBytes += sizeof(packetContainsBits);
//qDebug() << "packetContainsBits:" << packetContainsBits;
}
// radius
if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_RADIUS) == PACKET_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)) {
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)) {
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)) {
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)) {
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)) {
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)) {
memcpy(&newParticle._lifetime, dataAt, sizeof(newParticle._lifetime));
dataAt += sizeof(newParticle._lifetime);
processedBytes += sizeof(newParticle._lifetime);
}
// TODO: make inHand and shouldDie into single bits
// inHand
if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_INHAND) == PACKET_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)) {
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)) {
uint16_t scriptLength;
memcpy(&scriptLength, dataAt, sizeof(scriptLength));
dataAt += sizeof(scriptLength);
@ -318,11 +446,13 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
newParticle._script = tempString;
dataAt += scriptLength;
processedBytes += scriptLength;
}
const bool wantDebugging = false;
if (wantDebugging) {
printf("Particle::fromEditPacket()...\n");
printf(" Particle id in packet:%u\n", editID);
qDebug("Particle::fromEditPacket()...");
qDebug() << " Particle id in packet:" << editID;
//qDebug() << " position: " << newParticle._position;
newParticle.debugDump();
}
@ -331,25 +461,36 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
void Particle::debugDump() const {
printf("Particle id :%u\n", _id);
printf(" lifetime:%f\n", getLifetime());
printf(" age:%f\n", getAge());
printf(" edited ago:%f\n", getEditedAgo());
printf(" should die:%s\n", debug::valueOf(getShouldDie()));
printf(" position:%f,%f,%f\n", _position.x, _position.y, _position.z);
printf(" radius:%f\n", getRadius());
printf(" velocity:%f,%f,%f\n", _velocity.x, _velocity.y, _velocity.z);
printf(" gravity:%f,%f,%f\n", _gravity.x, _gravity.y, _gravity.z);
printf(" color:%d,%d,%d\n", _color[0], _color[1], _color[2]);
}
bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details,
bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, ParticleID id, const ParticleProperties& properties,
unsigned char* bufferOut, int sizeIn, int& sizeOut) {
bool success = true; // assume the best
unsigned char* copyAt = bufferOut;
sizeOut = 0;
for (int i = 0; i < count && success; i++) {
// get the octal code for the particle
unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
details[i].position.z, details[i].radius);
// this could be a problem if the caller doesn't include position....
glm::vec3 rootPosition(0);
float rootScale = 0.5f;
unsigned char* octcode = pointToOctalCode(rootPosition.x, rootPosition.y, rootPosition.z, rootScale);
// TODO: Consider this old code... including the correct octree for where the particle will go matters for
// particle servers with different jurisdictions, but for now, we'll send everything to the root, since the
// tree does the right thing...
//
//unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
// details[i].position.z, details[i].radius);
int octets = numberOfThreeBitSectionsInCode(octcode);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
@ -365,79 +506,132 @@ bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count,
sizeOut += lengthOfOctcode;
// Now add our edit content details...
bool isNewParticle = (id.id == NEW_PARTICLE);
// id
memcpy(copyAt, &details[i].id, sizeof(details[i].id));
copyAt += sizeof(details[i].id);
sizeOut += sizeof(details[i].id);
memcpy(copyAt, &id.id, sizeof(id.id));
copyAt += sizeof(id.id);
sizeOut += sizeof(id.id);
// special case for handling "new" particles
if (details[i].id == NEW_PARTICLE) {
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, &details[i].creatorTokenID, sizeof(details[i].creatorTokenID));
copyAt += sizeof(details[i].creatorTokenID);
sizeOut += sizeof(details[i].creatorTokenID);
memcpy(copyAt, &id.creatorTokenID, sizeof(id.creatorTokenID));
copyAt += sizeof(id.creatorTokenID);
sizeOut += sizeof(id.creatorTokenID);
}
// lastEdited
memcpy(copyAt, &details[i].lastEdited, sizeof(details[i].lastEdited));
copyAt += sizeof(details[i].lastEdited);
sizeOut += sizeof(details[i].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);
}
// radius
memcpy(copyAt, &details[i].radius, sizeof(details[i].radius));
copyAt += sizeof(details[i].radius);
sizeOut += sizeof(details[i].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);
}
// position
memcpy(copyAt, &details[i].position, sizeof(details[i].position));
copyAt += sizeof(details[i].position);
sizeOut += sizeof(details[i].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);
}
// color
memcpy(copyAt, details[i].color, sizeof(details[i].color));
copyAt += sizeof(details[i].color);
sizeOut += sizeof(details[i].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);
}
// velocity
memcpy(copyAt, &details[i].velocity, sizeof(details[i].velocity));
copyAt += sizeof(details[i].velocity);
sizeOut += sizeof(details[i].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);
}
// gravity
memcpy(copyAt, &details[i].gravity, sizeof(details[i].gravity));
copyAt += sizeof(details[i].gravity);
sizeOut += sizeof(details[i].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);
}
// damping
memcpy(copyAt, &details[i].damping, sizeof(details[i].damping));
copyAt += sizeof(details[i].damping);
sizeOut += sizeof(details[i].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);
}
// 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);
}
// inHand
memcpy(copyAt, &details[i].inHand, sizeof(details[i].inHand));
copyAt += sizeof(details[i].inHand);
sizeOut += sizeof(details[i].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);
}
// 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);
}
// script
uint16_t scriptLength = details[i].updateScript.size() + 1;
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(details[i].updateScript), scriptLength);
memcpy(copyAt, qPrintable(properties.getScript()), scriptLength);
copyAt += scriptLength;
sizeOut += scriptLength;
}
bool wantDebugging = false;
if (wantDebugging) {
printf("encodeParticleEditMessageDetails()....\n");
printf("Particle id :%u\n", details[i].id);
printf("Particle id :%u\n", id.id);
printf(" nextID:%u\n", _nextID);
}
}
// cleanup
delete[] octcode;
}
//qDebug() << "encoding... sizeOut:" << sizeOut;
return success;
}
@ -533,6 +727,8 @@ void Particle::runUpdateScript() {
particleScriptable.emitUpdate();
// it seems like we may need to send out particle edits if the state of our particle was changed.
if (_voxelEditSender) {
_voxelEditSender->releaseQueuedMessages();
}
@ -563,6 +759,8 @@ void Particle::collisionWithParticle(Particle* other) {
ParticleScriptObject otherParticleScriptable(other);
particleScriptable.emitCollisionWithParticle(&otherParticleScriptable);
// it seems like we may need to send out particle edits if the state of our particle was changed.
if (_voxelEditSender) {
_voxelEditSender->releaseQueuedMessages();
}
@ -596,6 +794,8 @@ void Particle::collisionWithVoxel(VoxelDetail* voxelDetails) {
VoxelDetailScriptObject voxelDetailsScriptable(voxelDetails);
particleScriptable.emitCollisionWithVoxel(&voxelDetailsScriptable);
// it seems like we may need to send out particle edits if the state of our particle was changed.
if (_voxelEditSender) {
_voxelEditSender->releaseQueuedMessages();
}
@ -607,13 +807,351 @@ void Particle::collisionWithVoxel(VoxelDetail* voxelDetails) {
void Particle::setLifetime(float lifetime) {
uint64_t lifetimeInUsecs = lifetime * USECS_PER_SECOND;
_created = usecTimestampNow() - lifetimeInUsecs;
void Particle::setAge(float age) {
uint64_t ageInUsecs = age * USECS_PER_SECOND;
_created = usecTimestampNow() - ageInUsecs;
}
void Particle::copyChangedProperties(const Particle& other) {
float lifetime = getLifetime();
float age = getAge();
*this = other;
setLifetime(lifetime);
setAge(age);
}
ParticleProperties Particle::getProperties() const {
ParticleProperties properties;
properties.copyFromParticle(*this);
return properties;
}
void Particle::setProperties(const ParticleProperties& properties) {
properties.copyToParticle(*this);
}
ParticleProperties::ParticleProperties() :
_position(0),
_color(),
_radius(DEFAULT_RADIUS),
_velocity(0),
_gravity(DEFAULT_GRAVITY),
_damping(DEFAULT_DAMPING),
_lifetime(DEFAULT_LIFETIME),
_script(""),
_inHand(false),
_shouldDie(false),
_lastEdited(usecTimestampNow()),
_positionChanged(false),
_colorChanged(false),
_radiusChanged(false),
_velocityChanged(false),
_gravityChanged(false),
_dampingChanged(false),
_lifetimeChanged(false),
_scriptChanged(false),
_inHandChanged(false),
_shouldDieChanged(false),
_defaultSettings(true)
{
}
uint16_t ParticleProperties::getChangedBits() const {
uint16_t changedBits = 0;
if (_radiusChanged) {
changedBits += PACKET_CONTAINS_RADIUS;
}
if (_positionChanged) {
changedBits += PACKET_CONTAINS_POSITION;
}
if (_colorChanged) {
changedBits += PACKET_CONTAINS_COLOR;
}
if (_velocityChanged) {
changedBits += PACKET_CONTAINS_VELOCITY;
}
if (_gravityChanged) {
changedBits += PACKET_CONTAINS_GRAVITY;
}
if (_dampingChanged) {
changedBits += PACKET_CONTAINS_DAMPING;
}
if (_lifetimeChanged) {
changedBits += PACKET_CONTAINS_LIFETIME;
}
if (_inHandChanged) {
changedBits += PACKET_CONTAINS_INHAND;
}
if (_scriptChanged) {
changedBits += PACKET_CONTAINS_SCRIPT;
}
// how do we want to handle this?
if (_shouldDieChanged) {
changedBits += PACKET_CONTAINS_SHOULDDIE;
}
return changedBits;
}
QScriptValue ParticleProperties::copyToScriptValue(QScriptEngine* engine) const {
QScriptValue properties = engine->newObject();
QScriptValue position = vec3toScriptValue(engine, _position);
properties.setProperty("position", position);
QScriptValue color = xColorToScriptValue(engine, _color);
properties.setProperty("color", color);
properties.setProperty("radius", _radius);
QScriptValue velocity = vec3toScriptValue(engine, _velocity);
properties.setProperty("velocity", velocity);
QScriptValue gravity = vec3toScriptValue(engine, _gravity);
properties.setProperty("gravity", gravity);
properties.setProperty("damping", _damping);
properties.setProperty("lifetime", _lifetime);
properties.setProperty("script", _script);
properties.setProperty("inHand", _inHand);
properties.setProperty("shouldDie", _shouldDie);
return properties;
}
void ParticleProperties::copyFromScriptValue(const QScriptValue &object) {
QScriptValue position = object.property("position");
if (position.isValid()) {
QScriptValue x = position.property("x");
QScriptValue y = position.property("y");
QScriptValue z = position.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
if (_defaultSettings || newPosition != _position) {
_position = newPosition;
_positionChanged = true;
}
}
}
QScriptValue color = object.property("color");
if (color.isValid()) {
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
xColor newColor;
newColor.red = red.toVariant().toInt();
newColor.green = green.toVariant().toInt();
newColor.blue = blue.toVariant().toInt();
if (_defaultSettings || (newColor.red != _color.red ||
newColor.green != _color.green ||
newColor.blue != _color.blue)) {
_color = newColor;
_colorChanged = true;
}
}
}
QScriptValue radius = object.property("radius");
if (radius.isValid()) {
float newRadius;
newRadius = radius.toVariant().toFloat();
if (_defaultSettings || newRadius != _radius) {
_radius = newRadius;
_radiusChanged = true;
}
}
QScriptValue velocity = object.property("velocity");
if (velocity.isValid()) {
QScriptValue x = velocity.property("x");
QScriptValue y = velocity.property("y");
QScriptValue z = velocity.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newVelocity;
newVelocity.x = x.toVariant().toFloat();
newVelocity.y = y.toVariant().toFloat();
newVelocity.z = z.toVariant().toFloat();
if (_defaultSettings || newVelocity != _velocity) {
_velocity = newVelocity;
_velocityChanged = true;
}
}
}
QScriptValue gravity = object.property("gravity");
if (gravity.isValid()) {
QScriptValue x = gravity.property("x");
QScriptValue y = gravity.property("y");
QScriptValue z = gravity.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newGravity;
newGravity.x = x.toVariant().toFloat();
newGravity.y = y.toVariant().toFloat();
newGravity.z = z.toVariant().toFloat();
if (_defaultSettings || newGravity != _gravity) {
_gravity = newGravity;
_gravityChanged = true;
}
}
}
QScriptValue damping = object.property("damping");
if (damping.isValid()) {
float newDamping;
newDamping = damping.toVariant().toFloat();
if (_defaultSettings || newDamping != _damping) {
_damping = newDamping;
_dampingChanged = true;
}
}
QScriptValue lifetime = object.property("lifetime");
if (lifetime.isValid()) {
float newLifetime;
newLifetime = lifetime.toVariant().toFloat();
if (_defaultSettings || newLifetime != _lifetime) {
_lifetime = newLifetime;
_lifetimeChanged = true;
}
}
QScriptValue script = object.property("script");
if (script.isValid()) {
QString newScript;
newScript = script.toVariant().toString();
if (_defaultSettings || newScript != _script) {
_script = newScript;
_scriptChanged = true;
}
}
QScriptValue inHand = object.property("inHand");
if (inHand.isValid()) {
bool newInHand;
newInHand = inHand.toVariant().toBool();
if (_defaultSettings || newInHand != _inHand) {
_inHand = newInHand;
_inHandChanged = true;
}
}
QScriptValue shouldDie = object.property("shouldDie");
if (shouldDie.isValid()) {
bool newShouldDie;
newShouldDie = shouldDie.toVariant().toBool();
if (_defaultSettings || newShouldDie != _shouldDie) {
_shouldDie = newShouldDie;
_shouldDieChanged = true;
}
}
_lastEdited = usecTimestampNow();
}
void ParticleProperties::copyToParticle(Particle& particle) const {
if (_positionChanged) {
particle.setPosition(_position / (float) TREE_SCALE);
}
if (_colorChanged) {
particle.setColor(_color);
}
if (_radiusChanged) {
particle.setRadius(_radius / (float) TREE_SCALE);
}
if (_velocityChanged) {
particle.setVelocity(_velocity / (float) TREE_SCALE);
}
if (_gravityChanged) {
particle.setGravity(_gravity / (float) TREE_SCALE);
}
if (_dampingChanged) {
particle.setDamping(_damping);
}
if (_lifetimeChanged) {
particle.setLifetime(_lifetime);
}
if (_scriptChanged) {
particle.setScript(_script);
}
if (_inHandChanged) {
particle.setInHand(_inHand);
}
if (_shouldDieChanged) {
particle.setShouldDie(_shouldDie);
}
}
void ParticleProperties::copyFromParticle(const Particle& particle) {
_position = particle.getPosition() * (float) TREE_SCALE;
_color = particle.getXColor();
_radius = particle.getRadius() * (float) TREE_SCALE;
_velocity = particle.getVelocity() * (float) TREE_SCALE;
_gravity = particle.getGravity() * (float) TREE_SCALE;
_damping = particle.getDamping();
_lifetime = particle.getLifetime();
_script = particle.getScript();
_inHand = particle.getInHand();
_shouldDie = particle.getShouldDie();
_positionChanged = false;
_colorChanged = false;
_radiusChanged = false;
_velocityChanged = false;
_gravityChanged = false;
_dampingChanged = false;
_lifetimeChanged = false;
_scriptChanged = false;
_inHandChanged = false;
_shouldDieChanged = false;
_defaultSettings = false;
}
QScriptValue ParticlePropertiesToScriptValue(QScriptEngine* engine, const ParticleProperties& properties) {
return properties.copyToScriptValue(engine);
}
void ParticlePropertiesFromScriptValue(const QScriptValue &object, ParticleProperties& properties) {
properties.copyFromScriptValue(object);
}
QScriptValue ParticleIDtoScriptValue(QScriptEngine* engine, const ParticleID& id) {
QScriptValue obj = engine->newObject();
obj.setProperty("id", id.id);
obj.setProperty("creatorTokenID", id.creatorTokenID);
obj.setProperty("isKnownID", id.isKnownID);
return obj;
}
void ParticleIDfromScriptValue(const QScriptValue &object, ParticleID& id) {
id.id = object.property("id").toVariant().toUInt();
id.creatorTokenID = object.property("creatorTokenID").toVariant().toUInt();
id.isKnownID = object.property("isKnownID").toVariant().toBool();
}

View file

@ -23,57 +23,169 @@ class VoxelsScriptingInterface;
class ParticlesScriptingInterface;
class VoxelEditPacketSender;
class ParticleEditPacketSender;
class ParticleProperties;
class Particle;
class ParticleTree;
const uint32_t NEW_PARTICLE = 0xFFFFFFFF;
const uint32_t UNKNOWN_TOKEN = 0xFFFFFFFF;
const uint32_t UNKNOWN_PARTICLE_ID = 0xFFFFFFFF;
class ParticleDetail {
public:
uint32_t id;
uint64_t lastEdited;
glm::vec3 position;
float radius;
rgbColor color;
glm::vec3 velocity;
glm::vec3 gravity;
float damping;
bool inHand;
QString updateScript;
uint32_t creatorTokenID;
};
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 float DEFAULT_LIFETIME = 60.0f * 60.0f * 24.0f; // particles live for 1 day by default
const float DEFAULT_DAMPING = 0.99f;
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 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
/// all units for position, velocity, gravity, radius, etc are in meter units
class ParticleProperties {
public:
ParticleProperties();
QScriptValue copyToScriptValue(QScriptEngine* engine) const;
void copyFromScriptValue(const QScriptValue& object);
void copyToParticle(Particle& particle) const;
void copyFromParticle(const Particle& particle);
const glm::vec3& getPosition() const { return _position; }
xColor getColor() const { return _color; }
float getRadius() const { return _radius; }
const glm::vec3& getVelocity() const { return _velocity; }
const glm::vec3& getGravity() const { return _gravity; }
float getDamping() const { return _damping; }
float getLifetime() const { return _lifetime; }
const QString& getScript() const { return _script; }
bool getInHand() const { return _inHand; }
bool getShouldDie() const { return _shouldDie; }
uint64_t getLastEdited() const { return _lastEdited; }
uint16_t getChangedBits() const;
/// set position in meter units
void setPosition(const glm::vec3& value) { _position = value; _positionChanged = true; }
/// set velocity in meter units
void setVelocity(const glm::vec3& value) { _velocity = value; _velocityChanged = true; }
void setColor(const xColor& value) { _color = value; _colorChanged = true; }
void setRadius(float value) { _radius = value; _radiusChanged = true; }
/// set gravity in meter units
void setGravity(const glm::vec3& value) { _gravity = value; _gravityChanged = true; }
void setInHand(bool inHand) { _inHand = inHand; _inHandChanged = true; }
void setDamping(float value) { _damping = value; _dampingChanged = true; }
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; }
void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; }
void setScript(QString updateScript) { _script = updateScript; _scriptChanged = true; }
private:
glm::vec3 _position;
xColor _color;
float _radius;
glm::vec3 _velocity;
glm::vec3 _gravity;
float _damping;
float _lifetime;
QString _script;
bool _inHand;
bool _shouldDie;
uint64_t _lastEdited;
bool _positionChanged;
bool _colorChanged;
bool _radiusChanged;
bool _velocityChanged;
bool _gravityChanged;
bool _dampingChanged;
bool _lifetimeChanged;
bool _scriptChanged;
bool _inHandChanged;
bool _shouldDieChanged;
bool _defaultSettings;
};
Q_DECLARE_METATYPE(ParticleProperties);
QScriptValue ParticlePropertiesToScriptValue(QScriptEngine* engine, const ParticleProperties& properties);
void ParticlePropertiesFromScriptValue(const QScriptValue &object, ParticleProperties& properties);
/// Abstract ID for editing particles. Used in Particle JS API - When particles are created in the JS api, they are given a
/// local creatorTokenID, the actual id for the particle is not known until the server responds to the creator with the
/// correct mapping. This class works with the scripting API an allows the developer to edit particles they created.
class ParticleID {
public:
ParticleID() :
id(NEW_PARTICLE), creatorTokenID(UNKNOWN_TOKEN), isKnownID(false) { };
ParticleID(uint32_t id, uint32_t creatorTokenID, bool isKnownID) :
id(id), creatorTokenID(creatorTokenID), isKnownID(isKnownID) { };
uint32_t id;
uint32_t creatorTokenID;
bool isKnownID;
};
Q_DECLARE_METATYPE(ParticleID);
QScriptValue ParticleIDtoScriptValue(QScriptEngine* engine, const ParticleID& properties);
void ParticleIDfromScriptValue(const QScriptValue &object, ParticleID& properties);
/// Particle class - this is the actual particle class.
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, bool inHand = NOT_IN_HAND,
QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE);
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);
/// 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);
static Particle fromEditPacket(unsigned char* data, int length, int& processedBytes, ParticleTree* tree);
virtual ~Particle();
virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
glm::vec3 gravity = DEFAULT_GRAVITY, float damping = DEFAULT_DAMPING, bool inHand = NOT_IN_HAND,
QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE);
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);
/// get position in domain scale units (0.0 - 1.0)
const glm::vec3& getPosition() const { return _position; }
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
/// get radius in domain scale units (0.0 - 1.0)
float getRadius() const { return _radius; }
float getMass() const { return _mass; }
/// get velocity in domain scale units (0.0 - 1.0)
const glm::vec3& getVelocity() const { return _velocity; }
/// get gravity in domain scale units (0.0 - 1.0)
const glm::vec3& getGravity() const { return _gravity; }
bool getInHand() const { return _inHand; }
float getDamping() const { return _damping; }
float getLifetime() const { return _lifetime; }
ParticleProperties getProperties() const;
/// The last updated/simulated time of this particle from the time perspective of the authoritative server/source
uint64_t getLastUpdated() const { return _lastUpdated; }
@ -82,7 +194,7 @@ public:
uint64_t getLastEdited() const { return _lastEdited; }
/// lifetime of the particle in seconds
float getLifetime() const { return static_cast<float>(usecTimestampNow() - _created) / static_cast<float>(USECS_PER_SECOND); }
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; }
bool getShouldDie() const { return _shouldDie; }
@ -90,7 +202,10 @@ public:
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
bool isNewlyCreated() const { return _newlyCreated; }
/// set position in domain scale units (0.0 - 1.0)
void setPosition(const glm::vec3& value) { _position = value; }
/// set velocity in domain scale units (0.0 - 1.0)
void setVelocity(const glm::vec3& value) { _velocity = value; }
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
@ -98,21 +213,26 @@ public:
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
/// set radius in domain scale units (0.0 - 1.0)
void setRadius(float value) { _radius = value; }
void setMass(float value);
/// set gravity in domain scale units (0.0 - 1.0)
void setGravity(const glm::vec3& value) { _gravity = value; }
void setInHand(bool inHand) { _inHand = inHand; }
void setDamping(float value) { _damping = value; }
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; }
void setLifetime(float value) { _lifetime = value; }
void setScript(QString updateScript) { _script = updateScript; }
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
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, int count, const ParticleDetail* details,
static bool encodeParticleEditMessageDetails(PACKET_TYPE command, ParticleID id, const ParticleProperties& details,
unsigned char* bufferOut, int sizeIn, int& sizeOut);
static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew);
@ -136,6 +256,11 @@ public:
{ _particleEditSender = senderInterface; }
// these methods allow you to create particles, and later edit them.
static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID);
static uint32_t getNextCreatorTokenID();
static void handleAddParticleResponse(unsigned char* packetData , int packetLength);
protected:
static VoxelEditPacketSender* _voxelEditSender;
static ParticleEditPacketSender* _particleEditSender;
@ -146,7 +271,7 @@ protected:
static QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color);
static void xColorFromScriptValue(const QScriptValue &object, xColor& color);
void setLifetime(float lifetime);
void setAge(float age);
glm::vec3 _position;
rgbColor _color;
@ -158,6 +283,7 @@ protected:
bool _shouldDie;
glm::vec3 _gravity;
float _damping;
float _lifetime;
QString _script;
bool _inHand;
@ -169,8 +295,14 @@ protected:
// this doesn't go on the wire, we send it as lifetime
uint64_t _created;
// used by the static interfaces for creator token ids
static uint32_t _nextCreatorTokenID;
static std::map<uint32_t,uint32_t> _tokenIDsToIDs;
};
/// Scriptable interface to a single Particle object. Used exclusively in the JavaScript API for interacting with single
/// Particles.
class ParticleScriptObject : public QObject {
Q_OBJECT
public:
@ -182,23 +314,44 @@ public:
public slots:
unsigned int getID() const { return _particle->getID(); }
glm::vec3 getPosition() const { return _particle->getPosition(); }
glm::vec3 getVelocity() const { return _particle->getVelocity(); }
xColor getColor() const { return _particle->getXColor(); }
glm::vec3 getGravity() const { return _particle->getGravity(); }
float getDamping() const { return _particle->getDamping(); }
float getRadius() const { return _particle->getRadius(); }
bool getShouldDie() { return _particle->getShouldDie(); }
float getLifetime() const { return _particle->getLifetime(); }
void setPosition(glm::vec3 value) { _particle->setPosition(value); }
void setVelocity(glm::vec3 value) { _particle->setVelocity(value); }
void setGravity(glm::vec3 value) { _particle->setGravity(value); }
/// get position in meter units
glm::vec3 getPosition() const { return _particle->getPosition() * (float)TREE_SCALE; }
/// get velocity in meter units
glm::vec3 getVelocity() const { return _particle->getVelocity() * (float)TREE_SCALE; }
xColor getColor() const { return _particle->getXColor(); }
/// get gravity in meter units
glm::vec3 getGravity() const { return _particle->getGravity() * (float)TREE_SCALE; }
float getDamping() const { return _particle->getDamping(); }
/// get radius in meter units
float getRadius() const { return _particle->getRadius() * (float)TREE_SCALE; }
bool getShouldDie() { return _particle->getShouldDie(); }
float getAge() const { return _particle->getAge(); }
float getLifetime() const { return _particle->getLifetime(); }
ParticleProperties getProperties() const { return _particle->getProperties(); }
/// set position in meter units
void setPosition(glm::vec3 value) { _particle->setPosition(value / (float)TREE_SCALE); }
/// set velocity in meter units
void setVelocity(glm::vec3 value) { _particle->setVelocity(value / (float)TREE_SCALE); }
/// set gravity in meter units
void setGravity(glm::vec3 value) { _particle->setGravity(value / (float)TREE_SCALE); }
void setDamping(float value) { _particle->setDamping(value); }
void setColor(xColor value) { _particle->setColor(value); }
void setRadius(float value) { _particle->setRadius(value); }
/// set radius in meter units
void setRadius(float value) { _particle->setRadius(value / (float)TREE_SCALE); }
void setShouldDie(bool value) { _particle->setShouldDie(value); }
void setScript(const QString& script) { _particle->setScript(script); }
void setLifetime(float value) const { return _particle->setLifetime(value); }
void setProperties(const ParticleProperties& properties) { return _particle->setProperties(properties); }
signals:
void update();
@ -210,4 +363,5 @@ private:
};
#endif /* defined(__hifi__Particle__) */

View file

@ -120,14 +120,16 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA)
particleA->setVelocity(particleA->getVelocity() - axialVelocity * (2.0f * massB / totalMass));
ParticleEditHandle particleEditHandle(_packetSender, _particles, particleA->getID());
particleEditHandle.updateParticle(particleA->getPosition(), particleA->getRadius(), particleA->getXColor(), particleA->getVelocity(),
particleA->getGravity(), particleA->getDamping(), particleA->getInHand(), particleA->getScript());
particleEditHandle.updateParticle(particleA->getPosition(), particleA->getRadius(), particleA->getXColor(),
particleA->getVelocity(), particleA->getGravity(), particleA->getDamping(), particleA->getLifetime(),
particleA->getInHand(), particleA->getScript());
particleB->setVelocity(particleB->getVelocity() + axialVelocity * (2.0f * massA / totalMass));
ParticleEditHandle penetratedparticleEditHandle(_packetSender, _particles, particleB->getID());
penetratedparticleEditHandle.updateParticle(particleB->getPosition(), particleB->getRadius(), particleB->getXColor(), particleB->getVelocity(),
particleB->getGravity(), particleB->getDamping(), particleB->getInHand(), particleB->getScript());
penetratedparticleEditHandle.updateParticle(particleB->getPosition(), particleB->getRadius(),
particleB->getXColor(), particleB->getVelocity(), particleB->getGravity(), particleB->getDamping(),
particleB->getLifetime(), particleB->getInHand(), particleB->getScript());
penetration /= (float)(TREE_SCALE);
updateCollisionSound(particleA, penetration, COLLISION_FREQUENCY);
@ -256,7 +258,8 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, float elast
ParticleEditHandle particleEditHandle(_packetSender, _particles, particle->getID());
particleEditHandle.updateParticle(position, particle->getRadius(), particle->getXColor(), velocity,
particle->getGravity(), particle->getDamping(), particle->getInHand(), particle->getScript());
particle->getGravity(), particle->getDamping(), particle->getLifetime(),
particle->getInHand(), particle->getScript());
}

View file

@ -13,12 +13,10 @@
#include "ParticleTree.h"
std::map<uint32_t,ParticleEditHandle*> ParticleEditHandle::_allHandles;
uint32_t ParticleEditHandle::_nextCreatorTokenID = 0;
ParticleEditHandle::ParticleEditHandle(ParticleEditPacketSender* packetSender, ParticleTree* localTree, uint32_t id) {
if (id == NEW_PARTICLE) {
_creatorTokenID = _nextCreatorTokenID;
_nextCreatorTokenID++;
_creatorTokenID = Particle::getNextCreatorTokenID();
_id = NEW_PARTICLE;
_isKnownID = false;
_allHandles[_creatorTokenID] = this;
@ -41,16 +39,17 @@ ParticleEditHandle::~ParticleEditHandle() {
}
void ParticleEditHandle::createParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, bool inHand, QString updateScript) {
glm::vec3 gravity, float damping, float lifetime, bool inHand, QString updateScript) {
// setup a ParticleDetail struct with the data
/****
uint64_t now = usecTimestampNow();
ParticleDetail addParticleDetail = { NEW_PARTICLE, now,
position, radius, {color.red, color.green, color.blue },
velocity, gravity, damping, inHand, updateScript, _creatorTokenID };
velocity, gravity, damping, lifetime, inHand, updateScript, _creatorTokenID };
// queue the packet
_packetSender->queueParticleEditMessages(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, 1, &addParticleDetail);
_packetSender->queueParticleEditMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, 1, &addParticleDetail);
// release them
_packetSender->releaseQueuedMessages();
@ -60,20 +59,23 @@ void ParticleEditHandle::createParticle(glm::vec3 position, float radius, xColor
// we can't really do this here, because if we create a particle locally, we'll get a ghost particle
// because we can't really handle updating/deleting it locally
}
****/
}
bool ParticleEditHandle::updateParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, bool inHand, QString updateScript) {
glm::vec3 gravity, float damping, float lifetime, bool inHand, QString updateScript) {
if (!isKnownID()) {
return false; // not allowed until we know the id
}
// setup a ParticleDetail struct with the data
/****
uint64_t now = usecTimestampNow();
ParticleDetail newParticleDetail = { _id, now,
position, radius, {color.red, color.green, color.blue },
velocity, gravity, damping, inHand, updateScript, _creatorTokenID };
velocity, gravity, damping, lifetime, inHand, updateScript, _creatorTokenID };
// queue the packet
_packetSender->queueParticleEditMessages(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, 1, &newParticleDetail);
@ -84,9 +86,10 @@ bool ParticleEditHandle::updateParticle(glm::vec3 position, float radius, xColor
// if we have a local tree, also update it...
if (_localTree) {
rgbColor rcolor = {color.red, color.green, color.blue };
Particle tempParticle(position, radius, rcolor, velocity, gravity, damping, inHand, updateScript, _id);
Particle tempParticle(position, radius, rcolor, velocity, gravity, damping, lifetime, inHand, updateScript, _id);
_localTree->storeParticle(tempParticle);
}
***/
return true;
}

View file

@ -35,17 +35,16 @@ public:
bool isKnownID() const { return _isKnownID; }
void createParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, bool inHand, QString updateScript);
glm::vec3 gravity, float damping, float lifetime, bool inHand, QString updateScript);
bool updateParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, bool inHand, QString updateScript);
glm::vec3 gravity, float damping, float lifetime, bool inHand, QString updateScript);
static void handleAddResponse(unsigned char* packetData , int packetLength);
private:
uint32_t _creatorTokenID;
uint32_t _id;
bool _isKnownID;
static uint32_t _nextCreatorTokenID;
static std::map<uint32_t,ParticleEditHandle*> _allHandles;
ParticleEditPacketSender* _packetSender;
ParticleTree* _localTree;

View file

@ -16,7 +16,7 @@
#include "Particle.h"
void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, const ParticleDetail& detail) {
void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties) {
// allows app to disable sending if for example voxels have been disabled
if (!_shouldSend) {
return; // bail early
@ -26,7 +26,7 @@ void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, const P
int sizeOut = 0;
// This encodes the voxel edit message into a buffer...
if (Particle::encodeParticleEditMessageDetails(type, 1, &detail, &bufferOut[0], _maxPacketSize, sizeOut)){
if (Particle::encodeParticleEditMessageDetails(type, particleID, properties, &bufferOut[0], _maxPacketSize, sizeOut)){
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
// jurisdictions for processing
if (!serversExist()) {
@ -42,19 +42,17 @@ void ParticleEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeC
}
void ParticleEditPacketSender::queueParticleEditMessages(PACKET_TYPE type, int numberOfDetails, ParticleDetail* details) {
void ParticleEditPacketSender::queueParticleEditMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties) {
if (!_shouldSend) {
return; // bail early
}
for (int i = 0; i < numberOfDetails; i++) {
// use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize
static unsigned char bufferOut[MAX_PACKET_SIZE];
int sizeOut = 0;
if (Particle::encodeParticleEditMessageDetails(type, 1, &details[i], &bufferOut[0], _maxPacketSize, sizeOut)) {
if (Particle::encodeParticleEditMessageDetails(type, particleID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(type, bufferOut, sizeOut);
}
}
}

View file

@ -21,12 +21,12 @@ public:
~ParticleEditPacketSender() { }
/// Send particle add message immediately
void sendEditParticleMessage(PACKET_TYPE type, const ParticleDetail& detail);
void sendEditParticleMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties);
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
void queueParticleEditMessages(PACKET_TYPE type, int numberOfDetails, ParticleDetail* details);
void queueParticleEditMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties);
// My server type is the particle server
virtual unsigned char getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; }

View file

@ -29,6 +29,48 @@ bool ParticleTree::handlesEditPacketType(PACKET_TYPE packetType) const {
return false;
}
class FindAndDeleteParticlesArgs {
public:
QList<uint32_t> _idsToDelete;
};
bool ParticleTree::findAndDeleteOperation(OctreeElement* element, void* extraData) {
//qDebug() << "findAndDeleteOperation()";
FindAndDeleteParticlesArgs* args = static_cast< FindAndDeleteParticlesArgs*>(extraData);
// if we've found and deleted all our target particles, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
//qDebug() << "findAndDeleteOperation() args->_idsToDelete.size():" << args->_idsToDelete.size();
for (QList<uint32_t>::iterator it = args->_idsToDelete.begin(); it != args->_idsToDelete.end(); it++) {
uint32_t particleID = *it;
//qDebug() << "findAndDeleteOperation() particleID:" << particleID;
if (particleTreeElement->removeParticleWithID(particleID)) {
// if the particle was in this element, then remove it from our search list.
//qDebug() << "findAndDeleteOperation() it = args->_idsToDelete.erase(it)";
it = args->_idsToDelete.erase(it);
}
if (it == args->_idsToDelete.end()) {
//qDebug() << "findAndDeleteOperation() breaking";
break;
}
}
// if we've found and deleted all our target particles, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
return true;
}
class FindAndUpdateParticleArgs {
public:
@ -55,7 +97,7 @@ void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) {
// if we didn't find it in the tree, then store it...
if (!args.found) {
glm::vec3 position = particle.getPosition();
float size = particle.getRadius();
float size = std::max(MINIMUM_PARTICLE_ELEMENT_SIZE, particle.getRadius());
ParticleTreeElement* element = (ParticleTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size);
element->storeParticle(particle, senderNode);
@ -127,6 +169,8 @@ public:
bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) {
//qDebug() << "ParticleTree::findByIDOperation()....";
FindByIDArgs* args = static_cast<FindByIDArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
@ -148,11 +192,17 @@ bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) {
}
const Particle* ParticleTree::findParticleByID(uint32_t id) {
const Particle* ParticleTree::findParticleByID(uint32_t id, bool alreadyLocked) {
FindByIDArgs args = { id, false, NULL };
if (!alreadyLocked) {
//qDebug() << "ParticleTree::findParticleByID().... about to call lockForRead()....";
lockForRead();
//qDebug() << "ParticleTree::findParticleByID().... after call lockForRead()....";
}
recurseTreeWithOperation(findByIDOperation, &args);
if (!alreadyLocked) {
unlock();
}
return args.foundParticle;
}
@ -164,13 +214,17 @@ int ParticleTree::processEditPacketData(PACKET_TYPE packetType, unsigned char* p
// we handle these types of "edit" packets
switch (packetType) {
case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: {
Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes);
//qDebug() << " got PACKET_TYPE_PARTICLE_ADD_OR_EDIT... ";
Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes, this);
storeParticle(newParticle, senderNode);
if (newParticle.isNewlyCreated()) {
notifyNewlyCreatedParticle(newParticle, senderNode);
}
//qDebug() << " DONE... PACKET_TYPE_PARTICLE_ADD_OR_EDIT... ";
} break;
// TODO: wire in support here for server to get PACKET_TYPE_PARTICLE_ERASE messages
// instead of using PACKET_TYPE_PARTICLE_ADD_OR_EDIT messages to delete particles
case PACKET_TYPE_PARTICLE_ERASE: {
processedBytes = 0;
} break;
@ -238,6 +292,12 @@ void ParticleTree::update() {
if (!shouldDie && treeBounds.contains(args._movingParticles[i].getPosition())) {
storeParticle(args._movingParticles[i]);
} else {
uint32_t particleID = args._movingParticles[i].getID();
uint64_t deletedAt = usecTimestampNow();
_recentlyDeletedParticlesLock.lockForWrite();
_recentlyDeletedParticleIDs.insert(deletedAt, particleID);
_recentlyDeletedParticlesLock.unlock();
}
}
@ -246,3 +306,143 @@ void ParticleTree::update() {
}
bool ParticleTree::hasParticlesDeletedSince(uint64_t sinceTime) {
// we can probably leverage the ordered nature of QMultiMap to do this quickly...
bool hasSomethingNewer = false;
_recentlyDeletedParticlesLock.lockForRead();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
while (iterator != _recentlyDeletedParticleIDs.constEnd()) {
//qDebug() << "considering... time/key:" << iterator.key();
if (iterator.key() > sinceTime) {
//qDebug() << "YES newer... time/key:" << iterator.key();
hasSomethingNewer = true;
}
++iterator;
}
_recentlyDeletedParticlesLock.unlock();
return hasSomethingNewer;
}
// sinceTime is an in/out parameter - it will be side effected with the last time sent out
bool ParticleTree::encodeParticlesDeletedSince(uint64_t& sinceTime, unsigned char* outputBuffer, size_t maxLength,
size_t& outputLength) {
bool hasMoreToSend = true;
unsigned char* copyAt = outputBuffer;
size_t numBytesPacketHeader = populateTypeAndVersion(outputBuffer, PACKET_TYPE_PARTICLE_ERASE);
copyAt += numBytesPacketHeader;
outputLength = numBytesPacketHeader;
uint16_t numberOfIds = 0; // placeholder for now
unsigned char* numberOfIDsAt = copyAt;
memcpy(copyAt, &numberOfIds, sizeof(numberOfIds));
copyAt += sizeof(numberOfIds);
outputLength += sizeof(numberOfIds);
// we keep a multi map of particle IDs to timestamps, we only want to include the particle IDs that have been
// deleted since we last sent to this node
_recentlyDeletedParticlesLock.lockForRead();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
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);
outputLength += sizeof(particleID);
numberOfIds++;
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
break;
}
}
}
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
// let our caller know how far we got
sinceTime = iterator.key();
break;
}
++iterator;
}
// if we got to the end, then we're done sending
if (iterator == _recentlyDeletedParticleIDs.constEnd()) {
hasMoreToSend = false;
}
_recentlyDeletedParticlesLock.unlock();
// replace the correct count for ids included
memcpy(numberOfIDsAt, &numberOfIds, sizeof(numberOfIds));
return hasMoreToSend;
}
// called by the server when it knows all nodes have been sent deleted packets
void ParticleTree::forgetParticlesDeletedBefore(uint64_t sinceTime) {
_recentlyDeletedParticlesLock.lockForWrite();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
while (iterator != _recentlyDeletedParticleIDs.constEnd()) {
//qDebug() << "considering... time/key:" << iterator.key();
if (iterator.key() <= sinceTime) {
//qDebug() << "YES older... time/key:" << iterator.key();
_recentlyDeletedParticleIDs.remove(iterator.key());
}
++iterator;
}
_recentlyDeletedParticlesLock.unlock();
}
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;
size_t packetLength = dataByteArray.size();
size_t numBytesPacketHeader = numBytesForPacketHeader(packetData);
size_t processedBytes = numBytesPacketHeader;
dataAt += numBytesPacketHeader;
uint16_t numberOfIds = 0; // placeholder for now
memcpy(&numberOfIds, dataAt, sizeof(numberOfIds));
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
}
uint32_t particleID = 0; // placeholder for now
memcpy(&particleID, dataAt, sizeof(particleID));
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

@ -41,11 +41,18 @@ public:
void storeParticle(const Particle& particle, Node* senderNode = NULL);
const Particle* findClosestParticle(glm::vec3 position, float targetRadius);
const Particle* findParticleByID(uint32_t id);
const Particle* findParticleByID(uint32_t id, bool alreadyLocked = false);
void addNewlyCreatedHook(NewlyCreatedParticleHook* hook);
void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook);
bool hasAnyDeletedParticles() const { return _recentlyDeletedParticleIDs.size() > 0; }
bool hasParticlesDeletedSince(uint64_t sinceTime);
bool encodeParticlesDeletedSince(uint64_t& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength);
void forgetParticlesDeletedBefore(uint64_t sinceTime);
void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode);
private:
static bool updateOperation(OctreeElement* element, void* extraData);
@ -53,11 +60,16 @@ private:
static bool findNearPointOperation(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);
void notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode);
QReadWriteLock _newlyCreatedHooksLock;
std::vector<NewlyCreatedParticleHook*> _newlyCreatedHooks;
QReadWriteLock _recentlyDeletedParticlesLock;
QMultiMap<uint64_t, uint32_t> _recentlyDeletedParticleIDs;
};
#endif /* defined(__hifi__ParticleTree__) */

View file

@ -92,7 +92,6 @@ void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) {
bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const {
QList<Particle>::iterator particleItr = _particles->begin();
QList<Particle>::const_iterator particleEnd = _particles->end();
while(particleItr != particleEnd) {
@ -199,6 +198,20 @@ const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const {
return foundParticle;
}
bool ParticleTreeElement::removeParticleWithID(uint32_t id) {
bool foundParticle = false;
uint16_t numberOfParticles = _particles->size();
for (uint16_t i = 0; i < numberOfParticles; i++) {
if ((*_particles)[i].getID() == id) {
foundParticle = true;
_particles->removeAt(i);
break;
}
}
return foundParticle;
}
int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args) {

View file

@ -91,6 +91,8 @@ public:
const Particle* getClosestParticle(glm::vec3 position) const;
const Particle* getParticleWithID(uint32_t id) const;
bool removeParticleWithID(uint32_t id);
protected:
virtual void init(unsigned char * octalCode);

View file

@ -7,42 +7,112 @@
//
#include "ParticlesScriptingInterface.h"
#include "ParticleTree.h"
void ParticlesScriptingInterface::queueParticleMessage(PACKET_TYPE packetType, ParticleDetail& particleDetails) {
getParticlePacketSender()->queueParticleEditMessages(packetType, 1, &particleDetails);
ParticlesScriptingInterface::ParticlesScriptingInterface() :
_nextCreatorTokenID(0),
_particleTree(NULL)
{
}
unsigned int ParticlesScriptingInterface::queueParticleAdd(glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, bool inHand, QString script) {
void ParticlesScriptingInterface::queueParticleMessage(PACKET_TYPE packetType,
ParticleID particleID, const ParticleProperties& properties) {
getParticlePacketSender()->queueParticleEditMessage(packetType, particleID, properties);
}
ParticleID ParticlesScriptingInterface::addParticle(const ParticleProperties& properties) {
// The application will keep track of creatorTokenID
uint32_t creatorTokenID = _nextCreatorTokenID;
_nextCreatorTokenID++;
uint32_t creatorTokenID = Particle::getNextCreatorTokenID();
// setup a ParticleDetail struct with the data
uint64_t now = usecTimestampNow();
ParticleDetail addParticleDetail = { NEW_PARTICLE, now,
position, radius, {color.red, color.green, color.blue }, velocity,
gravity, damping, inHand, script, creatorTokenID };
ParticleID id(NEW_PARTICLE, creatorTokenID, false );
// queue the packet
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, addParticleDetail);
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, id, properties);
return creatorTokenID;
return id;
}
void ParticlesScriptingInterface::editParticle(ParticleID particleID, const ParticleProperties& properties) {
uint32_t actualID = particleID.id;
if (!particleID.isKnownID) {
actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID);
// hmmm... we kind of want to bail if someone attempts to edit an unknown
if (actualID == UNKNOWN_PARTICLE_ID) {
//qDebug() << "ParticlesScriptingInterface::editParticle()... BAILING!!! particleID.creatorTokenID="
// << particleID.creatorTokenID;
return; // 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();
}
}
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties);
}
void ParticlesScriptingInterface::queueParticleEdit(unsigned int particleID, glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, bool inHand, QString script) {
// TODO: This deleteParticle() method uses the PACKET_TYPE_PARTICLE_ADD_OR_EDIT message to send
// a changed particle with a shouldDie() property set to true. This works and is currently the only
// way to tell the particle server to delete a particle. But we should change this to use the PACKET_TYPE_PARTICLE_ERASE
// message which takes a list of particle id's to delete.
void ParticlesScriptingInterface::deleteParticle(ParticleID particleID) {
// setup a ParticleDetail struct with the data
uint64_t now = usecTimestampNow();
ParticleDetail editParticleDetail = { particleID, now,
position, radius, {color.red, color.green, color.blue }, velocity,
gravity, damping, inHand, script, UNKNOWN_TOKEN };
// setup properties to kill the particle
ParticleProperties properties;
properties.setShouldDie(true);
// queue the packet
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, editParticleDetail);
uint32_t actualID = particleID.id;
if (!particleID.isKnownID) {
actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID);
// hmmm... we kind of want to bail if someone attempts to edit an unknown
if (actualID == UNKNOWN_PARTICLE_ID) {
//qDebug() << "ParticlesScriptingInterface::deleteParticle(), bailing - unknown particle...";
return; // bailing early
}
}
particleID.id = actualID;
particleID.isKnownID = true;
//qDebug() << "ParticlesScriptingInterface::deleteParticle(), queueParticleMessage......";
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties);
}
ParticleID ParticlesScriptingInterface::findClosestParticle(const glm::vec3& center, float radius) const {
ParticleID result(UNKNOWN_PARTICLE_ID, UNKNOWN_TOKEN, false);
if (_particleTree) {
const Particle* closestParticle = _particleTree->findClosestParticle(center/(float)TREE_SCALE,
radius/(float)TREE_SCALE);
if (closestParticle) {
result.id = closestParticle->getID();
result.isKnownID = true;
}
}
return result;
}

View file

@ -19,23 +19,26 @@
class ParticlesScriptingInterface : public OctreeScriptingInterface {
Q_OBJECT
public:
ParticlesScriptingInterface();
ParticleEditPacketSender* getParticlePacketSender() const { return (ParticleEditPacketSender*)getPacketSender(); }
virtual NODE_TYPE getServerNodeType() const { return NODE_TYPE_PARTICLE_SERVER; }
virtual OctreeEditPacketSender* createPacketSender() { return new ParticleEditPacketSender(); }
public slots:
/// queues the creation of a Particle which will be sent by calling process on the PacketSender
/// returns the creatorTokenID for the newly created particle
unsigned int queueParticleAdd(glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, bool inHand, QString script);
void setParticleTree(ParticleTree* particleTree) { _particleTree = particleTree; }
ParticleTree* getParticleTree(ParticleTree*) { return _particleTree; }
void queueParticleEdit(unsigned int particleID, glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, bool inHand, QString script);
public slots:
ParticleID addParticle(const ParticleProperties& properties);
void editParticle(ParticleID particleID, const ParticleProperties& properties);
void deleteParticle(ParticleID particleID);
ParticleID findClosestParticle(const glm::vec3& center, float radius) const;
private:
void queueParticleMessage(PACKET_TYPE packetType, ParticleDetail& particleDetails);
void queueParticleMessage(PACKET_TYPE packetType, ParticleID particleID, const ParticleProperties& properties);
uint32_t _nextCreatorTokenID;
ParticleTree* _particleTree;
};
#endif /* defined(__hifi__ParticlesScriptingInterface__) */

View file

@ -115,6 +115,8 @@ void ScriptEngine::init() {
// register meta-type for glm::vec3 conversions
registerMetaTypes(&_engine);
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
QScriptValue agentValue = _engine.newQObject(this);
_engine.globalObject().setProperty("Agent", agentValue);
@ -167,7 +169,6 @@ void ScriptEngine::evaluate() {
}
QScriptValue result = _engine.evaluate(_scriptContents);
qDebug("Evaluated script.");
if (_engine.hasUncaughtException()) {
int line = _engine.uncaughtExceptionLineNumber();
@ -182,8 +183,6 @@ void ScriptEngine::run() {
_isRunning = true;
QScriptValue result = _engine.evaluate(_scriptContents);
qDebug("Evaluated script");
if (_engine.hasUncaughtException()) {
int line = _engine.uncaughtExceptionLineNumber();
qDebug() << "Uncaught exception at line" << line << ":" << result.toString();

View file

@ -45,10 +45,10 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) {
return 1;
case PACKET_TYPE_PARTICLE_ADD_OR_EDIT:
return 2;
return 4;
case PACKET_TYPE_PARTICLE_DATA:
return 5;
return 8;
case PACKET_TYPE_PING_REPLY:
return 1;

View file

@ -56,3 +56,4 @@ void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
color.green = object.property("green").toVariant().toInt();
color.blue = object.property("blue").toVariant().toInt();
}

View file

@ -32,7 +32,7 @@ Octree* VoxelServer::createTree() {
return new VoxelTree(true);
}
bool VoxelServer::hasSpecialPacketToSend() {
bool VoxelServer::hasSpecialPacketToSend(Node* node) {
bool shouldSendEnvironments = _sendEnvironments && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, OCTREE_SEND_INTERVAL_USECS);
return shouldSendEnvironments;
}

View file

@ -44,7 +44,7 @@ public:
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend();
virtual bool hasSpecialPacketToSend(Node* node);
virtual int sendSpecialPacket(Node* node);

View file

@ -12,27 +12,31 @@ void VoxelsScriptingInterface::queueVoxelAdd(PACKET_TYPE addPacketType, VoxelDet
getVoxelPacketSender()->queueVoxelEditMessages(addPacketType, 1, &addVoxelDetails);
}
void VoxelsScriptingInterface::queueVoxelAdd(float x, float y, float z, float scale, uchar red, uchar green, uchar blue) {
void VoxelsScriptingInterface::setVoxelNonDestructive(float x, float y, float z, float scale,
uchar red, uchar green, uchar blue) {
// setup a VoxelDetail struct with the data
VoxelDetail addVoxelDetail = {x, y, z, scale, red, green, blue};
VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE,
scale / (float)TREE_SCALE, red, green, blue};
// queue the packet
queueVoxelAdd(PACKET_TYPE_VOXEL_SET, addVoxelDetail);
}
void VoxelsScriptingInterface::queueDestructiveVoxelAdd(float x, float y, float z, float scale,
void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale,
uchar red, uchar green, uchar blue) {
// setup a VoxelDetail struct with the data
VoxelDetail addVoxelDetail = {x, y, z, scale, red, green, blue};
VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE,
scale / (float)TREE_SCALE, red, green, blue};
// queue the destructive add
queueVoxelAdd(PACKET_TYPE_VOXEL_SET_DESTRUCTIVE, addVoxelDetail);
}
void VoxelsScriptingInterface::queueVoxelDelete(float x, float y, float z, float scale) {
void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale) {
// setup a VoxelDetail struct with data
VoxelDetail deleteVoxelDetail = {x, y, z, scale, 0, 0, 0};
VoxelDetail deleteVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE,
scale / (float)TREE_SCALE, 0, 0, 0};
getVoxelPacketSender()->queueVoxelEditMessages(PACKET_TYPE_VOXEL_ERASE, 1, &deleteVoxelDetail);
}

View file

@ -13,6 +13,8 @@
#include <JurisdictionListener.h>
#include <OctreeScriptingInterface.h>
#include "VoxelConstants.h"
#include "VoxelEditPacketSender.h"
/// handles scripting of voxel commands from JS passed to assigned clients
@ -26,31 +28,31 @@ public:
public slots:
/// queues the creation of a voxel which will be sent by calling process on the PacketSender
/// \param x the x-coordinate of the voxel (in VS space)
/// \param y the y-coordinate of the voxel (in VS space)
/// \param z the z-coordinate of the voxel (in VS space)
/// \param scale the scale of the voxel (in VS space)
/// \param x the x-coordinate of the voxel (in meter units)
/// \param y the y-coordinate of the voxel (in meter units)
/// \param z the z-coordinate of the voxel (in meter units)
/// \param scale the scale of the voxel (in meter units)
/// \param red the R value for RGB color of voxel
/// \param green the G value for RGB color of voxel
/// \param blue the B value for RGB color of voxel
void queueVoxelAdd(float x, float y, float z, float scale, uchar red, uchar green, uchar blue);
void setVoxelNonDestructive(float x, float y, float z, float scale, uchar red, uchar green, uchar blue);
/// queues the destructive creation of a voxel which will be sent by calling process on the PacketSender
/// \param x the x-coordinate of the voxel (in VS space)
/// \param y the y-coordinate of the voxel (in VS space)
/// \param z the z-coordinate of the voxel (in VS space)
/// \param scale the scale of the voxel (in VS space)
/// \param x the x-coordinate of the voxel (in meter units)
/// \param y the y-coordinate of the voxel (in meter units)
/// \param z the z-coordinate of the voxel (in meter units)
/// \param scale the scale of the voxel (in meter units)
/// \param red the R value for RGB color of voxel
/// \param green the G value for RGB color of voxel
/// \param blue the B value for RGB color of voxel
void queueDestructiveVoxelAdd(float x, float y, float z, float scale, uchar red, uchar green, uchar blue);
void setVoxel(float x, float y, float z, float scale, uchar red, uchar green, uchar blue);
/// queues the deletion of a voxel, sent by calling process on the PacketSender
/// \param x the x-coordinate of the voxel (in VS space)
/// \param y the y-coordinate of the voxel (in VS space)
/// \param z the z-coordinate of the voxel (in VS space)
/// \param scale the scale of the voxel (in VS space)
void queueVoxelDelete(float x, float y, float z, float scale);
/// \param x the x-coordinate of the voxel (in meter units)
/// \param y the y-coordinate of the voxel (in meter units)
/// \param z the z-coordinate of the voxel (in meter units)
/// \param scale the scale of the voxel (in meter units)
void eraseVoxel(float x, float y, float z, float scale);
private:
void queueVoxelAdd(PACKET_TYPE addPacketType, VoxelDetail& addVoxelDetails);
@ -62,9 +64,11 @@ public:
VoxelDetailScriptObject(VoxelDetail* voxelDetail) { _voxelDetail = voxelDetail; }
public slots:
glm::vec3 getPosition() const { return glm::vec3(_voxelDetail->x, _voxelDetail->y, _voxelDetail->z); }
/// position in meter units
glm::vec3 getPosition() const { return glm::vec3(_voxelDetail->x, _voxelDetail->y, _voxelDetail->z) * (float)TREE_SCALE; }
xColor getColor() const { xColor color = { _voxelDetail->red, _voxelDetail->green, _voxelDetail->blue }; return color; }
float getScale() const { return _voxelDetail->s; }
/// scale in meter units
float getScale() const { return _voxelDetail->s * (float)TREE_SCALE; }
private:
VoxelDetail* _voxelDetail;