diff --git a/examples/collidingParticles.js b/examples/collidingParticles.js index f53933871f..cf1fce5660 100644 --- a/examples/collidingParticles.js +++ b/examples/collidingParticles.js @@ -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 }; + + var properties = { + position: startPosition, + radius: largeRadius, + color: colorGreen, + velocity: verySlow, + gravity: gravity, + damping: damping, + inHand: false, + script: scriptA + }; - Particles.queueParticleAdd(startPosition, largeRadius, colorGreen, verySlow, gravity, damping, false, 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++; diff --git a/examples/editParticleExample.js b/examples/editParticleExample.js new file mode 100644 index 0000000000..4eb5dfe907 --- /dev/null +++ b/examples/editParticleExample.js @@ -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); + diff --git a/examples/fountain.js b/examples/fountain.js index b15557c8ee..86c125a834 100644 --- a/examples/fountain.js +++ b/examples/fountain.js @@ -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 diff --git a/examples/gun.js b/examples/gun.js index c9c0d6d8da..30d2b41449 100644 --- a/examples/gun.js +++ b/examples/gun.js @@ -24,7 +24,7 @@ function checkController() { var numberOfTriggers = Controller.getNumberOfTriggers(); var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - + // this is expected for hydras if (numberOfTriggers == 2 && controllersPerTrigger == 2) { for (var t = 0; t < numberOfTriggers; t++) { @@ -43,7 +43,7 @@ function checkController() { shootABullet = true; } } - + if (shootABullet) { var palmController = t * controllersPerTrigger; var palmPosition = Controller.getSpatialControlPosition(palmController); @@ -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,17 +79,24 @@ 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 }); } } } } - + // register the call back so it fires before each data send Agent.willSendVisualDataCallback.connect(checkController); diff --git a/examples/movingVoxel.js b/examples/movingVoxel.js index 44ab418aaf..0aadf7b30c 100644 --- a/examples/movingVoxel.js +++ b/examples/movingVoxel.js @@ -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; diff --git a/examples/paintGun.js b/examples/paintGun.js index f75df58314..0cbe3ff366 100644 --- a/examples/paintGun.js +++ b/examples/paintGun.js @@ -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 } + ); } } } diff --git a/examples/toyball.js b/examples/toyball.js new file mode 100644 index 0000000000..1682cbe3d4 --- /dev/null +++ b/examples/toyball.js @@ -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); diff --git a/examples/voxelBird.js b/examples/voxelBird.js index bc4cac0247..54c0129045 100644 --- a/examples/voxelBird.js +++ b/examples/voxelBird.js @@ -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, - blinkColor.r, blinkColor.g, blinkColor.b); + 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; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d4a3e7118d..de1bbb059c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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, @@ -2053,14 +1997,14 @@ void Application::updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot, glm::vec3& glm::vec3 rayOrigin, rayDirection; _viewFrustum.computePickRay(0.5f, 0.5f, rayOrigin, rayDirection); lookAtSpot = rayOrigin + rayDirection * FAR_AWAY_STARE; - + } else if (!_lookatTargetAvatar) { if (_isHoverVoxel) { // Look at the hovered voxel lookAtSpot = getMouseVoxelWorldCoordinates(_hoverVoxel); } else { - // Just look in direction of the mouse ray + // Just look in direction of the mouse ray lookAtSpot = lookAtRayOrigin + lookAtRayDirection * FAR_AWAY_STARE; } } @@ -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"); diff --git a/interface/src/Application.h b/interface/src/Application.h index 63a3209a9f..6be01db39f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -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, diff --git a/interface/src/BuckyBalls.cpp b/interface/src/BuckyBalls.cpp index 318126cc03..fec77d97f3 100644 --- a/interface/src/BuckyBalls.cpp +++ b/interface/src/BuckyBalls.cpp @@ -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; } diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/ParticleTreeRenderer.cpp index 81ef7f1221..aa4978891f 100644 --- a/interface/src/ParticleTreeRenderer.cpp +++ b/interface/src/ParticleTreeRenderer.cpp @@ -11,7 +11,7 @@ #include "ParticleTreeRenderer.h" -ParticleTreeRenderer::ParticleTreeRenderer() : +ParticleTreeRenderer::ParticleTreeRenderer() : OctreeRenderer() { } @@ -31,13 +31,13 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg // actually render it here... // we need to iterate the actual particles of the element ParticleTreeElement* particleTreeElement = (ParticleTreeElement*)element; - + const QList& particles = particleTreeElement->getParticles(); - + uint16_t numberOfParticles = particles.size(); - + bool drawAsSphere = true; - + for (uint16_t i = 0; i < numberOfParticles; i++) { const Particle& particle = particles[i]; // render particle aspoints @@ -46,7 +46,7 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg float sphereRadius = particle.getRadius() * (float)TREE_SCALE; args->_renderedItems++; - + if (drawAsSphere) { glPushMatrix(); glTranslatef(position.x, position.y, position.z); @@ -60,3 +60,8 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg } } } + +void ParticleTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, + Node* sourceNode) { + static_cast(_tree)->processEraseMessage(dataByteArray, senderSockAddr, sourceNode); +} diff --git a/interface/src/ParticleTreeRenderer.h b/interface/src/ParticleTreeRenderer.h index ba11799776..1c76cf623d 100644 --- a/interface/src/ParticleTreeRenderer.h +++ b/interface/src/ParticleTreeRenderer.h @@ -37,6 +37,8 @@ public: ParticleTree* getTree() { return (ParticleTree*)_tree; } + void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode); + protected: }; diff --git a/interface/src/VoxelPacketProcessor.cpp b/interface/src/VoxelPacketProcessor.cpp index 9c2ec2a539..fe50271ca4 100644 --- a/interface/src/VoxelPacketProcessor.cpp +++ b/interface/src/VoxelPacketProcessor.cpp @@ -17,7 +17,7 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, unsigned char* packetData, ssize_t packetLength) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "VoxelPacketProcessor::processPacket()"); - + const int WAY_BEHIND = 300; if (packetsToProcessCount() > WAY_BEHIND && Application::getInstance()->getLogger()->extraDebugging()) { @@ -27,19 +27,19 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns Application* app = Application::getInstance(); bool wasStatsPacket = false; - - + + // check to see if the UI thread asked us to kill the voxel tree. since we're the only thread allowed to do that if (app->_wantToKillLocalVoxels) { app->_voxels.killLocalVoxels(); app->_wantToKillLocalVoxels = false; } - + // note: PACKET_TYPE_OCTREE_STATS can have PACKET_TYPE_VOXEL_DATA // immediately following them inside the same packet. So, we process the PACKET_TYPE_OCTREE_STATS first // then process any remaining bytes as if it was another packet if (packetData[0] == PACKET_TYPE_OCTREE_STATS) { - + int statsMessageLength = app->parseOctreeStats(packetData, messageLength, senderSockAddr); wasStatsPacket = true; if (messageLength > statsMessageLength) { @@ -56,20 +56,25 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { app->trackIncomingVoxelPacket(packetData, messageLength, senderSockAddr, wasStatsPacket); - + SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr); 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()); } break; - + case PACKET_TYPE_ENVIRONMENT_DATA: { app->_environment.parseData(senderSockAddr, packetData, messageLength); } break; - + default : { app->_voxels.setDataSourceUUID(serverNode->getUUID()); app->_voxels.parseData(packetData, messageLength); diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index e5be719b40..51f1bc10de 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -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) { @@ -512,7 +334,6 @@ void Hand::render(bool isMine) { glPopMatrix(); } } - 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); diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index 0552c021f1..3c8ec2d562 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -19,7 +19,6 @@ #include #include #include -#include #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 diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 4faf408fca..0f1e393db9 100755 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -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; } diff --git a/libraries/octree-server/src/OctreeInboundPacketProcessor.cpp b/libraries/octree-server/src/OctreeInboundPacketProcessor.cpp index e6a89c76ed..e537838649 100644 --- a/libraries/octree-server/src/OctreeInboundPacketProcessor.cpp +++ b/libraries/octree-server/src/OctreeInboundPacketProcessor.cpp @@ -34,7 +34,7 @@ void OctreeInboundPacketProcessor::resetStats() { _totalLockWaitTime = 0; _totalElementsInPacket = 0; _totalPackets = 0; - + _singleSenderStats.clear(); } @@ -43,14 +43,14 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA unsigned char* packetData, ssize_t packetLength) { bool debugProcessPacket = _myServer->wantsVerboseDebug(); - + if (debugProcessPacket) { printf("OctreeInboundPacketProcessor::processPacket() packetData=%p packetLength=%ld\n", packetData, packetLength); } int numBytesPacketHeader = numBytesForPacketHeader(packetData); - - + + // Ask our tree subclass if it can handle the incoming packet... PACKET_TYPE packetType = packetData[0]; if (_myServer->getOctree()->handlesEditPacketType(packetType)) { @@ -58,7 +58,7 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA _receivedPacketCount++; SharedNodePointer senderNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr); - + unsigned short int sequence = (*((unsigned short int*)(packetData + numBytesPacketHeader))); uint64_t sentAt = (*((uint64_t*)(packetData + numBytesPacketHeader + sizeof(sequence)))); uint64_t arrivedAt = usecTimestampNow(); @@ -66,10 +66,10 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA int editsInPacket = 0; uint64_t processTime = 0; uint64_t lockWaitTime = 0; - + if (_myServer->wantsDebugReceiving()) { - qDebug() << "PROCESSING THREAD: got '" << packetType << "' packet - " << _receivedPacketCount - << " command from client receivedBytes=" << packetLength + qDebug() << "PROCESSING THREAD: got '" << packetType << "' packet - " << _receivedPacketCount + << " command from client receivedBytes=" << packetLength << " sequence=" << sequence << " transitTime=" << transitTime << " usecs"; } int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt); @@ -86,7 +86,7 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA uint64_t startLock = usecTimestampNow(); _myServer->getOctree()->lockForWrite(); uint64_t startProcess = usecTimestampNow(); - int editDataBytesRead = _myServer->getOctree()->processEditPacketData(packetType, + int editDataBytesRead = _myServer->getOctree()->processEditPacketData(packetType, packetData, packetLength, editData, maxSize, senderNode.data()); @@ -129,15 +129,15 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA } } -void OctreeInboundPacketProcessor::trackInboundPackets(const QUuid& nodeUUID, int sequence, uint64_t transitTime, +void OctreeInboundPacketProcessor::trackInboundPackets(const QUuid& nodeUUID, int sequence, uint64_t transitTime, int editsInPacket, uint64_t processTime, uint64_t lockWaitTime) { - + _totalTransitTime += transitTime; _totalProcessTime += processTime; _totalLockWaitTime += lockWaitTime; _totalElementsInPacket += editsInPacket; _totalPackets++; - + // find the individual senders stats and track them there too... // see if this is the first we've heard of this node... if (_singleSenderStats.find(nodeUUID) == _singleSenderStats.end()) { @@ -162,7 +162,7 @@ void OctreeInboundPacketProcessor::trackInboundPackets(const QUuid& nodeUUID, in SingleSenderStats::SingleSenderStats() { - _totalTransitTime = 0; + _totalTransitTime = 0; _totalProcessTime = 0; _totalLockWaitTime = 0; _totalElementsInPacket = 0; diff --git a/libraries/octree-server/src/OctreeSendThread.cpp b/libraries/octree-server/src/OctreeSendThread.cpp index 6aba5fafaf..a12a633e58 100644 --- a/libraries/octree-server/src/OctreeSendThread.cpp +++ b/libraries/octree-server/src/OctreeSendThread.cpp @@ -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++; diff --git a/libraries/octree-server/src/OctreeServer.cpp b/libraries/octree-server/src/OctreeServer.cpp index 55bc77f1bf..4a0ad0d827 100644 --- a/libraries/octree-server/src/OctreeServer.cpp +++ b/libraries/octree-server/src/OctreeServer.cpp @@ -100,27 +100,27 @@ void OctreeServer::initHTTPManager(int port) { } bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) { - + #ifdef FORCE_CRASH if (connection->requestOperation() == QNetworkAccessManager::GetOperation && path == "/force_crash") { - + qDebug() << "About to force a crash!"; - + int foo; int* forceCrash = &foo; - + QString responseString("forcing a crash..."); connection->respond(HTTPConnection::StatusCode200, qPrintable(responseString)); - + delete[] forceCrash; - + return true; } #endif - + bool showStats = false; - + if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (path == "/") { showStats = true; @@ -129,28 +129,28 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString& showStats = true; } } - + if (showStats) { uint64_t checkSum; // return a 200 QString statsString("\r\n
\r\n");
         statsString += QString("Your %1 Server is running... [RELOAD]\r\n").arg(getMyServerName());
-        
+
         tm* localtm = localtime(&_started);
         const int MAX_TIME_LENGTH = 128;
         char buffer[MAX_TIME_LENGTH];
         strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm);
         statsString += QString("Running since: %1").arg(buffer);
