diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 117ee8c453..44aa1146d4 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -584,7 +584,7 @@ function MyController(hand, triggerAction) { this.setState(STATE_RELEASE); return; } - + Entities.callEntityMethod(this.grabbedEntity, "continueNearGrabbingNonColliding"); }; diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index 3583aad76a..ab86007e4b 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -253,3 +253,21 @@ orientationOf = function(vector) { return Quat.multiply(yaw, pitch); } +randFloat = function(low, high) { + return low + Math.random() * (high - low); +} + + +randInt = function(low, high) { + return Math.floor(randFloat(low, high)); +} + +hexToRgb = function(hex) { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + red: parseInt(result[1], 16), + green: parseInt(result[2], 16), + blue: parseInt(result[3], 16) + } : null; +} + diff --git a/examples/closePaint.js b/examples/painting/closePaint.js similarity index 100% rename from examples/closePaint.js rename to examples/painting/closePaint.js diff --git a/examples/paint.js b/examples/painting/paint.js similarity index 100% rename from examples/paint.js rename to examples/painting/paint.js diff --git a/examples/painting/whiteboard/blackInk.fs b/examples/painting/whiteboard/blackInk.fs new file mode 100644 index 0000000000..1e77db285c --- /dev/null +++ b/examples/painting/whiteboard/blackInk.fs @@ -0,0 +1,23 @@ +vec2 iResolution = iWorldScale.xy; +vec2 iMouse = vec2(0); + +const float PI = 3.14159265; + +float time = iGlobalTime; +vec2 center = vec2(0.5, 0.5); +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 position = (fragCoord.xy/iResolution.xy) + 0.5; + float dist = pow(distance(position.xy, center), 3.); + dist = dist / 1.0 + sin(time * 10)/100.0; + vec3 color = vec3(dist, 0.0, dist); + fragColor = vec4(color, 1.0); +} + +vec4 getProceduralColor() { + vec4 result; + vec2 position = _position.xy; + + mainImage(result, position * iWorldScale.xy); + + return result; +} \ No newline at end of file diff --git a/examples/painting/whiteboard/colorIndicatorEntityScript.js b/examples/painting/whiteboard/colorIndicatorEntityScript.js new file mode 100644 index 0000000000..27b8ca01a1 --- /dev/null +++ b/examples/painting/whiteboard/colorIndicatorEntityScript.js @@ -0,0 +1,42 @@ +// +// colorIndicatorEntityScript.js +// examples/painting/whiteboard +// +// Created by Eric Levin on 9/21/15. +// 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 +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ +/*global ColorIndicator */ + +(function() { + + var _this; + ColorIndicator = function() { + _this = this; + }; + + ColorIndicator.prototype = { + + changeColor: function() { + var userData = JSON.parse(Entities.getEntityProperties(this.whiteboard, "userData").userData); + var newColor = userData.color.currentColor; + Entities.editEntity(this.entityID, { + color: newColor + }); + }, + + preload: function(entityID) { + this.entityID = entityID; + var props = Entities.getEntityProperties(this.entityID, ["position", "userData"]); + this.position = props.position; + this.whiteboard = JSON.parse(props.userData).whiteboard; + }, + + }; + + // entity scripts always need to return a newly constructed object of our type + return new ColorIndicator(); +}); \ No newline at end of file diff --git a/examples/painting/whiteboard/colorSelectorEntityScript.js b/examples/painting/whiteboard/colorSelectorEntityScript.js new file mode 100644 index 0000000000..f1105604c7 --- /dev/null +++ b/examples/painting/whiteboard/colorSelectorEntityScript.js @@ -0,0 +1,49 @@ +// +// colorSelectorEntityScript.js +// examples/painting/whiteboard +// +// Created by Eric Levin on 9/21/15. +// 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 +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ +/*global ColorSelector */ + +(function() { + Script.include("../../libraries/utils.js"); + var _this; + ColorSelector = function() { + _this = this; + }; + + ColorSelector.prototype = { + + startFarGrabNonColliding: function() { + this.selectColor(); + }, + + clickReleaseOnEntity: function() { + this.selectColor(); + }, + + selectColor: function() { + setEntityCustomData(this.colorKey, this.whiteboard, {currentColor: this.color}); + Entities.callEntityMethod(this.whiteboard, "changeColor"); + }, + + preload: function(entityID) { + this.entityID = entityID; + var props = Entities.getEntityProperties(this.entityID, ["position, color, userData"]); + this.position = props.position; + this.color = props.color; + this.colorKey = "color"; + this.whiteboard = JSON.parse(props.userData).whiteboard; + }, + + }; + + // entity scripts always need to return a newly constructed object of our type + return new ColorSelector(); +}); \ No newline at end of file diff --git a/examples/painting/whiteboard/eraseBoardEntityScript.js b/examples/painting/whiteboard/eraseBoardEntityScript.js new file mode 100644 index 0000000000..14679c625b --- /dev/null +++ b/examples/painting/whiteboard/eraseBoardEntityScript.js @@ -0,0 +1,45 @@ +// +// eraseBoardEntityScript.js +// examples/painting/whiteboard +// +// Created by Eric Levin on 9/21/15. +// 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 +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ +/*global BoardEraser */ + +(function() { + + var _this; + BoardEraser = function() { + _this = this; + }; + + BoardEraser.prototype = { + + startFarGrabNonColliding: function() { + this.eraseBoard(); + }, + + clickReleaseOnEntity: function() { + this.eraseBoard(); + }, + + eraseBoard: function() { + Entities.callEntityMethod(this.whiteboard, "eraseBoard"); + }, + + preload: function(entityID) { + this.entityID = entityID; + var props = Entities.getEntityProperties(this.entityID, ["userData"]); + this.whiteboard = JSON.parse(props.userData).whiteboard; + }, + + }; + + // entity scripts always need to return a newly constructed object of our type + return new BoardEraser(); +}); \ No newline at end of file diff --git a/examples/painting/whiteboard/whiteboardEntityScript.js b/examples/painting/whiteboard/whiteboardEntityScript.js new file mode 100644 index 0000000000..f38073f389 --- /dev/null +++ b/examples/painting/whiteboard/whiteboardEntityScript.js @@ -0,0 +1,250 @@ +// +// whiteBoardEntityScript.js +// examples/painting/whiteboard +// +// Created by Eric Levin on 10/12/15. +// 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 + +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ + +/*global Whiteboard */ + + + +(function() { + Script.include("../../libraries/utils.js"); + var _this; + var RIGHT_HAND = 1; + var LEFT_HAND = 0; + var MIN_POINT_DISTANCE = 0.01 ; + var MAX_POINT_DISTANCE = 0.5; + var MAX_POINTS_PER_LINE = 40; + var MAX_DISTANCE = 5; + + var PAINT_TRIGGER_THRESHOLD = 0.6; + var MIN_STROKE_WIDTH = 0.0005; + var MAX_STROKE_WIDTH = 0.03; + + Whiteboard = function() { + _this = this; + }; + + Whiteboard.prototype = { + + setRightHand: function() { + this.hand = RIGHT_HAND; + }, + + setLeftHand: function() { + this.hand = LEFT_HAND; + }, + + startFarGrabNonColliding: function() { + if (this.painting) { + return; + } + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + this.triggerAction = Controller.findAction("RIGHT_HAND_CLICK"); + } else if (this.hand === LEFT_HAND) { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + this.triggerAction = Controller.findAction("LEFT_HAND_CLICK"); + } + Overlays.editOverlay(this.laserPointer, { + visible: true + }); + }, + + continueFarGrabbingNonColliding: function() { + var handPosition = this.getHandPosition(); + var pickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()) + }; + + this.intersection = Entities.findRayIntersection(pickRay, true, this.whitelist); + //Comment out above line and uncomment below line to see difference in performance between using a whitelist, and not using one + // this.intersection = Entities.findRayIntersection(pickRay, true); + + if (this.intersection.intersects) { + var distance = Vec3.distance(handPosition, this.intersection.intersection); + if (distance < MAX_DISTANCE) { + this.triggerValue = Controller.getActionValue(this.triggerAction); + this.currentStrokeWidth = map(this.triggerValue, 0, 1, MIN_STROKE_WIDTH, MAX_STROKE_WIDTH); + var displayPoint = this.intersection.intersection; + displayPoint = Vec3.sum(displayPoint, Vec3.multiply(this.normal, 0.01)); + Overlays.editOverlay(this.laserPointer, { + position: displayPoint, + size: { + x: this.currentStrokeWidth, + y: this.currentStrokeWidth + } + }); + if (this.triggerValue > PAINT_TRIGGER_THRESHOLD) { + this.paint(this.intersection.intersection, this.intersection.surfaceNormal); + } else { + this.painting = false; + this.oldPosition = null; + } + } + } else if (this.intersection.properties.type !== "Unknown") { + //Sometimes ray will pick against an invisible object with type unkown... so if type is unknown, ignore + this.stopPainting(); + } + }, + + stopPainting: function() { + this.painting = false; + Overlays.editOverlay(this.laserPointer, { + visible: false + }); + this.oldPosition = null; + }, + + paint: function(position, normal) { + if (this.painting === false) { + if (this.oldPosition) { + this.newStroke(this.oldPosition); + } else { + this.newStroke(position); + } + this.painting = true; + } + + + var localPoint = Vec3.subtract(position, this.strokeBasePosition); + //Move stroke a bit forward along normal each point so it doesnt zfight with mesh its drawing on, or previous part of stroke(s) + localPoint = Vec3.sum(localPoint, Vec3.multiply(this.normal, this.forwardOffset)); + this.forwardOffset += 0.00001; + var distance = Vec3.distance(localPoint, this.strokePoints[this.strokePoints.length - 1]); + if (this.strokePoints.length > 0 && distance < MIN_POINT_DISTANCE) { + //need a minimum distance to avoid binormal NANs + + return; + } + if (this.strokePoints.length > 0 && distance > MAX_POINT_DISTANCE) { + //Prevents drawing lines accross models + this.painting = false; + return; + } + if (this.strokePoints.length === 0) { + localPoint = { + x: 0, + y: 0, + z: 0 + }; + } + + this.strokePoints.push(localPoint); + this.strokeNormals.push(this.normal); + this.strokeWidths.push(this.currentStrokeWidth); + Entities.editEntity(this.currentStroke, { + linePoints: this.strokePoints, + normals: this.strokeNormals, + strokeWidths: this.strokeWidths + }); + if (this.strokePoints.length === MAX_POINTS_PER_LINE) { + this.painting = false; + return; + } + this.oldPosition = position; + }, + + + newStroke: function(position) { + this.strokeBasePosition = position; + this.currentStroke = Entities.addEntity({ + position: position, + type: "PolyLine", + name: "paintStroke", + color: this.strokeColor, + dimensions: { + x: 50, + y: 50, + z: 50 + }, + lifetime: 200, + userData: JSON.stringify({ + whiteboard: this.entityID + }) + }); + this.strokePoints = []; + this.strokeNormals = []; + this.strokeWidths = []; + + this.strokes.push(this.currentStroke); + + }, + + releaseGrab: function() { + this.stopPainting(); + + }, + + changeColor: function() { + var userData = JSON.parse(Entities.getEntityProperties(this.entityID, ["userData"]).userData); + this.strokeColor = userData.color.currentColor; + this.colorIndicator = userData.colorIndicator; + Overlays.editOverlay(this.laserPointer, { + color: this.strokeColor + }); + + + Entities.callEntityMethod(this.colorIndicator, "changeColor"); + }, + + eraseBoard: function() { + var distance = Math.max(this.dimensions.x, this.dimensions.y); + var entities = Entities.findEntities(this.position, distance); + entities.forEach(function(entity) { + var props = Entities.getEntityProperties(entity, ["name, userData"]); + var name = props.name; + if(!props.userData) { + return; + } + var whiteboardID = JSON.parse(props.userData).whiteboard; + if (name === "paintStroke" && JSON.stringify(whiteboardID) === JSON.stringify(_this.entityID)) { + // This entity is a paintstroke and part of this whiteboard so delete it + Entities.deleteEntity(entity); + } + }); + }, + + preload: function(entityID) { + this.entityID = entityID; + var props = Entities.getEntityProperties(this.entityID, ["position", "rotation", "userData", "dimensions"]); + this.position = props.position; + this.rotation = props.rotation; + this.dimensions = props.dimensions; + this.normal = Vec3.multiply(Quat.getFront(this.rotation), -1); + this.painting = false; + this.strokes = []; + this.whitelist = [this.entityID]; + this.laserPointer = Overlays.addOverlay("circle3d", { + color: this.strokeColor, + solid: true, + rotation: this.rotation + }); + this.forwardOffset = 0.0005; + + this.changeColor(); + }, + + unload: function() { + + Overlays.deleteOverlay(this.laserPointer); + // this.eraseBoard(); + } + + }; + + + // entity scripts always need to return a newly constructed object of our type + return new Whiteboard(); +}); \ No newline at end of file diff --git a/examples/painting/whiteboard/whiteboardSpawner.js b/examples/painting/whiteboard/whiteboardSpawner.js new file mode 100644 index 0000000000..cbc26da670 --- /dev/null +++ b/examples/painting/whiteboard/whiteboardSpawner.js @@ -0,0 +1,201 @@ +// +// whiteBoardSpawner.js +// examples/painting/whiteboard +// +// Created by Eric Levina on 10/12/15. +// Copyright 2015 High Fidelity, Inc. +// +// Run this script to spawn a whiteboard that one can paint on +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ +// Script specific +/*global hexToRgb */ + +Script.include("../../libraries/utils.js"); +var scriptURL = Script.resolvePath("whiteboardEntityScript.js"); +var rotation = Quat.safeEulerAngles(Camera.getOrientation()); +rotation = Quat.fromPitchYawRollDegrees(0, rotation.y, 0); +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(rotation))); +center.y += 0.4; + +var colors = [ + hexToRgb("#2F8E84"), + hexToRgb("#66CCB3"), + hexToRgb("#A43C37"), + hexToRgb("#491849"), + hexToRgb("#6AB03B"), + hexToRgb("#993369"), + hexToRgb("#9B47C2") +]; + +//WHITEBOARD +var whiteboardDimensions = { + x: 2, + y: 1.5, + z: 0.08 +}; +var whiteboard = Entities.addEntity({ + type: "Model", + modelURL: "https://hifi-public.s3.amazonaws.com/ozan/support/for_eric/whiteboard/whiteboard.fbx", + name: "whiteboard", + position: center, + rotation: rotation, + script: scriptURL, + dimensions: whiteboardDimensions, + color: { + red: 255, + green: 255, + blue: 255 + } +}); + + +// COLOR INDICATOR BOX +var colorIndicatorDimensions = { + x: whiteboardDimensions.x, + y: 0.05, + z: 0.02 +}; +scriptURL = Script.resolvePath("colorIndicatorEntityScript.js"); +var colorIndicatorPosition = Vec3.sum(center, { + x: 0, + y: whiteboardDimensions.y / 2 + colorIndicatorDimensions.y / 2, + z: 0 +}); +var colorIndicatorBox = Entities.addEntity({ + type: "Box", + name: "Color Indicator", + color: colors[0], + rotation: rotation, + position: colorIndicatorPosition, + dimensions: colorIndicatorDimensions, + script: scriptURL, + userData: JSON.stringify({ + whiteboard: whiteboard + }) +}); + +Entities.editEntity(whiteboard, { + userData: JSON.stringify({ + color: { + currentColor: colors[0] + }, + colorIndicator: colorIndicatorBox + }) +}); + +//COLOR BOXES +var direction = Quat.getRight(rotation); +var colorBoxPosition = Vec3.subtract(center, Vec3.multiply(direction, whiteboardDimensions.x / 2)); +var colorBoxes = []; +var colorSquareDimensions = { + x: (whiteboardDimensions.x / 2) / (colors.length - 1), + y: 0.1, + z: 0.05 +}; +colorBoxPosition.y += whiteboardDimensions.y / 2 + colorIndicatorDimensions.y + colorSquareDimensions.y / 2; +var spaceBetweenColorBoxes = Vec3.multiply(direction, colorSquareDimensions.x * 2); +var scriptURL = Script.resolvePath("colorSelectorEntityScript.js"); +for (var i = 0; i < colors.length; i++) { + var colorBox = Entities.addEntity({ + type: "Box", + name: "Color Selector", + position: colorBoxPosition, + dimensions: colorSquareDimensions, + rotation: rotation, + color: colors[i], + script: scriptURL, + userData: JSON.stringify({ + whiteboard: whiteboard, + colorIndicator: colorIndicatorBox + }) + }); + colorBoxes.push(colorBox); + colorBoxPosition = Vec3.sum(colorBoxPosition, spaceBetweenColorBoxes); +} + + +// BLACK BOX +var blackBoxDimensions = { + x: 0.3, + y: 0.3, + z: 0.01 +}; + +colorBoxPosition = Vec3.subtract(center, Vec3.multiply(direction, whiteboardDimensions.x / 2 + blackBoxDimensions.x / 2 - 0.01)); +colorBoxPosition.y += 0.3; +var fragShaderURL = Script.resolvePath('blackInk.fs?v1' + Math.random()); +var blackBox = Entities.addEntity({ + type: 'Box', + name: "Black Color", + position: colorBoxPosition, + dimensions: blackBoxDimensions, + rotation: rotation, + color: { + red: 0, + green: 0, + blue: 0 + }, + script: scriptURL, + userData: JSON.stringify({ + whiteboard: whiteboard, + version: 2, + ProceduralEntity: { + shaderUrl: fragShaderURL + } + }) +}); + + +var eraseBoxDimensions = { + x: 0.5, + y: 0.1, + z: 0.01 +}; + + +var eraseBoxPosition = Vec3.sum(center, Vec3.multiply(direction, whiteboardDimensions.x / 2 + eraseBoxDimensions.x / 2 + 0.01)); +eraseBoxPosition.y += 0.3; +scriptURL = Script.resolvePath("eraseBoardEntityScript.js"); +var eraseAllText = Entities.addEntity({ + type: "Text", + position: eraseBoxPosition, + name: "Eraser", + script: scriptURL, + rotation: rotation, + dimensions: eraseBoxDimensions, + backgroundColor: { + red: 0, + green: 60, + blue: 0 + }, + textColor: { + red: 255, + green: 10, + blue: 10 + }, + text: "ERASE BOARD", + lineHeight: 0.07, + userData: JSON.stringify({ + whiteboard: whiteboard + }) +}); + + + +function cleanup() { + Entities.deleteEntity(whiteboard); + Entities.deleteEntity(eraseAllText); + Entities.deleteEntity(blackBox); + Entities.deleteEntity(colorIndicatorBox); + colorBoxes.forEach(function(colorBox) { + Entities.deleteEntity(colorBox); + }); +} + + +// Uncomment this line to delete whiteboard and all associated entity on script close +Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/utilities.js b/examples/utilities.js deleted file mode 100644 index 85e27079a8..0000000000 --- a/examples/utilities.js +++ /dev/null @@ -1,60 +0,0 @@ -// utilities.js -// examples -// -// Created by Eric Levin on June 8 -// Copyright 2015 High Fidelity, Inc. -// -// Common utilities -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - - - -map = function(value, min1, max1, min2, max2) { - return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); -} - -hslToRgb = function(hslColor) { - var h = hslColor.hue; - var s = hslColor.sat; - var l = hslColor.light; - var r, g, b; - - if (s == 0) { - r = g = b = l; // achromatic - } else { - var hue2rgb = function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - } - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - - return { - red: Math.round(r * 255), - green: Math.round(g * 255), - blue: Math.round(b * 255) - }; - -} - - - -randFloat = function(low, high) { - return low + Math.random() * (high - low); -} - - -randInt = function(low, high) { - return Math.floor(randFloat(low, high)); -} \ No newline at end of file diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index fb9ab6563b..8f316af276 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -482,7 +482,7 @@ void EntityTreeRenderer::deleteReleasedModels() { } RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, - bool precisionPicking) { + bool precisionPicking, const QVector& entityIdsToInclude) { RayToEntityIntersectionResult result; if (_tree) { EntityTreePointer entityTree = std::static_pointer_cast(_tree); @@ -490,7 +490,7 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons OctreeElementPointer element; EntityItemPointer intersectedEntity = NULL; result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, - result.face, result.surfaceNormal, + result.face, result.surfaceNormal, entityIdsToInclude, (void**)&intersectedEntity, lockType, &result.accurate, precisionPicking); if (result.intersects && intersectedEntity) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 08ff1ac296..15c030f77e 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -130,7 +130,7 @@ private: QList _releasedModels; RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, - bool precisionPicking); + bool precisionPicking, const QVector& entityIdsToInclude = QVector()); EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 0dd0129a1e..3a530c0f3a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -279,17 +279,19 @@ QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn return result; } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking) { - return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) { + QVector entities = qVectorQUuidFromScriptValue(entityIdsToInclude); + return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking, entities); } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking) { - return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) { + const QVector& entities = qVectorQUuidFromScriptValue(entityIdsToInclude); + return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entities); } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, - bool precisionPicking) { + bool precisionPicking, const QVector& entityIdsToInclude) { RayToEntityIntersectionResult result; @@ -297,7 +299,7 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke OctreeElementPointer element; EntityItemPointer intersectedEntity = NULL; result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, - result.surfaceNormal, (void**)&intersectedEntity, lockType, &result.accurate, + result.surfaceNormal, entityIdsToInclude, (void**)&intersectedEntity, lockType, &result.accurate, precisionPicking); if (result.intersects && intersectedEntity) { result.entityID = intersectedEntity->getEntityItemID(); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index cf10bdb997..48f13e81bd 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -111,11 +111,11 @@ public slots: /// If the scripting context has visible entities, this will determine a ray intersection, the results /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate /// will be false. - Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking = false); + Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue()); /// If the scripting context has visible entities, this will determine a ray intersection, and will block in /// order to return an accurate result - Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false); + Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue()); Q_INVOKABLE void setLightsArePickable(bool value); Q_INVOKABLE bool getLightsArePickable() const; @@ -186,7 +186,7 @@ private: /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, - bool precisionPicking); + bool precisionPicking, const QVector& entityIdsToInclude); EntityTreePointer _entityTree; EntitiesScriptEngineProvider* _entitiesScriptEngine = nullptr; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 48c30caf20..56ab27836c 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -495,12 +495,16 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking, float distanceToElementCube) { + const QVector& entityIdsToInclude, void** intersectedObject, bool precisionPicking, float distanceToElementCube) { // only called if we do intersect our bounding cube, but find if we actually intersect with entities... int entityNumber = 0; bool somethingIntersected = false; forEachEntity([&](EntityItemPointer entity) { + if (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) { + return; + } + AABox entityBox = entity->getAABox(); float localDistance; BoxFace localFace; diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index c26b5417ed..0a47542e65 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -144,7 +144,7 @@ public: virtual bool canRayIntersect() const { return hasEntities(); } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, void** intersectedObject, bool precisionPicking, float distanceToElementCube); virtual bool findSpherePenetration(const glm::vec3& center, float radius, diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 758a47bc61..3b6467401c 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -703,6 +703,7 @@ public: float& distance; BoxFace& face; glm::vec3& surfaceNormal; + const QVector& entityIdsToInclude; void** intersectedObject; bool found; bool precisionPicking; @@ -712,7 +713,7 @@ bool findRayIntersectionOp(OctreeElementPointer element, void* extraData) { RayArgs* args = static_cast(extraData); bool keepSearching = true; if (element->findRayIntersection(args->origin, args->direction, keepSearching, - args->element, args->distance, args->face, args->surfaceNormal, + args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude, args->intersectedObject, args->precisionPicking)) { args->found = true; } @@ -721,9 +722,9 @@ bool findRayIntersectionOp(OctreeElementPointer element, void* extraData) { bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, void** intersectedObject, Octree::lockType lockType, bool* accurateResult, bool precisionPicking) { - RayArgs args = { origin, direction, element, distance, face, surfaceNormal, intersectedObject, false, precisionPicking}; + RayArgs args = { origin, direction, element, distance, face, surfaceNormal, entityIdsToInclude, intersectedObject, false, precisionPicking}; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index dececf1456..f0a4d2c9c0 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -300,6 +300,7 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& node, float& distance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude = QVector(), void** intersectedObject = NULL, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL, diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 25758c29d9..9a48079338 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -575,7 +575,7 @@ void OctreeElement::notifyUpdateHooks() { bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, void** intersectedObject, bool precisionPicking) { keepSearching = true; // assume that we will continue searching after this. @@ -601,7 +601,7 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 if (_cube.contains(origin) || distanceToElementCube < distance) { if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails, - face, localSurfaceNormal, intersectedObject, precisionPicking, distanceToElementCube)) { + face, localSurfaceNormal, entityIdsToInclude, intersectedObject, precisionPicking, distanceToElementCube)) { if (distanceToElementDetails < distance) { distance = distanceToElementDetails; @@ -616,7 +616,7 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 bool OctreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, void** intersectedObject, bool precisionPicking, float distanceToElementCube) { // we did hit this element, so calculate appropriate distances diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index fcdc9985e3..c132d437cd 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -120,12 +120,12 @@ public: virtual bool canRayIntersect() const { return isLeaf(); } virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& node, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, void** intersectedObject = NULL, bool precisionPicking = false); virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, void** intersectedObject, bool precisionPicking, float distanceToElementCube); /// \param center center of sphere in meters diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index b2389f4db6..9ab0eaecb4 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -104,6 +104,21 @@ QVector qVectorFloatFromScriptValue(const QScriptValue& array) { return newVector; } +QVector qVectorQUuidFromScriptValue(const QScriptValue& array) { + if (!array.isArray()) { + return QVector(); + } + QVector newVector; + int length = array.property("length").toInteger(); + newVector.reserve(length); + for (int i = 0; i < length; i++) { + QString uuidAsString = array.property(i).toString(); + QUuid fromString(uuidAsString); + newVector << fromString; + } + return newVector; +} + QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector& vector) { QScriptValue array = engine->newArray(); for (int i = 0; i < vector.size(); i++) { diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index c419741c3b..cd1e3b0d3e 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -65,6 +65,8 @@ QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector& vector); QVector qVectorFloatFromScriptValue(const QScriptValue& array); +QVector qVectorQUuidFromScriptValue(const QScriptValue& array); + class PickRay { public: PickRay() : origin(0.0f), direction(0.0f) { }