Merge pull request #6079 from ericrius1/whiteboard

Entity whitelist for raypicking and whiteboard painting example
This commit is contained in:
Brad Hefta-Gaub 2015-10-15 17:50:35 -07:00
commit 26f82e899e
23 changed files with 676 additions and 83 deletions

View file

@ -584,7 +584,7 @@ function MyController(hand, triggerAction) {
this.setState(STATE_RELEASE);
return;
}
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrabbingNonColliding");
};

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();
});

View file

@ -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();
});

View file

@ -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();
});

View file

@ -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();
});

View file

@ -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);

View file

@ -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));
}

View file

@ -482,7 +482,7 @@ void EntityTreeRenderer::deleteReleasedModels() {
}
RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
bool precisionPicking) {
bool precisionPicking, const QVector<QUuid>& entityIdsToInclude) {
RayToEntityIntersectionResult result;
if (_tree) {
EntityTreePointer entityTree = std::static_pointer_cast<EntityTree>(_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) {

View file

@ -130,7 +130,7 @@ private:
QList<Model*> _releasedModels;
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
bool precisionPicking);
bool precisionPicking, const QVector<QUuid>& entityIdsToInclude = QVector<QUuid>());
EntityItemID _currentHoverOverEntityID;
EntityItemID _currentClickingOnEntityID;

View file

@ -279,17 +279,19 @@ QVector<QUuid> 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<QUuid> 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<QUuid>& 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<QUuid>& 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();

View file

@ -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<QUuid>& entityIdsToInclude);
EntityTreePointer _entityTree;
EntitiesScriptEngineProvider* _entitiesScriptEngine = nullptr;

View file

@ -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<QUuid>& 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;

View file

@ -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<QUuid>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking, float distanceToElementCube);
virtual bool findSpherePenetration(const glm::vec3& center, float radius,

View file

@ -703,6 +703,7 @@ public:
float& distance;
BoxFace& face;
glm::vec3& surfaceNormal;
const QVector<QUuid>& entityIdsToInclude;
void** intersectedObject;
bool found;
bool precisionPicking;
@ -712,7 +713,7 @@ bool findRayIntersectionOp(OctreeElementPointer element, void* extraData) {
RayArgs* args = static_cast<RayArgs*>(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<QUuid>& 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;

View file

@ -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<QUuid>& entityIdsToInclude = QVector<QUuid>(),
void** intersectedObject = NULL,
Octree::lockType lockType = Octree::TryLock,
bool* accurateResult = NULL,

View file

@ -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<QUuid>& 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<QUuid>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking, float distanceToElementCube) {
// we did hit this element, so calculate appropriate distances

View file

@ -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<QUuid>& 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<QUuid>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking, float distanceToElementCube);
/// \param center center of sphere in meters

View file

@ -104,6 +104,21 @@ QVector<float> qVectorFloatFromScriptValue(const QScriptValue& array) {
return newVector;
}
QVector<QUuid> qVectorQUuidFromScriptValue(const QScriptValue& array) {
if (!array.isArray()) {
return QVector<QUuid>();
}
QVector<QUuid> 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<float>& vector) {
QScriptValue array = engine->newArray();
for (int i = 0; i < vector.size(); i++) {

View file

@ -65,6 +65,8 @@ QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector<floa
void qVectorFloatFromScriptValue(const QScriptValue& array, QVector<float>& vector);
QVector<float> qVectorFloatFromScriptValue(const QScriptValue& array);
QVector<QUuid> qVectorQUuidFromScriptValue(const QScriptValue& array);
class PickRay {
public:
PickRay() : origin(0.0f), direction(0.0f) { }