-        
+
         // Convert now to tm struct for UTC
         tm* gmtm = gmtime(&_started);
         if (gmtm) {
             strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", gmtm);
             statsString += (QString(" [%1 UTM] ").arg(buffer));
         }
-        
+
         statsString += "\r\n";
-        
+
         uint64_t now  = usecTimestampNow();
         const int USECS_PER_MSEC = 1000;
         uint64_t msecsElapsed = (now - _startedUSecs) / USECS_PER_MSEC;
@@ -158,13 +158,13 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
         const int SECS_PER_MIN = 60;
         const int MIN_PER_HOUR = 60;
         const int MSECS_PER_MIN = MSECS_PER_SEC * SECS_PER_MIN;
-        
+
         float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
         int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
         int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
-        
+
         statsString += "Uptime: ";
-        
+
         if (hours > 0) {
             statsString += QString("%1 hour%2").arg(hours).arg((hours > 1) ? "s" : "");
         }
@@ -175,7 +175,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
             statsString += QString().sprintf("%.3f seconds", seconds);
         }
         statsString += "\r\n\r\n";
-        
+
         // display voxel file load time
         if (isInitialLoadComplete()) {
             if (isPersistEnabled()) {
@@ -183,14 +183,14 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
             } else {
                 statsString += QString("%1 File Persist Disabled...\r\n").arg(getMyServerName());
             }
-            
+
             statsString += "\r\n";
-            
+
             uint64_t msecsElapsed = getLoadElapsedTime() / USECS_PER_MSEC;;
             float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
             int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
             int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
-            
+
             statsString += QString("%1 File Load Took").arg(getMyServerName());
             if (hours > 0) {
                 statsString += QString("%1 hour%2").arg(hours).arg((hours > 1) ? "s" : "");
@@ -202,25 +202,25 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
                 statsString += QString().sprintf("%.3f seconds", seconds);
             }
             statsString += "\r\n";
-            
+
         } else {
             statsString += "Voxels not yet loaded...\r\n";
         }
-        
+
         statsString += "\r\n\r\n";
         statsString += "Configuration:\r\n";
-        
+
         for (int i = 1; i < _argc; i++) {
             statsString += _argv[i];
         }
         statsString += "\r\n"; //one to end the config line
         statsString += "\r\n\r\n"; // two more for spacing
-        
+
         // display scene stats
         unsigned long nodeCount = OctreeElement::getNodeCount();
         unsigned long internalNodeCount = OctreeElement::getInternalNodeCount();
         unsigned long leafNodeCount = OctreeElement::getLeafNodeCount();
-        
+
         QLocale locale(QLocale::English);
         const float AS_PERCENT = 100.0;
         statsString += "Current Nodes in scene:\r\n";
@@ -234,7 +234,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
                                          ((float)leafNodeCount / (float)nodeCount) * AS_PERCENT);
         statsString += "\r\n";
         statsString += "\r\n";
-        
+
         // display outbound packet stats
         statsString += QString("%1 Outbound Packet Statistics...\r\n").arg(getMyServerName());
         uint64_t totalOutboundPackets = OctreeSendThread::_totalPackets;
