From 35b5dd520edbbfc3da73da6239c6c6e73e650cc0 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 17 Nov 2014 20:12:58 -0800 Subject: [PATCH 1/3] added simulated holdingClickOnEntity in case of no mouse move --- interface/src/entities/EntityTreeRenderer.cpp | 32 ++++++++++++++++++- interface/src/entities/EntityTreeRenderer.h | 4 +++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index f8fd9cf298..016b7b5363 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -46,7 +46,9 @@ QThread* EntityTreeRenderer::getMainThread() { EntityTreeRenderer::EntityTreeRenderer(bool wantScripts) : OctreeRenderer(), _wantScripts(wantScripts), - _entitiesScriptEngine(NULL) { + _entitiesScriptEngine(NULL), + _lastMouseEventValid(false) +{ REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) @@ -202,6 +204,19 @@ void EntityTreeRenderer::update() { // check to see if the avatar has moved and if we need to handle enter/leave entity logic checkEnterLeaveEntities(); + + // Even if we're not moving the mouse, if we started clicking on an entity and we have + // not yet released the hold then this is still considered a holdingClickOnEntity event + // and we want to simulate this message here as well as in mouse move + if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) { + emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent); + QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, _lastMouseEvent); + QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); + if (currentClickingEntity.property("holdingClickOnEntity").isValid()) { + currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); + } + } + } } @@ -667,6 +682,14 @@ QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& en return args; } +QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent) { + QScriptValueList args; + args << entityID.toScriptValue(_entitiesScriptEngine); + args << mouseEvent.toScriptValue(_entitiesScriptEngine); + return args; +} + + QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) { QScriptValueList args; args << entityID.toScriptValue(_entitiesScriptEngine); @@ -693,6 +716,8 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs); } } + _lastMouseEvent = MouseEvent(*event, deviceID); + _lastMouseEventValid = true; } void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { @@ -724,10 +749,13 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi // makes it the unknown ID, we just released so we can't be clicking on anything _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); + _lastMouseEvent = MouseEvent(*event, deviceID); + _lastMouseEventValid = true; } void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); + PickRay ray = computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock); if (rayPickResult.intersects) { @@ -809,6 +837,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); } } + _lastMouseEvent = MouseEvent(*event, deviceID); + _lastMouseEventValid = true; } void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index c94eb78b77..7a8155cd6b 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -129,8 +129,12 @@ private: QScriptValue loadEntityScript(const EntityItemID& entityItemID); QString loadScriptContents(const QString& scriptMaybeURLorText); QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); + QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent); QHash _entityScripts; + + bool _lastMouseEventValid; + MouseEvent _lastMouseEvent; }; #endif // hifi_EntityTreeRenderer_h From bc4e7ab66289e7b397cc4852a48de30f00b2d448 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 17 Nov 2014 20:13:17 -0800 Subject: [PATCH 2/3] add rotate support to movable.js --- examples/entityScripts/movable.js | 224 +++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 5 deletions(-) diff --git a/examples/entityScripts/movable.js b/examples/entityScripts/movable.js index 432a0be3f0..fc0e2f8a8f 100644 --- a/examples/entityScripts/movable.js +++ b/examples/entityScripts/movable.js @@ -13,6 +13,28 @@ this.entityID = null; this.properties = null; this.graboffset = null; + this.clickedAt = null; + this.firstHolding = true; + this.clickedX = -1; + this.clickedY = -1; + this.rotateOverlayTarget = null; + this.rotateOverlayInner = null; + this.rotateOverlayOuter = null; + this.rotateOverlayCurrent = null; + this.rotateMode = false; + this.originalRotation = null; + + var rotateOverlayTargetSize = 10000; // really big target + var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool + var innerRadius; + var outerRadius; + var yawCenter; + var yawZero; + var rotationNormal; + var yawNormal; + + var debug = true; + // Pr, Vr are respectively the Ray's Point of origin and Vector director // Pp, Np are respectively the Plane's Point of origin and Normal vector @@ -40,7 +62,6 @@ var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, this.properties.position, upVector); this.graboffset = Vec3.subtract(this.properties.position, intersection); - this.updatePosition(mouseEvent); }; this.move = function(mouseEvent) { @@ -51,6 +72,56 @@ this.updatePosition(mouseEvent); }; + this.rotate = function(mouseEvent) { + var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) + var result = Overlays.findRayIntersection(pickRay); + + if (result.intersects) { + var center = yawCenter; + var zero = yawZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + // var innerRadius = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); + Entities.editEntity(this.entityID, { rotation: Quat.multiply(yawChange, this.originalRotation) }); + + + // update the rotation display accordingly... + var startAtCurrent = 360-angleFromZero; + var endAtCurrent = 360; + var startAtRemainder = 0; + var endAtRemainder = 360-angleFromZero; + if (angleFromZero < 0) { + startAtCurrent = 0; + endAtCurrent = -angleFromZero; + startAtRemainder = -angleFromZero; + endAtRemainder = 360; + } + + if (snapToInner) { + Overlays.editOverlay(this.rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(this.rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(this.rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, + majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + } else { + Overlays.editOverlay(this.rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(this.rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(this.rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, + majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + } + } + }; // All callbacks start by updating the properties this.updateProperties = function(entityID) { if (this.entityID === null || !this.entityID.isKnownID) { @@ -58,7 +129,109 @@ } this.properties = Entities.getEntityProperties(this.entityID); }; - + + this.cleanupRotateOverlay = function() { + Overlays.deleteOverlay(this.rotateOverlayTarget); + Overlays.deleteOverlay(this.rotateOverlayInner); + Overlays.deleteOverlay(this.rotateOverlayOuter); + Overlays.deleteOverlay(this.rotateOverlayCurrent); + this.rotateOverlayTarget = null; + this.rotateOverlayInner = null; + this.rotateOverlayOuter = null; + this.rotateOverlayCurrent = null; + } + + this.displayRotateOverlay = function(mouseEvent) { + var yawOverlayAngles = { x: 90, y: 0, z: 0 }; + var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); + + yawNormal = { x: 0, y: 1, z: 0 }; + yawCenter = this.properties.position; + rotationNormal = yawNormal; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(this.properties.dimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(this.properties.dimensions, 0.5); + innerRadius = diagonal; + outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + + this.rotateOverlayTarget = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: 10000, + color: { red: 0, green: 0, blue: 0 }, + alpha: 0.0, + solid: true, + visible: true, + rotation: yawOverlayRotation, + ignoreRayIntersection: false + }); + + this.rotateOverlayInner = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: innerRadius, + innerRadius: 0.9, + alpha: innerAlpha, + color: { red: 51, green: 152, blue: 203 }, + solid: true, + visible: true, + rotation: yawOverlayRotation, + hasTickMarks: true, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this + }); + + this.rotateOverlayOuter = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + color: { red: 51, green: 152, blue: 203 }, + solid: true, + visible: true, + rotation: yawOverlayRotation, + + hasTickMarks: true, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this + }); + + this.rotateOverlayCurrent = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + color: { red: 224, green: 67, blue: 36}, + alpha: 0.8, + solid: true, + visible: true, + rotation: yawOverlayRotation, + ignoreRayIntersection: true, // always ignore this + hasTickMarks: true, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + }); + + var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) + var result = Overlays.findRayIntersection(pickRay); + yawZero = result.intersection; + + }; + this.preload = function(entityID) { this.updateProperties(entityID); // All callbacks start by updating the properties }; @@ -66,15 +239,56 @@ this.clickDownOnEntity = function(entityID, mouseEvent) { this.updateProperties(entityID); // All callbacks start by updating the properties this.grab(mouseEvent); + + var d = new Date(); + this.clickedAt = d.getTime(); + this.firstHolding = true; + + this.clickedX = mouseEvent.x; + this.clickedY = mouseEvent.y; }; - + this.holdingClickOnEntity = function(entityID, mouseEvent) { + this.updateProperties(entityID); // All callbacks start by updating the properties - this.move(mouseEvent); + + if (this.firstHolding) { + // if we haven't moved yet... + if (this.clickedX == mouseEvent.x && this.clickedY == mouseEvent.y) { + var d = new Date(); + var now = d.getTime(); + + if (now - this.clickedAt > 500) { + this.displayRotateOverlay(mouseEvent); + this.firstHolding = false; + this.rotateMode = true; + this.originalRotation = this.properties.rotation; + } + } else { + this.firstHolding = false; + } + } + + if (this.rotateMode) { + this.rotate(mouseEvent); + } else { + this.move(mouseEvent); + } }; this.clickReleaseOnEntity = function(entityID, mouseEvent) { this.updateProperties(entityID); // All callbacks start by updating the properties - this.release(mouseEvent); + if (this.rotateMode) { + this.rotate(mouseEvent); + } else { + this.release(mouseEvent); + } + + if (this.rotateOverlayTarget != null) { + this.cleanupRotateOverlay(); + this.rotateMode = false; + } + + this.firstHolding = false; }; }) From 8f9ca32a2bb84de0427bb24450accc2bf58b853c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 17 Nov 2014 22:13:49 -0800 Subject: [PATCH 3/3] added sounds to moving --- examples/entityScripts/movable.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/examples/entityScripts/movable.js b/examples/entityScripts/movable.js index fc0e2f8a8f..94ed7137fe 100644 --- a/examples/entityScripts/movable.js +++ b/examples/entityScripts/movable.js @@ -23,6 +23,8 @@ this.rotateOverlayCurrent = null; this.rotateMode = false; this.originalRotation = null; + this.sound = null; + this.injector = null; var rotateOverlayTargetSize = 10000; // really big target var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool @@ -34,7 +36,28 @@ var yawNormal; var debug = true; + + // Download sound if needed + this.maybeDownloadSound = function() { + if (this.sound === null) { + this.sound = SoundCache.getSound("http://public.highfidelity.io/sounds/Collisions-otherorganic/whoosh2.raw"); + } + } + // Play drag sound + this.playSound = function() { + this.stopSound(); + if (this.sound && this.sound.downloaded) { + this.injector = Audio.playSound(this.sound, { position: this.properties.position, loop: true, volume: 0.1 }); + } + } + // stop drag sound + this.stopSound = function() { + if (this.injector) { + Audio.stopInjector(this.injector); + this.injector = null; + } + } // Pr, Vr are respectively the Ray's Point of origin and Vector director // Pp, Np are respectively the Plane's Point of origin and Normal vector @@ -66,6 +89,9 @@ this.move = function(mouseEvent) { this.updatePosition(mouseEvent); + if (this.injector === null) { + this.playSound(); + } }; this.release = function(mouseEvent) { @@ -234,6 +260,7 @@ this.preload = function(entityID) { this.updateProperties(entityID); // All callbacks start by updating the properties + this.maybeDownloadSound(); }; this.clickDownOnEntity = function(entityID, mouseEvent) { @@ -289,6 +316,7 @@ } this.firstHolding = false; + this.stopSound(); }; })