From c036b5fd4ba759a574e24d0463d3f75b16518497 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 3 Jun 2015 10:19:28 -0700 Subject: [PATCH 01/12] Actually commit the changes this time! --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 9 +++++++-- libraries/physics/src/EntityMotionState.cpp | 7 +++++++ libraries/physics/src/EntityMotionState.h | 2 ++ libraries/physics/src/ObjectMotionState.cpp | 3 +++ libraries/physics/src/ObjectMotionState.h | 1 + libraries/physics/src/PhysicsEngine.cpp | 6 ++++-- libraries/shared/src/RegisteredMetaTypes.h | 8 +++++--- 7 files changed, 29 insertions(+), 7 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 23e504bfb8..5e88eae17a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1131,10 +1131,15 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT } const float mass = entity->computeMass(); const float COLLISION_PENTRATION_TO_VELOCITY = 50; // as a subsitute for RELATIVE entity->getVelocity() - const float linearVelocity = glm::length(collision.penetration) * COLLISION_PENTRATION_TO_VELOCITY; + // The collision.penetration is a pretty good indicator of changed velocity AFTER the initial contact, + // but that first contact depends on exactly where we hit in the physics step. + // We can get a more consistent initial-contact energy reading by using the changed velocity. + const float linearVelocity = (collision.type == CONTACT_EVENT_TYPE_START) ? + glm::length(collision.velocityChange) : + glm::length(collision.penetration) * COLLISION_PENTRATION_TO_VELOCITY; const float energy = mass * linearVelocity * linearVelocity / 2.0f; const glm::vec3 position = collision.contactPoint; - const float COLLISION_ENERGY_AT_FULL_VOLUME = 0.5f; + const float COLLISION_ENERGY_AT_FULL_VOLUME = (collision.type == CONTACT_EVENT_TYPE_START) ? 10.0f : 0.5f; const float COLLISION_MINIMUM_VOLUME = 0.005f; const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME); if (energyFactorOfFull < COLLISION_MINIMUM_VOLUME) { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 9a24aabb34..1f5a1b9f4e 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -494,6 +494,7 @@ void EntityMotionState::measureBodyAcceleration() { float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP); float invDt = 1.0f / dt; _lastMeasureStep = thisStep; + _measuredDeltaTime = dt; // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt @@ -502,6 +503,12 @@ void EntityMotionState::measureBodyAcceleration() { _lastVelocity = velocity; } } +glm::vec3 EntityMotionState::getObjectLinearVelocityChange() const { + // This is the dampened change in linear velocity, as calculated in measureBodyAcceleration: dv = a * dt + // It is generally only meaningful during the lifespan of collision. In particular, it is not meaningful + // when the entity first starts moving via direct user action. + return _measuredAcceleration * _measuredDeltaTime; +} // virtual void EntityMotionState::setMotionType(MotionType motionType) { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 65279dc01a..b38254d42c 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -64,6 +64,7 @@ public: virtual glm::vec3 getObjectLinearVelocity() const { return _entity->getVelocity(); } virtual glm::vec3 getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } virtual glm::vec3 getObjectGravity() const { return _entity->getGravity(); } + virtual glm::vec3 getObjectLinearVelocityChange() const; virtual const QUuid& getObjectID() const { return _entity->getID(); } @@ -101,6 +102,7 @@ protected: uint32_t _lastMeasureStep; glm::vec3 _lastVelocity; glm::vec3 _measuredAcceleration; + float _measuredDeltaTime; quint8 _accelerationNearlyGravityCount; bool _candidateForOwnership; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 57b75f0f3b..67dc8f294c 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -82,6 +82,9 @@ void ObjectMotionState::setBodyGravity(const glm::vec3& gravity) const { glm::vec3 ObjectMotionState::getBodyLinearVelocity() const { return bulletToGLM(_body->getLinearVelocity()); } +glm::vec3 ObjectMotionState::getObjectLinearVelocityChange() const { + return glm::vec3(); // Subclasses override where meaningful. +} glm::vec3 ObjectMotionState::getBodyAngularVelocity() const { return bulletToGLM(_body->getAngularVelocity()); diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 246ed16627..141fbe252b 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -90,6 +90,7 @@ public: glm::vec3 getBodyLinearVelocity() const; glm::vec3 getBodyAngularVelocity() const; + virtual glm::vec3 getObjectLinearVelocityChange() const; virtual uint32_t getAndClearIncomingDirtyFlags() = 0; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index b622a37136..7b9f499925 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -317,6 +317,8 @@ CollisionEvents& PhysicsEngine::getCollisionEvents() { if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) { ObjectMotionState* A = static_cast(contactItr->first._a); ObjectMotionState* B = static_cast(contactItr->first._b); + glm::vec3 velocityChange = (A ? A->getObjectLinearVelocityChange() : glm::vec3(0.0f)) + + (B ? B->getObjectLinearVelocityChange() : glm::vec3(0.0f)); if (A && A->getType() == MOTIONSTATE_TYPE_ENTITY) { QUuid idA = A->getObjectID(); @@ -326,14 +328,14 @@ CollisionEvents& PhysicsEngine::getCollisionEvents() { } glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset; glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); - _collisionEvents.push_back(Collision(type, idA, idB, position, penetration)); + _collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange)); } else if (B && B->getType() == MOTIONSTATE_TYPE_ENTITY) { QUuid idB = B->getObjectID(); glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset; // NOTE: we're flipping the order of A and B (so that the first objectID is never NULL) // hence we must negate the penetration. glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB); - _collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration)); + _collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration, velocityChange)); } } diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 48eecba227..1dcc85107a 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -78,15 +78,17 @@ enum ContactEventType { class Collision { public: - Collision() : type(CONTACT_EVENT_TYPE_START), idA(), idB(), contactPoint(0.0f), penetration(0.0f) { } - Collision(ContactEventType cType, const QUuid& cIdA, const QUuid& cIdB, const glm::vec3& cPoint, const glm::vec3& cPenetration) - : type(cType), idA(cIdA), idB(cIdB), contactPoint(cPoint), penetration(cPenetration) { } + Collision() : type(CONTACT_EVENT_TYPE_START), idA(), idB(), contactPoint(0.0f), penetration(0.0f), velocityChange(0.0f) { } + Collision(ContactEventType cType, const QUuid& cIdA, const QUuid& cIdB, const glm::vec3& cPoint, + const glm::vec3& cPenetration, const glm::vec3& velocityChange) + : type(cType), idA(cIdA), idB(cIdB), contactPoint(cPoint), penetration(cPenetration), velocityChange(velocityChange) { } ContactEventType type; QUuid idA; QUuid idB; glm::vec3 contactPoint; glm::vec3 penetration; + glm::vec3 velocityChange; }; Q_DECLARE_METATYPE(Collision) QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision); From 407746b842b7c3d2aaf01d7cc5ee23962425d58c Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 3 Jun 2015 10:38:20 -0700 Subject: [PATCH 02/12] Fix (consistent) typo in non-magic-number constant's name. --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 5e88eae17a..93e302c81b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1130,13 +1130,13 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT return; } const float mass = entity->computeMass(); - const float COLLISION_PENTRATION_TO_VELOCITY = 50; // as a subsitute for RELATIVE entity->getVelocity() + const float COLLISION_PENETRATION_TO_VELOCITY = 50; // as a subsitute for RELATIVE entity->getVelocity() // The collision.penetration is a pretty good indicator of changed velocity AFTER the initial contact, // but that first contact depends on exactly where we hit in the physics step. // We can get a more consistent initial-contact energy reading by using the changed velocity. const float linearVelocity = (collision.type == CONTACT_EVENT_TYPE_START) ? glm::length(collision.velocityChange) : - glm::length(collision.penetration) * COLLISION_PENTRATION_TO_VELOCITY; + glm::length(collision.penetration) * COLLISION_PENETRATION_TO_VELOCITY; const float energy = mass * linearVelocity * linearVelocity / 2.0f; const glm::vec3 position = collision.contactPoint; const float COLLISION_ENERGY_AT_FULL_VOLUME = (collision.type == CONTACT_EVENT_TYPE_START) ? 10.0f : 0.5f; From 5727400b9af023e8fbcea3a19e0740205c991de2 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 4 Jun 2015 14:20:19 -0700 Subject: [PATCH 03/12] Sound toys script (in examples/soundToys.js) --- examples/example/soundToys.js | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 examples/example/soundToys.js diff --git a/examples/example/soundToys.js b/examples/example/soundToys.js new file mode 100644 index 0000000000..9d8c824b90 --- /dev/null +++ b/examples/example/soundToys.js @@ -0,0 +1,66 @@ +"use strict"; +// Creates some objects that each play a sound when they are hit (or when they hit something else). +// +// Created by Howard Stearns on June 3, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var Camera, Vec3, Quat, Entities, Script; // Globals defined by HiFi, var'ed here to keep jslint happy. +var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var SOUND_BUCKET = "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/"; +var MAX_ANGULAR_SPEED = Math.PI; +var N_EACH_OBJECTS = 3; + +var ourToys = []; +function deleteAll() { + ourToys.forEach(Entities.deleteEntity); +} +function makeAll() { + var currentPosition = Vec3.sum(Camera.getPosition(), Vec3.multiply(4, Quat.getFront(Camera.getOrientation()))), + right = Vec3.multiply(0.6, Quat.getRight(Camera.getOrientation())), + currentDimensions, + data = [ + ["models/props/Dice/goldDie.fbx", HIFI_PUBLIC_BUCKET + "sounds/dice/diceCollide.wav"], + ["models/props/Pool/ball_8.fbx", HIFI_PUBLIC_BUCKET + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav"], + ["eric/models/woodFloor.fbx", SOUND_BUCKET + "67LCollision05.wav"] + ]; + currentPosition = Vec3.sum(currentPosition, Vec3.multiply(-1 * data.length * N_EACH_OBJECTS / 2, right)); + function makeOne(model, sound) { + var thisEntity; + function dropOnce() { // Once gravity is added, it will work if picked up and again dropped. + Entities.editEntity(thisEntity, {gravity: {x: 0, y: -9.8, z: 0}}); + Script.removeEventHandler(thisEntity, 'clickDownOnEntity', dropOnce); + } + thisEntity = Entities.addEntity({ + type: "Model", + modelURL: HIFI_PUBLIC_BUCKET + model, + collisionSoundURL: sound, + collisionsWillMove: true, + shapeType: "box", + restitution: 0.8, + dimensions: currentDimensions, + position: currentPosition, + angularVelocity: { + x: Math.random() * MAX_ANGULAR_SPEED, + y: Math.random() * MAX_ANGULAR_SPEED, + z: Math.random() * MAX_ANGULAR_SPEED + } + }); + ourToys.push(thisEntity); + Script.addEventHandler(thisEntity, 'clickDownOnEntity', dropOnce); + currentDimensions = Vec3.multiply(currentDimensions, 2); + currentPosition = Vec3.sum(currentPosition, right); + } + function makeThree(modelSound) { + var i, model = modelSound[0], sound = modelSound[1]; + currentDimensions = {x: 0.1, y: 0.1, z: 0.1}; + for (i = 0; i < N_EACH_OBJECTS; i++) { + makeOne(model, sound); + } + } + data.forEach(makeThree); +} +makeAll(); +Script.scriptEnding.connect(deleteAll); From 5cbb5dadca469653cf3e7b33ecb80fa779ff678f Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 4 Jun 2015 14:21:12 -0700 Subject: [PATCH 04/12] More perspicacious zero. --- libraries/physics/src/ObjectMotionState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 67dc8f294c..f7fc044c92 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -83,7 +83,7 @@ glm::vec3 ObjectMotionState::getBodyLinearVelocity() const { return bulletToGLM(_body->getLinearVelocity()); } glm::vec3 ObjectMotionState::getObjectLinearVelocityChange() const { - return glm::vec3(); // Subclasses override where meaningful. + return glm::vec3(0.0f); // Subclasses override where meaningful. } glm::vec3 ObjectMotionState::getBodyAngularVelocity() const { From 407d3b6a6eff1461d54dd6ff499b3d5e40bd94a5 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 4 Jun 2015 14:23:51 -0700 Subject: [PATCH 05/12] Pitch is dependent on (largest dimension of) minimum bounding, not max. Adjust sound range of full volume. --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 93e302c81b..7fda8214bb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1134,12 +1134,14 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT // The collision.penetration is a pretty good indicator of changed velocity AFTER the initial contact, // but that first contact depends on exactly where we hit in the physics step. // We can get a more consistent initial-contact energy reading by using the changed velocity. + // Note that velocityChange is not a good indicator for continuing collisions, because it does not distinguish + // between bounce and sliding along a surface. const float linearVelocity = (collision.type == CONTACT_EVENT_TYPE_START) ? glm::length(collision.velocityChange) : glm::length(collision.penetration) * COLLISION_PENETRATION_TO_VELOCITY; const float energy = mass * linearVelocity * linearVelocity / 2.0f; const glm::vec3 position = collision.contactPoint; - const float COLLISION_ENERGY_AT_FULL_VOLUME = (collision.type == CONTACT_EVENT_TYPE_START) ? 10.0f : 0.5f; + const float COLLISION_ENERGY_AT_FULL_VOLUME = (collision.type == CONTACT_EVENT_TYPE_START) ? 150.0f : 5.0f; const float COLLISION_MINIMUM_VOLUME = 0.005f; const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME); if (energyFactorOfFull < COLLISION_MINIMUM_VOLUME) { @@ -1172,7 +1174,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); soxr_quality_spec_t qualitySpec = soxr_quality_spec(SOXR_MQ, 0); const int channelCount = sound->isStereo() ? 2 : 1; - const float factor = log(1.0f + (entity->getMaximumAACube().getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); + const float factor = log(1.0f + (entity->getMinimumAACube().getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); const int standardRate = AudioConstants::SAMPLE_RATE; const int resampledRate = standardRate * factor; const int nInputSamples = samples.size() / sizeof(int16_t); From a2ae51371be4e39cae6620b03d39069989bc642b Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 15:44:39 -0700 Subject: [PATCH 06/12] updated pointer.js script to work with new line entity --- examples/pointer.js | 159 +++++++++----------------------------------- 1 file changed, 30 insertions(+), 129 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index 32698209a4..5b293c8478 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -1,30 +1,17 @@ // pointer.js // examples // -// Created by Eric Levin on May 26, 2015 +// Created by Seth Alves on May 15th +// Modified by Eric Levin on June 4 // Copyright 2015 High Fidelity, Inc. // // Provides a pointer with option to draw on surfaces // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// var lineEntityID = null; var lineIsRezzed = false; -var altHeld = false; -var lineCreated = false; -var position, positionOffset, prevPosition; -var nearLinePosition; -var strokes = []; -var STROKE_ADJUST = 0.005; -var DISTANCE_DRAW_THRESHOLD = .02; -var drawDistance = 0; - -var LINE_WIDTH = 20; - -var userCanPoint = false; -var userCanDraw = false; var BUTTON_SIZE = 32; var PADDING = 3; @@ -43,16 +30,7 @@ var buttonOnColor = { HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); -var drawButton = Overlays.addOverlay("image", { - x: screenSize.x / 2 - BUTTON_SIZE + PADDING * 2, - y: screenSize.y - (BUTTON_SIZE + PADDING), - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: HIFI_PUBLIC_BUCKET + "images/pencil.png?v2", - color: buttonOffColor, - alpha: 1 -}); - +var userCanPoint = false; var pointerButton = Overlays.addOverlay("image", { x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, y: screenSize.y - (BUTTON_SIZE + PADDING), @@ -61,14 +39,12 @@ var pointerButton = Overlays.addOverlay("image", { imageURL: HIFI_PUBLIC_BUCKET + "images/laser.png", color: buttonOffColor, alpha: 1 -}) +}); -var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2.0, Quat.getFront(Camera.getOrientation()))); -center.y += 0.5; -function calculateNearLinePosition(targetPosition) { +function nearLinePoint(targetPosition) { var handPosition = MyAvatar.getRightPalmPosition(); var along = Vec3.subtract(targetPosition, handPosition); along = Vec3.normalize(along); @@ -87,39 +63,40 @@ function removeLine() { function createOrUpdateLine(event) { - if (!userCanPoint) { - return; - } var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking var props = Entities.getEntityProperties(intersection.entityID); - if (intersection.intersects) { - startPosition = intersection.intersection; - var subtractVec = Vec3.multiply(Vec3.normalize(pickRay.direction), STROKE_ADJUST); - startPosition = Vec3.subtract(startPosition, subtractVec); - nearLinePosition = calculateNearLinePosition(intersection.intersection); - positionOffset = Vec3.subtract(startPosition, nearLinePosition); + if (intersection.intersects && userCanPoint) { + var points = [nearLinePoint(intersection.intersection), intersection.intersection] if (lineIsRezzed) { Entities.editEntity(lineEntityID, { - position: nearLinePosition, - dimensions: positionOffset, + position: nearLinePoint(intersection.intersection), + linePoints: points, + dimensions: { + x: 1, + y: 1, + z: 1 + }, + lifetime: 15 + props.lifespan // renew lifetime }); - if (userCanDraw) { - draw(); - } } else { lineIsRezzed = true; - prevPosition = startPosition; lineEntityID = Entities.addEntity({ type: "Line", - position: nearLinePosition, - dimensions: positionOffset, + position: nearLinePoint(intersection.intersection), + linePoints: points, + dimensions: { + x: 1, + y: 1, + z: 1 + }, color: { red: 255, green: 255, blue: 255 }, + lifetime: 15 // if someone crashes while pointing, don't leave the line there forever. }); } } else { @@ -127,120 +104,44 @@ function createOrUpdateLine(event) { } } -function draw() { - //We only want to draw line if distance between starting and previous point is large enough - drawDistance = Vec3.distance(startPosition, prevPosition); - if (drawDistance < DISTANCE_DRAW_THRESHOLD) { +function mousePressEvent(event) { + if (!event.isLeftButton) { return; } - var offset = Vec3.subtract(startPosition, prevPosition); - strokes.push(Entities.addEntity({ - type: "Line", - position: prevPosition, - dimensions: offset, - color: { - red: 200, - green: 40, - blue: 200 - }, - lineWidth: LINE_WIDTH - })); - prevPosition = startPosition; -} - -function mousePressEvent(event) { - var clickedOverlay = Overlays.getOverlayAtPoint({ + createOrUpdateLine(event); + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - if (clickedOverlay == drawButton) { - userCanDraw = !userCanDraw; - if (userCanDraw === true) { - Overlays.editOverlay(drawButton, { - color: buttonOnColor - }); - } else { - Overlays.editOverlay(drawButton, { - color: buttonOffColor - }); - } - } - if (clickedOverlay == pointerButton) { userCanPoint = !userCanPoint; if (userCanPoint === true) { Overlays.editOverlay(pointerButton, { color: buttonOnColor }); - if (userCanDraw === true) { - - Overlays.editOverlay(drawButton, { - color: buttonOnColor - }); - } } else { Overlays.editOverlay(pointerButton, { color: buttonOffColor }); - Overlays.editOverlay(drawButton, { - color: buttonOffColor - }); } } - - if (!event.isLeftButton || altHeld) { - return; - } - Controller.mouseMoveEvent.connect(mouseMoveEvent); - createOrUpdateLine(event); - lineCreated = true; } - function mouseMoveEvent(event) { createOrUpdateLine(event); } - function mouseReleaseEvent(event) { - if (!lineCreated) { + if (!event.isLeftButton) { return; } - Controller.mouseMoveEvent.disconnect(mouseMoveEvent); removeLine(); - lineCreated = false; } -function keyPressEvent(event) { - if (event.text == "ALT") { - altHeld = true; - } -} - -function keyReleaseEvent(event) { - if (event.text == "ALT") { - altHeld = false; - } - -} - -function cleanup() { - for (var i = 0; i < strokes.length; i++) { - Entities.deleteEntity(strokes[i]); - } - - Overlays.deleteOverlay(drawButton); - Overlays.deleteOverlay(pointerButton); -} - - -Script.scriptEnding.connect(cleanup); +Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); - -Controller.keyPressEvent.connect(keyPressEvent); -Controller.keyReleaseEvent.connect(keyReleaseEvent); \ No newline at end of file +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); \ No newline at end of file From 5e15c14539947639f1c2e96b73b4c56cfc1b324f Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 16:45:32 -0700 Subject: [PATCH 07/12] added sanitization for invalid line points data. Fixed bug where occasionally wrong view frustum would be sent to script" --- interface/src/Application.cpp | 7 +++++-- .../src/RenderableLineEntityItem.cpp | 5 ++++- libraries/entities/src/LineEntityItem.cpp | 13 ++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e800fa5ff0..e3c5d735a8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3093,8 +3093,11 @@ PickRay Application::computePickRay(float x, float y) const { if (isHMDMode()) { getApplicationOverlay().computeHmdPickRay(glm::vec2(x, y), result.origin, result.direction); } else { - auto frustum = activeRenderingThread ? getDisplayViewFrustum() : getViewFrustum(); - frustum->computePickRay(x, y, result.origin, result.direction); + if (QThread::currentThread() == activeRenderingThread) { + getDisplayViewFrustum()->computePickRay(x, y, result.origin, result.direction); + } else { + getViewFrustum()->computePickRay(x, y, result.origin, result.direction); + } } return result; } diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index 1951b2592b..22fef37ce3 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -44,7 +44,10 @@ void RenderableLineEntityItem::render(RenderArgs* args) { geometryCache->updateVertices(_lineVerticesID, getLinePoints(), lineColor); _pointsChanged = false; } - geometryCache->renderVertices(gpu::LINE_STRIP, _lineVerticesID); + if (getLinePoints().size() > 1) { + geometryCache->renderVertices(gpu::LINE_STRIP, _lineVerticesID); + } + glPopMatrix(); RenderableDebugableEntityItem::render(this, args); diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index c43de8d076..0c6b73a7ce 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -85,7 +85,18 @@ bool LineEntityItem::setProperties(const EntityItemProperties& properties) { } void LineEntityItem::setLinePoints(const QVector& points) { - _points = points; + QVector sanitizedPoints; + for (int i = 0; i < points.size(); i++) { + glm::vec3 point = points.at(i); + // Make sure all of our points are valid numbers. + // Must be greater than 0 because vector component is set to 0 if it is invalid data + if (point.x > 0 && point.y > 0 && point.z > 0){ + sanitizedPoints << point; + } else { + qDebug() << "INVALID POINT"; + } + } + _points = sanitizedPoints; _pointsChanged = true; } From 1fd0650955295101e5cb78775f43e187ecc1bbfa Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 16:54:52 -0700 Subject: [PATCH 08/12] removing overlay on script ending --- examples/pointer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/pointer.js b/examples/pointer.js index 5b293c8478..cca46709ee 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -142,6 +142,12 @@ function mouseReleaseEvent(event) { removeLine(); } +function cleanup() { + Overlays.deleteOverlay(pointerButton); +} + +Script.scriptEnding.connect(cleanup); + Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); \ No newline at end of file From 241330442c1575bc69dcceab4df590cf6f66d746 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 17:35:26 -0700 Subject: [PATCH 09/12] added paint.js and line rider script. Users can paint lines with hydra or mouse, and then go on a ride over the line they just created. --- examples/lineRider.js | 117 +++++++++++ examples/paint.js | 478 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 595 insertions(+) create mode 100644 examples/lineRider.js create mode 100644 examples/paint.js diff --git a/examples/lineRider.js b/examples/lineRider.js new file mode 100644 index 0000000000..21a690fc63 --- /dev/null +++ b/examples/lineRider.js @@ -0,0 +1,117 @@ +// +// lineRider.js +// examples +// +// Created by Eric Levin on 6/4/15. +// Copyright 2014 High Fidelity, Inc. +// +// Takes the avatar on a line ride. Meant to be used in conjunction with paint.js +// Paint a line and then click on roller coaster icon to start! +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +LineRider = function() { + HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + var screenSize = Controller.getViewportDimensions(); + + var BUTTON_SIZE = 32; + var PADDING = 3; + + this.buttonOffColor = { + red: 250, + green: 10, + blue: 10 + }; + this.buttonOnColor = { + red: 10, + green: 200, + blue: 100 + }; + this.riding = false; + + this.startButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE + PADDING * 2, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/coaster.png?v2", + color: this.buttonOffColor, + alpha: 1 + }); + + this.currentPoint = 0; + this.shouldUpdate = false; + this.moveIntervalTime = 50; + +} + + +LineRider.prototype.move = function() { + if (!this.shouldMove) { + return; + } + MyAvatar.position = this.points[this.currentPoint++]; + + if (this.currentPoint === this.points.length) { + this.currentPoint = 0; + } + var self = this; + Script.setTimeout(function() { + self.move(); + }, this.moveIntervalTime); +} + +LineRider.prototype.setPath = function(points) { + this.points = points; +} + +LineRider.prototype.addStartHandler = function(callback) { + this.onStart = callback; +} + + +LineRider.prototype.mousePressEvent = function(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == this.startButton) { + this.toggleRide(); + + } + +} + +LineRider.prototype.toggleRide = function() { + this.riding = !this.riding; + if (this.riding === true) { + Overlays.editOverlay(this.startButton, { + color: this.buttonOnColor + }); + if (this.onStart) { + this.onStart(); + //make sure we actually have a path + if (this.points.length > 2) { + this.shouldMove = true; + } + var self = this; + Script.setTimeout(function() { + self.move(); + }, this.moveIntervalTime); + } + } else { + Overlays.editOverlay(this.startButton, { + color: this.buttonOffColor + }) + this.shouldMove = false; + } + +} +LineRider.prototype.startRide = function() { + this.shouldUpdate = true; + +} + +LineRider.prototype.cleanup = function() { + Overlays.deleteOverlay(this.startButton); +} \ No newline at end of file diff --git a/examples/paint.js b/examples/paint.js new file mode 100644 index 0000000000..9a774a1442 --- /dev/null +++ b/examples/paint.js @@ -0,0 +1,478 @@ +// +// paint.js +// examples +// +// Created by Eric Levin on 6/4/15. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to paint with the hydra or mouse! +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +Script.include('lineRider.js') +var MAX_POINTS_PER_LINE = 30; +var DRAWING_DISTANCE = 5; + +var colorPalette = [{ + red: 236, + green: 208, + blue: 120 +}, { + red: 217, + green: 91, + blue: 67 +}, { + red: 192, + green: 41, + blue: 66 +}, { + red: 84, + green: 36, + blue: 55 +}, { + red: 83, + green: 119, + blue: 122 +}]; + +var currentColorIndex = 0; +var currentColor = colorPalette[currentColorIndex]; + + + +if (hydraCheck() === true) { + HydraPaint(); +} else { + MousePaint(); +} + + +function cycleColor() { + currentColor = colorPalette[++currentColorIndex]; + if (currentColorIndex === colorPalette.length - 1) { + currentColorIndex = -1; + } + +} + +function hydraCheck() { + var numberOfButtons = Controller.getNumberOfButtons(); + var numberOfTriggers = Controller.getNumberOfTriggers(); + var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); + var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + hydrasConnected = (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2); + return hydrasConnected; //hydrasConnected; +} + + +function MousePaint() { + var lines = []; + var isDrawing = false; + var path = []; + + var lineRider = new LineRider(); + lineRider.addStartHandler(function() { + var points = []; + //create points array from list of all points in path + path.forEach(function(point) { + points.push(point); + }); + lineRider.setPath(points); + }); + + + + var LINE_WIDTH = 7; + var line; + var points = []; + + + var BRUSH_SIZE = 0.08; + + var brush = Entities.addEntity({ + type: 'Sphere', + position: { + x: 0, + y: 0, + z: 0 + }, + color: currentColor, + dimensions: { + x: BRUSH_SIZE, + y: BRUSH_SIZE, + z: BRUSH_SIZE + } + }); + + + function newLine(point) { + line = Entities.addEntity({ + position: MyAvatar.position, + type: "Line", + color: currentColor, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + lineWidth: LINE_WIDTH + }); + points = []; + if (point) { + points.push(point); + path.push(point); + } + lines.push(line); + } + + + function mouseMoveEvent(event) { + if (!isDrawing) { + return; + } + + + var pickRay = Camera.computePickRay(event.x, event.y); + var addVector = Vec3.multiply(Vec3.normalize(pickRay.direction), DRAWING_DISTANCE); + var point = Vec3.sum(Camera.getPosition(), addVector); + points.push(point); + Entities.editEntity(line, { + linePoints: points + }); + Entities.editEntity(brush, { + position: point + }); + path.push(point); + + if (points.length === MAX_POINTS_PER_LINE) { + //We need to start a new line! + newLine(point); + } + } + + function mousePressEvent(event) { + lineRider.mousePressEvent(event); + path = []; + newLine(); + isDrawing = true; + + + } + + function mouseReleaseEvent() { + isDrawing = false; + } + + function keyPressEvent(event) { + if (event.text === "SPACE") { + cycleColor(); + Entities.editEntity(brush, { + color: currentColor + }); + } + } + + function cleanup() { + lines.forEach(function(line) { + Entities.deleteEntity(line); + }); + Entities.deleteEntity(brush); + + } + + + Controller.mousePressEvent.connect(mousePressEvent); + Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + Controller.mouseMoveEvent.connect(mouseMoveEvent); + Script.scriptEnding.connect(cleanup); + + function randFloat(low, high) { + return Math.floor(low + Math.random() * (high - low)); + } + + + function randInt(low, high) { + return Math.floor(randFloat(low, high)); + } + + Controller.keyPressEvent.connect(keyPressEvent); +} + + + +//*****************HYDRA PAINT ******************************************* + + + + +function HydraPaint() { + + + + var lineRider = new LineRider(); + lineRider.addStartHandler(function() { + var points = []; + //create points array from list of all points in path + rightController.path.forEach(function(point) { + points.push(point); + }); + lineRider.setPath(points); + }); + + var LEFT = 0; + var RIGHT = 1; + + var currentTime = 0; + + + var DISTANCE_FROM_HAND = 2; + var minBrushSize = .02; + var maxBrushSize = .04 + + + var minLineWidth = 5; + var maxLineWidth = 10; + var currentLineWidth = minLineWidth; + var MIN_PAINT_TRIGGER_THRESHOLD = .01; + var LINE_LIFETIME = 20; + var COLOR_CHANGE_TIME_FACTOR = 0.1; + + var RIGHT_BUTTON_1 = 7 + var RIGHT_BUTTON_2 = 8 + var RIGHT_BUTTON_3 = 9; + var RIGHT_BUTTON_4 = 10 + + var LEFT_BUTTON_1 = 1; + var LEFT_BUTTON_2 = 2; + var LEFT_BUTTON_3 = 3; + var LEFT_BUTTON_4 = 4; + + var STROKE_SMOOTH_FACTOR = 1; + + var MIN_DRAW_DISTANCE = 1; + var MAX_DRAW_DISTANCE = 2; + + function controller(side, undoButton, redoButton, cycleColorButton, startRideButton) { + this.triggerHeld = false; + this.triggerThreshold = 0.9; + this.side = side; + this.palm = 2 * side; + this.tip = 2 * side + 1; + this.trigger = side; + this.lines = []; + this.deletedLines = [] //just an array of properties objects + this.isPainting = false; + + this.undoButton = undoButton; + this.undoButtonPressed = false; + this.prevUndoButtonPressed = false; + + this.redoButton = redoButton; + this.redoButtonPressed = false; + this.prevRedoButtonPressed = false; + + this.cycleColorButton = cycleColorButton; + this.cycleColorButtonPressed = false; + this.prevColorCycleButtonPressed = false; + + this.startRideButton = startRideButton; + this.startRideButtonPressed = false; + this.prevStartRideButtonPressed = false; + + this.strokeCount = 0; + this.currentBrushSize = minBrushSize; + this.points = []; + this.path = []; + + this.brush = Entities.addEntity({ + type: 'Sphere', + position: { + x: 0, + y: 0, + z: 0 + }, + color: currentColor, + dimensions: { + x: minBrushSize, + y: minBrushSize, + z: minBrushSize + } + }); + + + this.newLine = function(point) { + this.line = Entities.addEntity({ + position: MyAvatar.position, + type: "Line", + color: currentColor, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + lineWidth: 5, + // lifetime: LINE_LIFETIME + }); + this.points = []; + if (point) { + this.points.push(point); + this.path.push(point); + } + this.lines.push(this.line); + } + + this.update = function(deltaTime) { + this.updateControllerState(); + this.avatarPalmOffset = Vec3.subtract(this.palmPosition, MyAvatar.position); + this.projectedForwardDistance = Vec3.dot(Quat.getFront(Camera.getOrientation()), this.avatarPalmOffset); + this.mappedPalmOffset = map(this.projectedForwardDistance, -.5, .5, MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); + this.tipDirection = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); + this.offsetVector = Vec3.multiply(this.mappedPalmOffset, this.tipDirection); + this.drawPoint = Vec3.sum(this.palmPosition, this.offsetVector); + this.currentBrushSize = map(this.triggerValue, 0, 1, minBrushSize, maxBrushSize); + Entities.editEntity(this.brush, { + position: this.drawPoint, + dimensions: { + x: this.currentBrushSize, + y: this.currentBrushSize, + z: this.currentBrushSize + }, + color: currentColor + }); + if (this.triggerValue > MIN_PAINT_TRIGGER_THRESHOLD) { + if (!this.isPainting) { + this.isPainting = true; + this.newLine(); + this.path = []; + } + if (this.strokeCount % STROKE_SMOOTH_FACTOR === 0) { + this.paint(this.drawPoint); + } + this.strokeCount++; + } else if (this.triggerValue < MIN_PAINT_TRIGGER_THRESHOLD && this.isPainting) { + this.releaseTrigger(); + } + + this.oldPalmPosition = this.palmPosition; + this.oldTipPosition = this.tipPosition; + } + + this.releaseTrigger = function() { + this.isPainting = false; + + } + + + this.updateControllerState = function() { + this.undoButtonPressed = Controller.isButtonPressed(this.undoButton); + this.redoButtonPressed = Controller.isButtonPressed(this.redoButton); + this.cycleColorButtonPressed = Controller.isButtonPressed(this.cycleColorButton); + this.startRideButtonPressed = Controller.isButtonPressed(this.startRideButton); + + //This logic gives us button release + if (this.prevUndoButtonPressed === true && this.undoButtonPressed === false) { + //User released undo button, so undo + this.undoStroke(); + } + if (this.prevRedoButtonPressed === true && this.redoButtonPressed === false) { + this.redoStroke(); + } + + if (this.prevCycleColorButtonPressed === true && this.cycleColorButtonPressed === false) { + cycleColor(); + Entities.editEntity(this.brush, { + color: currentColor + }); + } + if (this.prevStartRideButtonPressed === true && this.startRideButtonPressed === false) { + lineRider.toggleRide(); + } + this.prevRedoButtonPressed = this.redoButtonPressed; + this.prevUndoButtonPressed = this.undoButtonPressed; + this.prevCycleColorButtonPressed = this.cycleColorButtonPressed; + this.prevStartRideButtonPressed = this.startRideButtonPressed; + + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); + this.triggerValue = Controller.getTriggerValue(this.trigger); + } + + this.undoStroke = function() { + var deletedLine = this.lines.pop(); + var deletedLineProps = Entities.getEntityProperties(deletedLine); + this.deletedLines.push(deletedLineProps); + Entities.deleteEntity(deletedLine); + } + + this.redoStroke = function() { + var restoredLine = Entities.addEntity(this.deletedLines.pop()); + Entities.addEntity(restoredLine); + this.lines.push(restoredLine); + } + + this.paint = function(point) { + + currentLineWidth = map(this.triggerValue, 0, 1, minLineWidth, maxLineWidth); + this.points.push(point); + this.path.push(point); + Entities.editEntity(this.line, { + linePoints: this.points, + lineWidth: currentLineWidth, + color: this.rgbColor + }); + if (this.points.length > MAX_POINTS_PER_LINE) { + this.newLine(point); + } + } + + this.cleanup = function() { + Entities.deleteEntity(this.brush); + this.lines.forEach(function(line) { + Entities.deleteEntity(line); + }); + } + } + + function update(deltaTime) { + rightController.update(deltaTime); + leftController.update(deltaTime); + currentTime += deltaTime; + } + + function scriptEnding() { + rightController.cleanup(); + leftController.cleanup(); + lineRider.cleanup(); + } + + function mousePressEvent(event) { + lineRider.mousePressEvent(event); + } + + function vectorIsZero(v) { + return v.x === 0 && v.y === 0 && v.z === 0; + } + + + var rightController = new controller(RIGHT, RIGHT_BUTTON_3, RIGHT_BUTTON_4, RIGHT_BUTTON_1, RIGHT_BUTTON_2); + var leftController = new controller(LEFT, LEFT_BUTTON_3, LEFT_BUTTON_4, LEFT_BUTTON_1, LEFT_BUTTON_2); + + Script.update.connect(update); + Script.scriptEnding.connect(scriptEnding); + Controller.mousePressEvent.connect(mousePressEvent); + + function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); + } + + function randFloat(low, high) { + return Math.floor(low + Math.random() * (high - low)); + } + + + function randInt(low, high) { + return Math.floor(randFloat(low, high)); + } +} \ No newline at end of file From f276ca30cd9c656d690409348d5cab1cbae41559 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 17:55:55 -0700 Subject: [PATCH 10/12] refactoring for common functions. Fix for randFloat --- examples/paint.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/examples/paint.js b/examples/paint.js index 9a774a1442..670ffe047c 100644 --- a/examples/paint.js +++ b/examples/paint.js @@ -188,15 +188,6 @@ function MousePaint() { Controller.mouseMoveEvent.connect(mouseMoveEvent); Script.scriptEnding.connect(cleanup); - function randFloat(low, high) { - return Math.floor(low + Math.random() * (high - low)); - } - - - function randInt(low, high) { - return Math.floor(randFloat(low, high)); - } - Controller.keyPressEvent.connect(keyPressEvent); } @@ -420,7 +411,6 @@ function HydraPaint() { Entities.editEntity(this.line, { linePoints: this.points, lineWidth: currentLineWidth, - color: this.rgbColor }); if (this.points.length > MAX_POINTS_PER_LINE) { this.newLine(point); @@ -463,16 +453,17 @@ function HydraPaint() { Script.scriptEnding.connect(scriptEnding); Controller.mousePressEvent.connect(mousePressEvent); - function map(value, min1, max1, min2, max2) { - return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); - } +} function randFloat(low, high) { - return Math.floor(low + Math.random() * (high - low)); + return low + Math.random() * ( high - low ); } function randInt(low, high) { return Math.floor(randFloat(low, high)); } -} \ No newline at end of file + + function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); + } \ No newline at end of file From 9264ef980c4d29d731571478f42481895258dfa9 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 21:13:16 -0700 Subject: [PATCH 11/12] added redo and undo functionality to mouse component of paint script, and fixed overlay not being deleted on script ending bug --- examples/paint.js | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/examples/paint.js b/examples/paint.js index 670ffe047c..787e9156bc 100644 --- a/examples/paint.js +++ b/examples/paint.js @@ -66,11 +66,13 @@ function hydraCheck() { return hydrasConnected; //hydrasConnected; } +//************ Mouse Paint ************************** function MousePaint() { var lines = []; + var deletedLines = []; var isDrawing = false; - var path = []; + var path = []; var lineRider = new LineRider(); lineRider.addStartHandler(function() { @@ -152,6 +154,19 @@ function MousePaint() { } } + function undoStroke() { + var deletedLine = lines.pop(); + var deletedLineProps = Entities.getEntityProperties(deletedLine); + deletedLines.push(deletedLineProps); + Entities.deleteEntity(deletedLine); + } + + function redoStroke() { + var restoredLine = Entities.addEntity(deletedLines.pop()); + Entities.addEntity(restoredLine); + lines.push(restoredLine); + } + function mousePressEvent(event) { lineRider.mousePressEvent(event); path = []; @@ -172,6 +187,12 @@ function MousePaint() { color: currentColor }); } + if (event.text === "z") { + undoStroke(); + } + if(event.text === "x") { + redoStroke(); + } } function cleanup() { @@ -179,6 +200,7 @@ function MousePaint() { Entities.deleteEntity(line); }); Entities.deleteEntity(brush); + lineRider.cleanup(); } @@ -197,7 +219,6 @@ function MousePaint() { - function HydraPaint() { @@ -431,7 +452,7 @@ function HydraPaint() { currentTime += deltaTime; } - function scriptEnding() { + function cleanup() { rightController.cleanup(); leftController.cleanup(); lineRider.cleanup(); @@ -450,20 +471,20 @@ function HydraPaint() { var leftController = new controller(LEFT, LEFT_BUTTON_3, LEFT_BUTTON_4, LEFT_BUTTON_1, LEFT_BUTTON_2); Script.update.connect(update); - Script.scriptEnding.connect(scriptEnding); + Script.scriptEnding.connect(cleanup); Controller.mousePressEvent.connect(mousePressEvent); } - function randFloat(low, high) { - return low + Math.random() * ( high - low ); - } +function randFloat(low, high) { + return low + Math.random() * (high - low); +} - function randInt(low, high) { - return Math.floor(randFloat(low, high)); - } +function randInt(low, high) { + return Math.floor(randFloat(low, high)); +} - function map(value, min1, max1, min2, max2) { - return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); - } \ No newline at end of file +function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); +} \ No newline at end of file From fd8126510d15d1c38f0e0354c40c5525f6980349 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 21:41:19 -0700 Subject: [PATCH 12/12] marker ball now stays without mouse whether or not user is drawing --- examples/paint.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/paint.js b/examples/paint.js index 787e9156bc..837b217797 100644 --- a/examples/paint.js +++ b/examples/paint.js @@ -131,21 +131,22 @@ function MousePaint() { function mouseMoveEvent(event) { - if (!isDrawing) { - return; - } var pickRay = Camera.computePickRay(event.x, event.y); var addVector = Vec3.multiply(Vec3.normalize(pickRay.direction), DRAWING_DISTANCE); var point = Vec3.sum(Camera.getPosition(), addVector); - points.push(point); Entities.editEntity(line, { linePoints: points }); Entities.editEntity(brush, { position: point }); + if (!isDrawing) { + return; + } + + points.push(point); path.push(point); if (points.length === MAX_POINTS_PER_LINE) { @@ -168,6 +169,10 @@ function MousePaint() { } function mousePressEvent(event) { + if(!event.isLeftButton) { + isDrawing = false; + return; + } lineRider.mousePressEvent(event); path = []; newLine();