@@ -243,7 +243,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
         uint64_t totalBytesOfOctalCodes = OctreePacketData::getTotalBytesOfOctalCodes();
         uint64_t totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks();
         uint64_t totalBytesOfColor = OctreePacketData::getTotalBytesOfColor();
-        
+
         const int COLUMN_WIDTH = 10;
         statsString += QString("           Total Outbound Packets: %1 packets\r\n")
             .arg(locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' '));
@@ -260,10 +260,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
         statsString += QString().sprintf("                Total Color Bytes: %s bytes (%5.2f%%)\r\n",
             locale.toString((uint)totalBytesOfColor).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
             ((float)totalBytesOfColor / (float)totalOutboundBytes) * AS_PERCENT);
-        
+
         statsString += "\r\n";
         statsString += "\r\n";
-        
+
         // display inbound packet stats
         statsString += QString().sprintf("%s Edit Statistics... [RESET]\r\n",
                                          getMyServerName());
@@ -274,9 +274,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
         uint64_t averageLockWaitTimePerElement = _octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
         uint64_t totalElementsProcessed = _octreeInboundPacketProcessor->getTotalElementsProcessed();
         uint64_t totalPacketsProcessed = _octreeInboundPacketProcessor->getTotalPacketsProcessed();
-        
+
         float averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed;
-        
+
         statsString += QString("           Total Inbound Packets: %1 packets\r\n")
             .arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' '));
         statsString += QString("          Total Inbound Elements: %1 elements\r\n")
@@ -292,18 +292,18 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
             .arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
         statsString += QString("  Average Wait Lock Time/Element: %1 usecs\r\n")
             .arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
-        
-        
+
+
         int senderNumber = 0;
         NodeToSenderStatsMap& allSenderStats = _octreeInboundPacketProcessor->getSingleSenderStats();
         for (NodeToSenderStatsMapIterator i = allSenderStats.begin(); i != allSenderStats.end(); i++) {
             senderNumber++;
             QUuid senderID = i->first;
             SingleSenderStats& senderStats = i->second;
-            
+
             statsString += QString("\r\n             Stats for sender %1 uuid: %2\r\n")
                 .arg(senderNumber).arg(senderID.toString());
-            
+
             averageTransitTimePerPacket = senderStats.getAverageTransitTimePerPacket();
             averageProcessTimePerPacket = senderStats.getAverageProcessTimePerPacket();
             averageLockWaitTimePerPacket = senderStats.getAverageLockWaitTimePerPacket();
@@ -311,9 +311,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
             averageLockWaitTimePerElement = senderStats.getAverageLockWaitTimePerElement();
             totalElementsProcessed = senderStats.getTotalElementsProcessed();
             totalPacketsProcessed = senderStats.getTotalPacketsProcessed();
-            
+
             averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed;
-            
+
             statsString += QString("               Total Inbound Packets: %1 packets\r\n")
                 .arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' '));
             statsString += QString("              Total Inbound Elements: %1 elements\r\n")
@@ -330,16 +330,16 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
                 .arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
             statsString += QString("      Average Wait Lock Time/Element: %1 usecs\r\n")
                 .arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
-            
+
         }
-        
+
         statsString += "\r\n\r\n";
-        
+
         // display memory usage stats
         statsString += "Current Memory Usage Statistics\r\n";
         statsString += QString().sprintf("\r\nOctreeElement size... %ld bytes\r\n", sizeof(OctreeElement));
         statsString += "\r\n";
-        
+
         const char* memoryScaleLabel;
         const float MEGABYTES = 1000000.f;
         const float GIGABYTES = 1000000000.f;
@@ -351,7 +351,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
             memoryScaleLabel = "GB";
             memoryScale = GIGABYTES;
         }
-        
+
         statsString += QString().sprintf("Element Node Memory Usage:       %8.2f %s\r\n",
                                          OctreeElement::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel);
         statsString += QString().sprintf("Octcode Memory Usage:            %8.2f %s\r\n",
@@ -362,7 +362,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
         statsString += QString().sprintf("                         Total:  %8.2f %s\r\n",
                                          OctreeElement::getTotalMemoryUsage() / memoryScale, memoryScaleLabel);
         statsString += "\r\n";
-        
+
         statsString += "OctreeElement Children Population Statistics...\r\n";
         checkSum = 0;
         for (int i=0; i <= NUMBER_OF_CHILDREN; i++) {
@@ -374,11 +374,11 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
         statsString += "                                ----------------------\r\n";
         statsString += QString("                    Total:      %1 nodes\r\n")
             .arg(locale.toString((uint)checkSum).rightJustified(16, ' '));
-        
+
 #ifdef BLENDED_UNION_CHILDREN
         statsString += "\r\n";
         statsString += "OctreeElement Children Encoding Statistics...\r\n";
-        
+
         statsString += QString().sprintf("    Single or No Children:      %10.llu nodes (%5.2f%%)\r\n",
                                          OctreeElement::getSingleChildrenCount(),
                                          ((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT));
@@ -397,31 +397,31 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
         statsString += QString().sprintf("    Children as External Array: %10.llu nodes (%5.2f%%)\r\n",
                                          OctreeElement::getExternalChildrenCount(),
                                          ((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT);
-        
+
         checkSum = OctreeElement::getSingleChildrenCount() +
         OctreeElement::getTwoChildrenOffsetCount() + OctreeElement::getTwoChildrenExternalCount() +
         OctreeElement::getThreeChildrenOffsetCount() + OctreeElement::getThreeChildrenExternalCount() +
         OctreeElement::getExternalChildrenCount();
-        
+
         statsString += "                                ----------------\r\n";
         statsString += QString().sprintf("                         Total: %10.llu nodes\r\n", checkSum);
         statsString += QString().sprintf("                      Expected: %10.lu nodes\r\n", nodeCount);
-        
+
         statsString += "\r\n";
         statsString += "In other news....\r\n";
-        
+
         statsString += QString().sprintf("could store 4 children internally:     %10.llu nodes\r\n",
                                          OctreeElement::getCouldStoreFourChildrenInternally());
         statsString += QString().sprintf("could NOT store 4 children internally: %10.llu nodes\r\n",
                                          OctreeElement::getCouldNotStoreFourChildrenInternally());
 #endif
-        
+
         statsString += "\r\n\r\n";
         statsString += "
\r\n"; statsString += "
"; - + connection->respond(HTTPConnection::StatusCode200, qPrintable(statsString), "text/html"); - + return true; } else { // have HTTPManager attempt to process this request from the document_root diff --git a/libraries/octree-server/src/OctreeServer.h b/libraries/octree-server/src/OctreeServer.h index 7e9538f38c..c68f09b6e0 100644 --- a/libraries/octree-server/src/OctreeServer.h +++ b/libraries/octree-server/src/OctreeServer.h @@ -27,7 +27,7 @@ /// Handles assignments of type OctreeServer - sending octrees to various clients. class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler { Q_OBJECT -public: +public: OctreeServer(const unsigned char* dataBuffer, int numBytes); ~OctreeServer(); @@ -58,27 +58,27 @@ 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); - + bool handleHTTPRequest(HTTPConnection* connection, const QString& path); public slots: /// runs the voxel server assignment void run(); void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr); - + void nodeKilled(SharedNodePointer node); protected: void parsePayload(); void initHTTPManager(int port); - + int _argc; const char** _argv; char** _parsedArgV; - + HTTPManager* _httpManager; char _persistFilename[MAX_FILENAME_LENGTH]; @@ -94,9 +94,9 @@ protected: OctreePersistThread* _persistThread; static OctreeServer* _instance; - + time_t _started; - uint64_t _startedUSecs; + uint64_t _startedUSecs; }; #endif // __octree_server__OctreeServer__ diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index c090ea1512..dad97b18cd 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -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; diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index ff6a1ded61..a3d4e7862b 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -21,14 +21,14 @@ EditPacketBuffer::EditPacketBuffer(PACKET_TYPE type, unsigned char* buffer, ssiz _nodeUUID = nodeUUID; _currentType = type; _currentSize = length; - memcpy(_currentBuffer, buffer, length); + memcpy(_currentBuffer, buffer, length); }; -const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND; +const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND; -OctreeEditPacketSender::OctreeEditPacketSender(PacketSenderNotify* notify) : - PacketSender(notify), +OctreeEditPacketSender::OctreeEditPacketSender(PacketSenderNotify* notify) : + PacketSender(notify), _shouldSend(true), _maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES), _releaseQueuedMessagesPending(false), @@ -57,7 +57,7 @@ bool OctreeEditPacketSender::serversExist() const { bool hasServers = false; bool atLeastOnJurisdictionMissing = false; // assume the best NodeList* nodeList = NodeList::getInstance(); - + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // only send to the NodeTypes that are getMyNodeType() if (node->getType() == getMyNodeType()) { @@ -78,15 +78,15 @@ bool OctreeEditPacketSender::serversExist() const { break; // no point in looking further... } } - + return (hasServers && !atLeastOnJurisdictionMissing); } // This method is called when the edit packet layer has determined that it has a fully formed packet destined for -// a known nodeID. +// a known nodeID. void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) { NodeList* nodeList = NodeList::getInstance(); - + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // only send to the NodeTypes that are getMyNodeType() if (node->getType() == getMyNodeType() && @@ -94,7 +94,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c if (nodeList->getNodeActiveSocketOrPing(node.data())) { const HifiSockAddr* nodeAddress = node->getActiveSocket(); queuePacketForSending(*nodeAddress, buffer, length); - + // debugging output... bool wantDebugging = false; if (wantDebugging) { @@ -103,10 +103,10 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c uint64_t createdAt = (*((uint64_t*)(buffer + numBytesPacketHeader + sizeof(sequence)))); uint64_t queuedAt = usecTimestampNow(); uint64_t transitTime = queuedAt - createdAt; - - qDebug() << "OctreeEditPacketSender::queuePacketToNode() queued " << buffer[0] << - " - command to node bytes=" << length << - " sequence=" << sequence << + + qDebug() << "OctreeEditPacketSender::queuePacketToNode() queued " << buffer[0] << + " - command to node bytes=" << length << + " sequence=" << sequence << " transitTimeSoFar=" << transitTime << " usecs"; } } @@ -116,7 +116,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c void OctreeEditPacketSender::processPreServerExistsPackets() { assert(serversExist()); // we should only be here if we have jurisdictions - + // First send out all the single message packets... while (!_preServerSingleMessagePackets.empty()) { EditPacketBuffer* packet = _preServerSingleMessagePackets.front(); @@ -133,7 +133,7 @@ void OctreeEditPacketSender::processPreServerExistsPackets() { _preServerPackets.erase(_preServerPackets.begin()); } - // if while waiting for the jurisdictions the caller called releaseQueuedMessages() + // if while waiting for the jurisdictions the caller called releaseQueuedMessages() // then we want to honor that request now. if (_releaseQueuedMessagesPending) { releaseQueuedMessages(); @@ -160,17 +160,17 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l if (!_shouldSend) { return; // bail early } - + assert(serversExist()); // we must have jurisdictions to be here!! int headerBytes = numBytesForPacketHeader(buffer) + sizeof(short) + sizeof(uint64_t); unsigned char* octCode = buffer + headerBytes; // skip the packet header to get to the octcode - + // 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 + // 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 // for each server - + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { // only send to the NodeTypes that are getMyNodeType() if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) { @@ -190,10 +190,11 @@ 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 } - + // If we don't have jurisdictions, then we will simply queue up all of these packets and wait till we have // jurisdictions for processing if (!serversExist()) { @@ -211,52 +212,61 @@ 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 + // 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 // for each server - + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { // only send to the NodeTypes that are getMyNodeType() if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) { QUuid nodeUUID = node->getUUID(); bool isMyJurisdiction = true; - + if (_serverJurisdictions) { // we need to get the jurisdiction for this // here we need to get the "pending packet" for this server 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)) { releaseQueuedPacket(packetBuffer); initializePacket(packetBuffer, type); } - + // If the buffer is empty and not correctly initialized for our type... if (type != packetBuffer._currentType && packetBuffer._currentSize == 0) { initializePacket(packetBuffer, type); } - + // This is really the first time we know which server/node this particular edit message // is going to, so we couldn't adjust for clock skew till now. But here's our chance. // We call this virtual function that allows our specific type of EditPacketSender to // 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; } @@ -265,7 +275,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned c } void OctreeEditPacketSender::releaseQueuedMessages() { - // if we don't yet have jurisdictions then we can't actually release messages yet because we don't + // if we don't yet have jurisdictions then we can't actually release messages yet because we don't // know where to send them to. Instead, just remember this request and when we eventually get jurisdictions // call release again at that time. if (!serversExist()) { @@ -273,12 +283,14 @@ void OctreeEditPacketSender::releaseQueuedMessages() { } else { for (std::map::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; diff --git a/libraries/particle-server/src/ParticleNodeData.h b/libraries/particle-server/src/ParticleNodeData.h index 991c4c063c..5d6655f505 100644 --- a/libraries/particle-server/src/ParticleNodeData.h +++ b/libraries/particle-server/src/ParticleNodeData.h @@ -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__) */ diff --git a/libraries/particle-server/src/ParticleServer.cpp b/libraries/particle-server/src/ParticleServer.cpp index b1b46b3133..9d7d70db83 100644 --- a/libraries/particle-server/src/ParticleServer.cpp +++ b/libraries/particle-server/src/ParticleServer.cpp @@ -6,6 +6,7 @@ // Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // +#include #include #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) { @@ -46,20 +50,89 @@ void ParticleServer::particleCreated(const Particle& newParticle, Node* node) { int numBytesPacketHeader = populateTypeAndVersion(outputBuffer, PACKET_TYPE_PARTICLE_ADD_RESPONSE); int packetLength = numBytesPacketHeader; copyAt += numBytesPacketHeader; - + // encode the creatorTokenID uint32_t creatorTokenID = newParticle.getCreatorTokenID(); memcpy(copyAt, &creatorTokenID, sizeof(creatorTokenID)); copyAt += sizeof(creatorTokenID); packetLength += sizeof(creatorTokenID); - + // encode the particle ID uint32_t particleID = newParticle.getID(); memcpy(copyAt, &particleID, sizeof(particleID)); copyAt += sizeof(particleID); packetLength += sizeof(particleID); - + NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength, 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(node->getLinkedData()); + if (nodeData) { + uint64_t deletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt(); + + ParticleTree* tree = static_cast(_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(node->getLinkedData()); + if (nodeData) { + uint64_t deletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt(); + uint64_t deletePacketSentAt = usecTimestampNow(); + + ParticleTree* tree = static_cast(_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(_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(otherNode->getLinkedData()); + uint64_t nodeLastDeletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt(); + if (nodeLastDeletedParticlesSentAt < earliestLastDeletedParticlesSent) { + earliestLastDeletedParticlesSent = nodeLastDeletedParticlesSentAt; + } + } + } + //qDebug() << "earliestLastDeletedParticlesSent=" << earliestLastDeletedParticlesSent; + tree->forgetParticlesDeletedBefore(earliestLastDeletedParticlesSent); + } +} + diff --git a/libraries/particle-server/src/ParticleServer.h b/libraries/particle-server/src/ParticleServer.h index 5ace2ef7fc..e925d4e9ce 100644 --- a/libraries/particle-server/src/ParticleServer.h +++ b/libraries/particle-server/src/ParticleServer.h @@ -18,11 +18,12 @@ /// Handles assignments of type ParticleServer - sending particles to various clients. class ParticleServer : public OctreeServer, public NewlyCreatedParticleHook { -public: + Q_OBJECT +public: ParticleServer(const unsigned char* dataBuffer, int numBytes); ~ParticleServer(); - // Subclasses must implement these methods + // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode(Node* newNode); virtual Octree* createTree(); virtual unsigned char getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; } @@ -30,12 +31,17 @@ public: virtual const char* getMyServerName() const { return PARTICLE_SERVER_NAME; } virtual const char* getMyLoggingServerTargetName() const { return PARTICLE_SERVER_LOGGING_TARGET_NAME; } virtual const char* getMyDefaultPersistFilename() const { return LOCAL_PARTICLES_PERSIST_FILE; } - + // 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: }; diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index e395996c45..5570797ab4 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -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 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,55 +361,98 @@ 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 - memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius)); - dataAt += sizeof(newParticle._radius); - processedBytes += sizeof(newParticle._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 - memcpy(&newParticle._position, dataAt, sizeof(newParticle._position)); - dataAt += sizeof(newParticle._position); - processedBytes += sizeof(newParticle._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 - memcpy(newParticle._color, dataAt, sizeof(newParticle._color)); - dataAt += sizeof(newParticle._color); - processedBytes += sizeof(newParticle._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 - memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity)); - dataAt += sizeof(newParticle._velocity); - processedBytes += sizeof(newParticle._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 - memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity)); - dataAt += sizeof(newParticle._gravity); - processedBytes += sizeof(newParticle._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 - memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping)); - dataAt += sizeof(newParticle._damping); - processedBytes += sizeof(newParticle._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 - memcpy(&newParticle._inHand, dataAt, sizeof(newParticle._inHand)); - dataAt += sizeof(newParticle._inHand); - processedBytes += sizeof(newParticle._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 - uint16_t scriptLength; - memcpy(&scriptLength, dataAt, sizeof(scriptLength)); - dataAt += sizeof(scriptLength); - processedBytes += sizeof(scriptLength); - QString tempString((const char*)dataAt); - newParticle._script = tempString; - dataAt += scriptLength; - processedBytes += scriptLength; + if (isNewParticle || ((packetContainsBits & PACKET_CONTAINS_SCRIPT) == PACKET_CONTAINS_SCRIPT)) { + uint16_t scriptLength; + memcpy(&scriptLength, dataAt, sizeof(scriptLength)); + dataAt += sizeof(scriptLength); + processedBytes += sizeof(scriptLength); + QString tempString((const char*)dataAt); + 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,114 +461,178 @@ 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); + // get the octal code for the particle - int octets = numberOfThreeBitSectionsInCode(octcode); - int lengthOfOctcode = bytesRequiredForCodeLength(octets); - int lenfthOfEditData = lengthOfOctcode + expectedEditMessageBytes(); + // 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); - // make sure we have room to copy this particle - if (sizeOut + lenfthOfEditData > sizeIn) { - success = false; - } else { - // add it to our message - memcpy(copyAt, octcode, lengthOfOctcode); - copyAt += lengthOfOctcode; - sizeOut += lengthOfOctcode; + // 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); - // Now add our edit content details... + int octets = numberOfThreeBitSectionsInCode(octcode); + int lengthOfOctcode = bytesRequiredForCodeLength(octets); + int lenfthOfEditData = lengthOfOctcode + expectedEditMessageBytes(); - // id - memcpy(copyAt, &details[i].id, sizeof(details[i].id)); - copyAt += sizeof(details[i].id); - sizeOut += sizeof(details[i].id); - // special case for handling "new" particles - if (details[i].id == 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 - memcpy(copyAt, &details[i].creatorTokenID, sizeof(details[i].creatorTokenID)); - copyAt += sizeof(details[i].creatorTokenID); - sizeOut += sizeof(details[i].creatorTokenID); - } + // make sure we have room to copy this particle + if (sizeOut + lenfthOfEditData > sizeIn) { + success = false; + } else { + // add it to our message + memcpy(copyAt, octcode, lengthOfOctcode); + copyAt += lengthOfOctcode; + sizeOut += lengthOfOctcode; - // lastEdited - memcpy(copyAt, &details[i].lastEdited, sizeof(details[i].lastEdited)); - copyAt += sizeof(details[i].lastEdited); - sizeOut += sizeof(details[i].lastEdited); + // Now add our edit content details... + bool isNewParticle = (id.id == NEW_PARTICLE); - // radius - memcpy(copyAt, &details[i].radius, sizeof(details[i].radius)); - copyAt += sizeof(details[i].radius); - sizeOut += sizeof(details[i].radius); + // id + memcpy(copyAt, &id.id, sizeof(id.id)); + copyAt += sizeof(id.id); + sizeOut += sizeof(id.id); - // position - memcpy(copyAt, &details[i].position, sizeof(details[i].position)); - copyAt += sizeof(details[i].position); - sizeOut += sizeof(details[i].position); + // special case for handling "new" particles + if (isNewParticle) { + // If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that + // we want to send back to the creator as an map to the actual id + memcpy(copyAt, &id.creatorTokenID, sizeof(id.creatorTokenID)); + copyAt += sizeof(id.creatorTokenID); + sizeOut += sizeof(id.creatorTokenID); + } - // color - memcpy(copyAt, details[i].color, sizeof(details[i].color)); - copyAt += sizeof(details[i].color); - sizeOut += sizeof(details[i].color); + // lastEdited + uint64_t lastEdited = properties.getLastEdited(); + memcpy(copyAt, &lastEdited, sizeof(lastEdited)); + copyAt += sizeof(lastEdited); + sizeOut += sizeof(lastEdited); - // velocity - memcpy(copyAt, &details[i].velocity, sizeof(details[i].velocity)); - copyAt += sizeof(details[i].velocity); - sizeOut += sizeof(details[i].velocity); + // 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); + } - // gravity - memcpy(copyAt, &details[i].gravity, sizeof(details[i].gravity)); - copyAt += sizeof(details[i].gravity); - sizeOut += sizeof(details[i].gravity); + // 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); + } - // damping - memcpy(copyAt, &details[i].damping, sizeof(details[i].damping)); - copyAt += sizeof(details[i].damping); - sizeOut += sizeof(details[i].damping); + // 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); + } - // inHand - memcpy(copyAt, &details[i].inHand, sizeof(details[i].inHand)); - copyAt += sizeof(details[i].inHand); - sizeOut += sizeof(details[i].inHand); + // 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); + } - // script - uint16_t scriptLength = details[i].updateScript.size() + 1; + // 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 + 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 + 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 + 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 + 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(" nextID:%u\n", _nextID); - } } - // cleanup - delete[] octcode; + + bool wantDebugging = false; + if (wantDebugging) { + printf("encodeParticleEditMessageDetails()....\n"); + printf("Particle id :%u\n", id.id); + printf(" nextID:%u\n", _nextID); + } } + // cleanup + delete[] octcode; + + //qDebug() << "encoding... sizeOut:" << sizeOut; + return success; } @@ -476,7 +670,7 @@ void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssiz // MIN_VALID_SPEED is obtained by computing speed gained at one gravity during the shortest expected frame period const float MIN_EXPECTED_FRAME_PERIOD = 0.005f; // 1/200th of a second -const float MIN_VALID_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); +const float MIN_VALID_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); void Particle::update(const uint64_t& now) { float timeElapsed = (float)(now - _lastUpdated) / (float)(USECS_PER_SECOND); @@ -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(); +} + + diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index d53c7b9c40..160eee9db2 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -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(usecTimestampNow() - _created) / static_cast(USECS_PER_SECOND); } + float getAge() const { return static_cast(usecTimestampNow() - _created) / static_cast(USECS_PER_SECOND); } float getEditedAgo() const { return static_cast(usecTimestampNow() - _lastEdited) / static_cast(USECS_PER_SECOND); } uint32_t getID() const { return _id; } 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 _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(); } + + /// get position in meter units + glm::vec3 getPosition() const { return _particle->getPosition() * (float)TREE_SCALE; } - 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 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__) */ diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index dee0259eb7..61417aa735 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -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); @@ -137,7 +139,7 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) // MIN_VALID_SPEED is obtained by computing speed gained at one gravity after the shortest expected frame const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second -const float HALTING_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); +const float HALTING_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { @@ -164,7 +166,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // only collide when particle and collision point are moving toward each other // (doing this prevents some "collision snagging" when particle penetrates the object) - // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar. + // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar. // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle. // TODO: make this less hacky when we have more per-collision details float elasticity = ELASTICITY; @@ -173,7 +175,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { if (attenuationFactor < 1.f) { collisionInfo._addedVelocity *= attenuationFactor; elasticity *= attenuationFactor; - // NOTE: the math below keeps the damping piecewise continuous, + // NOTE: the math below keeps the damping piecewise continuous, // while ramping it up to 1.0 when attenuationFactor = 0 damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING); } @@ -196,7 +198,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { collisionInfo._addedVelocity /= (float)(TREE_SCALE); glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity(); if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) { - // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar. + // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar. // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle. // TODO: make this less hacky when we have more per-collision details float elasticity = ELASTICITY; @@ -205,7 +207,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { if (attenuationFactor < 1.f) { collisionInfo._addedVelocity *= attenuationFactor; elasticity *= attenuationFactor; - // NOTE: the math below keeps the damping piecewise continuous, + // NOTE: the math below keeps the damping piecewise continuous, // while ramping it up to 1.0 when attenuationFactor = 0 damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING); } @@ -213,7 +215,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { collisionInfo._penetration /= (float)(TREE_SCALE); updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, ELASTICITY, damping, collisionInfo); + applyHardCollision(particle, ELASTICITY, damping, collisionInfo); } } } @@ -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()); } diff --git a/libraries/particles/src/ParticleEditHandle.cpp b/libraries/particles/src/ParticleEditHandle.cpp index 88d3143a0f..30ff71951d 100644 --- a/libraries/particles/src/ParticleEditHandle.cpp +++ b/libraries/particles/src/ParticleEditHandle.cpp @@ -13,24 +13,22 @@ #include "ParticleTree.h" std::map 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; } else { - _creatorTokenID = UNKNOWN_TOKEN; + _creatorTokenID = UNKNOWN_TOKEN; _id = id; _isKnownID = true; // don't add to _allHandles because we already know it... } _packetSender = packetSender; _localTree = localTree; - + } ParticleEditHandle::~ParticleEditHandle() { @@ -40,54 +38,59 @@ ParticleEditHandle::~ParticleEditHandle() { } } -void ParticleEditHandle::createParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity, - glm::vec3 gravity, float damping, bool inHand, QString updateScript) { +void ParticleEditHandle::createParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity, + 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 }; - + position, radius, {color.red, color.green, color.blue }, + 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(); - + // if we have a local tree, also update it... if (_localTree) { // 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) { +bool ParticleEditHandle::updateParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity, + 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 }; + position, radius, {color.red, color.green, color.blue }, + velocity, gravity, damping, lifetime, inHand, updateScript, _creatorTokenID }; // queue the packet _packetSender->queueParticleEditMessages(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, 1, &newParticleDetail); - + // release them _packetSender->releaseQueuedMessages(); // 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; } @@ -95,7 +98,7 @@ void ParticleEditHandle::handleAddResponse(unsigned char* packetData , int packe unsigned char* dataAt = packetData; int numBytesPacketHeader = numBytesForPacketHeader(packetData); dataAt += numBytesPacketHeader; - + uint32_t creatorTokenID; memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); dataAt += sizeof(creatorTokenID); diff --git a/libraries/particles/src/ParticleEditHandle.h b/libraries/particles/src/ParticleEditHandle.h index 186dc47944..5343b29343 100644 --- a/libraries/particles/src/ParticleEditHandle.h +++ b/libraries/particles/src/ParticleEditHandle.h @@ -34,18 +34,17 @@ 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); + void createParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity, + 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, 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); - static void handleAddResponse(unsigned char* packetData , int packetLength); private: uint32_t _creatorTokenID; uint32_t _id; bool _isKnownID; - static uint32_t _nextCreatorTokenID; static std::map _allHandles; ParticleEditPacketSender* _packetSender; ParticleTree* _localTree; diff --git a/libraries/particles/src/ParticleEditPacketSender.cpp b/libraries/particles/src/ParticleEditPacketSender.cpp index bd56728176..f745adc05b 100644 --- a/libraries/particles/src/ParticleEditPacketSender.cpp +++ b/libraries/particles/src/ParticleEditPacketSender.cpp @@ -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()) { @@ -37,24 +37,22 @@ void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, const P } } -void ParticleEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) { +void ParticleEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) { Particle::adjustEditPacketForClockSkew(codeColorBuffer, length, clockSkew); -} +} -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)) { - queueOctreeEditMessage(type, bufferOut, sizeOut); - } - } + // 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, particleID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) { + queueOctreeEditMessage(type, bufferOut, sizeOut); + } } diff --git a/libraries/particles/src/ParticleEditPacketSender.h b/libraries/particles/src/ParticleEditPacketSender.h index 2295ee22b2..c45a3ef8c4 100644 --- a/libraries/particles/src/ParticleEditPacketSender.h +++ b/libraries/particles/src/ParticleEditPacketSender.h @@ -19,14 +19,14 @@ class ParticleEditPacketSender : public OctreeEditPacketSender { public: ParticleEditPacketSender(PacketSenderNotify* notify = NULL) : OctreeEditPacketSender(notify) { } ~ParticleEditPacketSender() { } - - /// Send particle add message immediately - void sendEditParticleMessage(PACKET_TYPE type, const ParticleDetail& detail); - /// 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 + /// Send particle add message immediately + 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; } diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index b02df9d500..a3c8072b3a 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -15,7 +15,7 @@ ParticleTree::ParticleTree(bool shouldReaverage) : Octree(shouldReaverage) { } ParticleTreeElement* ParticleTree::createNewElement(unsigned char * octalCode) const { - ParticleTreeElement* newElement = new ParticleTreeElement(octalCode); + ParticleTreeElement* newElement = new ParticleTreeElement(octalCode); return newElement; } @@ -29,13 +29,55 @@ bool ParticleTree::handlesEditPacketType(PACKET_TYPE packetType) const { return false; } +class FindAndDeleteParticlesArgs { +public: + QList _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(element); + + //qDebug() << "findAndDeleteOperation() args->_idsToDelete.size():" << args->_idsToDelete.size(); + + for (QList::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: const Particle& searchParticle; bool found; }; - + bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraData) { FindAndUpdateParticleArgs* args = static_cast(extraData); ParticleTreeElement* particleTreeElement = static_cast(element); @@ -51,15 +93,15 @@ void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) { // First, look for the existing particle in the tree.. FindAndUpdateParticleArgs args = { particle, false }; recurseTreeWithOperation(findAndUpdateOperation, &args); - + // 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); - } + } // what else do we need to do here to get reaveraging to work _isDirty = true; } @@ -72,25 +114,25 @@ public: const Particle* closestParticle; float closestParticleDistance; }; - + bool ParticleTree::findNearPointOperation(OctreeElement* element, void* extraData) { FindNearPointArgs* args = static_cast(extraData); ParticleTreeElement* particleTreeElement = static_cast(element); glm::vec3 penetration; - bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position, + bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position, args->targetRadius, penetration); // If this particleTreeElement contains the point, then search it... if (sphereIntersection) { const Particle* thisClosestParticle = particleTreeElement->getClosestParticle(args->position); - + // we may have gotten NULL back, meaning no particle was available if (thisClosestParticle) { glm::vec3 particlePosition = thisClosestParticle->getPosition(); float distanceFromPointToParticle = glm::distance(particlePosition, args->position); - + // If we're within our target radius if (distanceFromPointToParticle <= args->targetRadius) { // we are closer than anything else we've found @@ -101,11 +143,11 @@ bool ParticleTree::findNearPointOperation(OctreeElement* element, void* extraDat } } } - + // we should be able to optimize this... return true; // keep searching in case children have closer particles } - + // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching return false; } @@ -124,9 +166,11 @@ public: bool found; const Particle* foundParticle; }; - + bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) { +//qDebug() << "ParticleTree::findByIDOperation()...."; + FindByIDArgs* args = static_cast(extraData); ParticleTreeElement* particleTreeElement = static_cast(element); @@ -134,7 +178,7 @@ bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) { if (args->found) { return false; } - + // as the tree element if it has this particle const Particle* foundParticle = particleTreeElement->getParticleWithID(args->id); if (foundParticle) { @@ -142,17 +186,23 @@ bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) { args->found = true; return false; } - + // keep looking return true; } -const Particle* ParticleTree::findParticleByID(uint32_t id) { +const Particle* ParticleTree::findParticleByID(uint32_t id, bool alreadyLocked) { FindByIDArgs args = { id, false, NULL }; - lockForRead(); + if (!alreadyLocked) { + //qDebug() << "ParticleTree::findParticleByID().... about to call lockForRead()...."; + lockForRead(); + //qDebug() << "ParticleTree::findParticleByID().... after call lockForRead()...."; + } recurseTreeWithOperation(findByIDOperation, &args); - unlock(); + 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; @@ -227,7 +281,7 @@ void ParticleTree::update() { ParticleTreeUpdateArgs args = { }; recurseTreeWithOperation(updateOperation, &args); - + // now add back any of the particles that moved elements.... int movingParticles = args._movingParticles.size(); for (int i = 0; i < movingParticles; i++) { @@ -235,14 +289,160 @@ void ParticleTree::update() { // if the particle is still inside our total bounds, then re-add it AABox treeBounds = getRoot()->getAABox(); - + 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(); } } - + // prune the tree... recurseTreeWithOperation(pruneOperation, NULL); } +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::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::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin(); + while (iterator != _recentlyDeletedParticleIDs.constEnd()) { + QList values = _recentlyDeletedParticleIDs.values(iterator.key()); + for (int valueItem = 0; valueItem < values.size(); ++valueItem) { + //qDebug() << "considering... " << iterator.key() << ": " << values.at(valueItem); + + // if the timestamp is more recent then out last sent time, include it + if (iterator.key() > sinceTime) { + //qDebug() << "including... " << iterator.key() << ": " << values.at(valueItem); + uint32_t particleID = values.at(valueItem); + memcpy(copyAt, &particleID, sizeof(particleID)); + copyAt += sizeof(particleID); + 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::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); + } +} diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index 7c8d724a17..33e5d5fe75 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -21,14 +21,14 @@ class ParticleTree : public Octree { Q_OBJECT public: ParticleTree(bool shouldReaverage = false); - + /// Implements our type specific root element factory virtual ParticleTreeElement* createNewElement(unsigned char * octalCode = NULL) const; - + /// Type safe version of getRoot() ParticleTreeElement* getRoot() { return (ParticleTreeElement*)_rootNode; } - - + + // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing virtual bool getWantSVOfileVersions() const { return true; } @@ -37,15 +37,22 @@ public: virtual int processEditPacketData(PACKET_TYPE packetType, unsigned char* packetData, int packetLength, unsigned char* editData, int maxLength, Node* senderNode); - virtual void update(); + virtual void update(); 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 _newlyCreatedHooks; + + + QReadWriteLock _recentlyDeletedParticlesLock; + QMultiMap _recentlyDeletedParticleIDs; }; #endif /* defined(__hifi__ParticleTree__) */ diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 2be0c330d2..7994909004 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -11,7 +11,7 @@ #include "ParticleTree.h" #include "ParticleTreeElement.h" -ParticleTreeElement::ParticleTreeElement(unsigned char* octalCode) : OctreeElement(), _particles(NULL) { +ParticleTreeElement::ParticleTreeElement(unsigned char* octalCode) : OctreeElement(), _particles(NULL) { init(octalCode); }; @@ -22,7 +22,7 @@ ParticleTreeElement::~ParticleTreeElement() { } // This will be called primarily on addChildAt(), which means we're adding a child of our -// own type to our own tree. This means we should initialize that child with any tree and type +// own type to our own tree. This means we should initialize that child with any tree and type // specific settings that our children must have. One example is out VoxelSystem, which // we know must match ours. OctreeElement* ParticleTreeElement::createNewElement(unsigned char* octalCode) const { @@ -37,8 +37,8 @@ void ParticleTreeElement::init(unsigned char* octalCode) { _voxelMemoryUsage += sizeof(ParticleTreeElement); } -ParticleTreeElement* ParticleTreeElement::addChildAtIndex(int index) { - ParticleTreeElement* newElement = (ParticleTreeElement*)OctreeElement::addChildAtIndex(index); +ParticleTreeElement* ParticleTreeElement::addChildAtIndex(int index) { + ParticleTreeElement* newElement = (ParticleTreeElement*)OctreeElement::addChildAtIndex(index); newElement->setTree(_myTree); return newElement; } @@ -77,7 +77,7 @@ void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) { // into the arguments moving particles. These will be added back or deleted completely if (particle.getShouldDie() || !_box.contains(particle.getPosition())) { args._movingParticles.push_back(particle); - + // erase this particle particleItr = _particles->erase(particleItr); } else { @@ -90,16 +90,15 @@ void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) { // roaming piles of particles. } -bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float radius, +bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { - QList::iterator particleItr = _particles->begin(); QList::const_iterator particleEnd = _particles->end(); while(particleItr != particleEnd) { Particle& particle = (*particleItr); glm::vec3 particleCenter = particle.getPosition(); float particleRadius = particle.getRadius(); - + // don't penetrate yourself if (particleCenter == center && particleRadius == radius) { return false; @@ -115,7 +114,7 @@ bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float r continue; } } - + if (findSphereSpherePenetration(center, radius, particleCenter, particleRadius, penetration)) { // return true on first valid particle penetration *penetratedObject = (void*)(&particle); @@ -135,12 +134,12 @@ bool ParticleTreeElement::containsParticle(const Particle& particle) const { return true; } } - return false; + return false; } bool ParticleTreeElement::updateParticle(const Particle& particle) { - // NOTE: this method must first lookup the particle by ID, hence it is O(N) - // and "particle is not found" is worst-case (full N) but maybe we don't care? + // NOTE: this method must first lookup the particle by ID, hence it is O(N) + // and "particle is not found" is worst-case (full N) but maybe we don't care? // (guaranteed that num particles per elemen is small?) const bool wantDebug = false; uint16_t numberOfParticles = _particles->size(); @@ -150,27 +149,27 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { int difference = thisParticle.getLastUpdated() - particle.getLastUpdated(); bool changedOnServer = thisParticle.getLastEdited() < particle.getLastEdited(); bool localOlder = thisParticle.getLastUpdated() < particle.getLastUpdated(); - if (changedOnServer || localOlder) { + if (changedOnServer || localOlder) { if (wantDebug) { - printf("local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", + printf("local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", particle.getID(), (changedOnServer ? "CHANGED" : "same"), - (localOlder ? "OLDER" : "NEWER"), + (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(particle.isNewlyCreated()) ); } thisParticle.copyChangedProperties(particle); } else { if (wantDebug) { printf(">>> IGNORING SERVER!!! Would've caused jutter! <<< " - "local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", + "local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", particle.getID(), (changedOnServer ? "CHANGED" : "same"), - (localOlder ? "OLDER" : "NEWER"), + (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(particle.isNewlyCreated()) ); } } return true; } } - return false; + return false; } const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) const { @@ -183,7 +182,7 @@ const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) cons closestParticle = &(*_particles)[i]; } } - return closestParticle; + return closestParticle; } const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const { @@ -196,26 +195,40 @@ const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const { break; } } - return foundParticle; + 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) { - + +int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args) { + const unsigned char* dataAt = data; int bytesRead = 0; uint16_t numberOfParticles = 0; int expectedBytesPerParticle = Particle::expectedBytes(); if (bytesLeftToRead >= sizeof(numberOfParticles)) { - + // read our particles in.... numberOfParticles = *(uint16_t*)dataAt; dataAt += sizeof(numberOfParticles); bytesLeftToRead -= sizeof(numberOfParticles); bytesRead += sizeof(numberOfParticles); - + if (bytesLeftToRead >= (numberOfParticles * expectedBytesPerParticle)) { for (uint16_t i = 0; i < numberOfParticles; i++) { Particle tempParticle; @@ -227,7 +240,7 @@ int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, in } } } - + return bytesRead; } @@ -238,7 +251,7 @@ void ParticleTreeElement::calculateAverageFromChildren() { // will detect if children are leaves AND collapsable into the parent node // and in that case will collapse children and make this node -// a leaf, returns TRUE if all the leaves are collapsed into a +// a leaf, returns TRUE if all the leaves are collapsed into a // single node bool ParticleTreeElement::collapseChildren() { // nothing to do here yet... diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index ab697aed1b..8493d59bb8 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -28,11 +28,11 @@ public: class ParticleTreeElement : public OctreeElement { friend class ParticleTree; // to allow createElement to new us... - + ParticleTreeElement(unsigned char* octalCode = NULL); virtual OctreeElement* createNewElement(unsigned char* octalCode = NULL) const; - + public: virtual ~ParticleTreeElement(); @@ -40,31 +40,31 @@ public: ParticleTreeElement* getChildAtIndex(int index) { return (ParticleTreeElement*)OctreeElement::getChildAtIndex(index); } // methods you can and should override to implement your tree functionality - + /// Adds a child to the current element. Override this if there is additional child initialization your class needs. virtual ParticleTreeElement* addChildAtIndex(int index); - /// Override this to implement LOD averaging on changes to the tree. + /// Override this to implement LOD averaging on changes to the tree. virtual void calculateAverageFromChildren(); - /// Override this to implement LOD collapsing and identical child pruning on changes to the tree. + /// Override this to implement LOD collapsing and identical child pruning on changes to the tree. virtual bool collapseChildren(); /// Should this element be considered to have content in it. This will be used in collision and ray casting methods. /// By default we assume that only leaves are actual content, but some octrees may have different semantics. virtual bool hasContent() const { return isLeaf(); } - + /// Override this to break up large octree elements when an edit operation is performed on a smaller octree element. - /// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the + /// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the /// meaningful split would be to break the larger cube into smaller cubes of the same color/texture. virtual void splitChildren() { } - + /// Override to indicate that this element requires a split before editing lower elements in the octree virtual bool requiresSplit() const { return false; } /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. virtual bool appendElementData(OctreePacketData* packetData) const; - + /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); @@ -76,21 +76,23 @@ public: virtual bool isRendered() const { return getShouldRender(); } virtual bool deleteApproved() const { return !hasParticles(); } - virtual bool findSpherePenetration(const glm::vec3& center, float radius, + virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; const QList& getParticles() const { return *_particles; } QList& getParticles() { return *_particles; } bool hasParticles() const { return _particles->size() > 0; } - + void update(ParticleTreeUpdateArgs& args); void setTree(ParticleTree* tree) { _myTree = tree; } - + bool containsParticle(const Particle& particle) const; bool updateParticle(const Particle& particle); 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); diff --git a/libraries/particles/src/ParticlesScriptingInterface.cpp b/libraries/particles/src/ParticlesScriptingInterface.cpp index 7f8c435a12..14388b6ac4 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.cpp +++ b/libraries/particles/src/ParticlesScriptingInterface.cpp @@ -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++; - - // 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 }; - + uint32_t creatorTokenID = Particle::getNextCreatorTokenID(); + + ParticleID id(NEW_PARTICLE, creatorTokenID, false ); + // queue the packet - queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, addParticleDetail); - - return creatorTokenID; + queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, id, properties); + + 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 }; - - // queue the packet - queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, editParticleDetail); + // setup properties to kill the particle + ParticleProperties properties; + properties.setShouldDie(true); + + 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; +} + diff --git a/libraries/particles/src/ParticlesScriptingInterface.h b/libraries/particles/src/ParticlesScriptingInterface.h index 265f5a7ca4..edd2ac43dc 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.h +++ b/libraries/particles/src/ParticlesScriptingInterface.h @@ -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__) */ diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a5cb903320..e6a74af9b3 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -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(); diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 83dbf4654f..f595dfcafc 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -18,18 +18,18 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) { case PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO: case PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO: return 2; - + case PACKET_TYPE_HEAD_DATA: return 15; case PACKET_TYPE_OCTREE_STATS: return 2; - + case PACKET_TYPE_DOMAIN: case PACKET_TYPE_DOMAIN_LIST_REQUEST: case PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY: return 2; - + case PACKET_TYPE_VOXEL_QUERY: return 2; @@ -40,19 +40,19 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) { case PACKET_TYPE_VOXEL_DATA: return 1; - + case PACKET_TYPE_JURISDICTION: return 1; - - case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: - return 2; + + case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: + return 4; case PACKET_TYPE_PARTICLE_DATA: - return 5; - + return 8; + case PACKET_TYPE_PING_REPLY: return 1; - + case PACKET_TYPE_DATA_SERVER_GET: case PACKET_TYPE_DATA_SERVER_PUT: case PACKET_TYPE_DATA_SERVER_SEND: @@ -78,7 +78,7 @@ bool packetVersionMatch(unsigned char* packetHeader) { int populateTypeAndVersion(unsigned char* destinationHeader, PACKET_TYPE type) { destinationHeader[0] = type; destinationHeader[1] = versionForPacketType(type); - + // return the number of bytes written for pointer pushing return 2; } @@ -102,7 +102,7 @@ int numBytesForPacketVersion(const unsigned char* packetVersion) { int numBytesForPacketHeader(const unsigned char* packetHeader) { // int numBytesType = numBytesForPacketType(packetHeader); // return numBytesType + numBytesForPacketVersion(packetHeader + numBytesType); - + // currently this need not be dynamic - there are 2 bytes for each packet header return 2; } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 9a099b4171..9d2eec6b40 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -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(); } + diff --git a/libraries/voxel-server/src/VoxelServer.cpp b/libraries/voxel-server/src/VoxelServer.cpp index 1147718486..a1f5eaf55e 100644 --- a/libraries/voxel-server/src/VoxelServer.cpp +++ b/libraries/voxel-server/src/VoxelServer.cpp @@ -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; } @@ -41,11 +41,11 @@ int VoxelServer::sendSpecialPacket(Node* node) { int numBytesPacketHeader = populateTypeAndVersion(_tempOutputBuffer, PACKET_TYPE_ENVIRONMENT_DATA); int envPacketLength = numBytesPacketHeader; int environmentsToSend = getSendMinimalEnvironment() ? 1 : getEnvironmentDataCount(); - + for (int i = 0; i < environmentsToSend; i++) { envPacketLength += getEnvironmentData(i)->getBroadcastData(_tempOutputBuffer + envPacketLength); } - + NodeList::getInstance()->getNodeSocket().writeDatagram((char*) _tempOutputBuffer, envPacketLength, node->getActiveSocket()->getAddress(), node->getActiveSocket()->getPort()); diff --git a/libraries/voxel-server/src/VoxelServer.h b/libraries/voxel-server/src/VoxelServer.h index e785d2f65e..316280e37c 100644 --- a/libraries/voxel-server/src/VoxelServer.h +++ b/libraries/voxel-server/src/VoxelServer.h @@ -24,7 +24,7 @@ /// Handles assignments of type VoxelServer - sending voxels to various clients. class VoxelServer : public OctreeServer { -public: +public: VoxelServer(const unsigned char* dataBuffer, int numBytes); ~VoxelServer(); @@ -33,7 +33,7 @@ public: EnvironmentData* getEnvironmentData(int i) { return &_environmentData[i]; } int getEnvironmentDataCount() const { return sizeof(_environmentData)/sizeof(EnvironmentData); } - // Subclasses must implement these methods + // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode(Node* newNode); virtual Octree* createTree(); virtual unsigned char getMyNodeType() const { return NODE_TYPE_VOXEL_SERVER; } @@ -41,10 +41,10 @@ public: virtual const char* getMyServerName() const { return VOXEL_SERVER_NAME; } virtual const char* getMyLoggingServerTargetName() const { return VOXEL_SERVER_LOGGING_TARGET_NAME; } virtual const char* getMyDefaultPersistFilename() const { return LOCAL_VOXELS_PERSIST_FILE; } - + // subclass may implement these method virtual void beforeRun(); - virtual bool hasSpecialPacketToSend(); + virtual bool hasSpecialPacketToSend(Node* node); virtual int sendSpecialPacket(Node* node); diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index b17e40c036..3f4e19f60a 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -12,28 +12,32 @@ 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); } diff --git a/libraries/voxels/src/VoxelsScriptingInterface.h b/libraries/voxels/src/VoxelsScriptingInterface.h index c725b93493..6bbe21d601 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.h +++ b/libraries/voxels/src/VoxelsScriptingInterface.h @@ -13,6 +13,8 @@ #include #include + +#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;