Merge remote-tracking branch 'upstream/master' into HEAD

This commit is contained in:
Brad Davis 2015-10-21 10:05:32 -07:00
commit 21a3079a39
20 changed files with 300 additions and 211 deletions

View file

@ -19,7 +19,7 @@ Script.include("../libraries/utils.js");
// //
// add lines where the hand ray picking is happening // add lines where the hand ray picking is happening
// //
var DEBUG_HAND_RAY_PICKING = false; var WANT_DEBUG = false;
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// //
@ -49,6 +49,7 @@ var PICK_MAX_DISTANCE = 500; // max length of pick-ray
// near grabbing // near grabbing
// //
var GRAB_RADIUS = 0.3; // if the ray misses but an object is this close, it will still be selected
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable.
var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected
@ -193,11 +194,12 @@ function MyController(hand, triggerAction) {
}; };
this.setState = function(newState) { this.setState = function(newState) {
// print("STATE: " + this.state + " --> " + newState); if (WANT_DEBUG) {
print("STATE: " + this.state + " --> " + newState);
}
this.state = newState; this.state = newState;
} }
this.debugLine = function(closePoint, farPoint, color){ this.debugLine = function(closePoint, farPoint, color){
Entities.addEntity({ Entities.addEntity({
type: "Line", type: "Line",
@ -226,14 +228,13 @@ function MyController(hand, triggerAction) {
}); });
} else { } else {
var age = Entities.getEntityProperties(this.pointer, "age").age; var age = Entities.getEntityProperties(this.pointer, "age").age;
Entities.editEntity(this.pointer, { this.pointer = Entities.editEntity(this.pointer, {
position: closePoint, position: closePoint,
linePoints: [ZERO_VEC, farPoint], linePoints: [ZERO_VEC, farPoint],
color: color, color: color,
lifetime: age + LIFETIME lifetime: age + LIFETIME
}); });
} }
}; };
this.lineOff = function() { this.lineOff = function() {
@ -282,7 +283,6 @@ function MyController(hand, triggerAction) {
return; return;
} }
// the trigger is being pressed, do a ray test // the trigger is being pressed, do a ray test
var handPosition = this.getHandPosition(); var handPosition = this.getHandPosition();
var distantPickRay = { var distantPickRay = {
@ -290,29 +290,17 @@ function MyController(hand, triggerAction) {
direction: Quat.getUp(this.getHandRotation()), direction: Quat.getUp(this.getHandRotation()),
length: PICK_MAX_DISTANCE length: PICK_MAX_DISTANCE
}; };
var palmPickRay = {
origin: handPosition,
direction: Quat.getFront(this.getHandRotation()),
length: NEAR_PICK_MAX_DISTANCE
};
var otherPickRay = {
origin: handPosition,
direction: Quat.getRight(this.getHandRotation()),
length: NEAR_PICK_MAX_DISTANCE
};
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
// don't pick 60x per second. do this check after updating the line so it's not jumpy. // don't pick 60x per second.
var pickRays = [];
var now = Date.now(); var now = Date.now();
if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) {
return; pickRays = [distantPickRay];
}
this.lastPickTime = now; this.lastPickTime = now;
}
var pickRays = [distantPickRay, palmPickRay, otherPickRay];
for (var index=0; index < pickRays.length; ++index) { for (var index=0; index < pickRays.length; ++index) {
var pickRay = pickRays[index]; var pickRay = pickRays[index];
var directionNormalized = Vec3.normalize(pickRay.direction); var directionNormalized = Vec3.normalize(pickRay.direction);
@ -322,12 +310,13 @@ function MyController(hand, triggerAction) {
direction: pickRay.direction direction: pickRay.direction
}; };
if (DEBUG_HAND_RAY_PICKING) if (WANT_DEBUG) {
this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), { this.debugLine(pickRayBacked.origin, Vec3.multiply(pickRayBacked.direction, NEAR_PICK_MAX_DISTANCE), {
red: 0, red: 0,
green: 255, green: 255,
blue: 0 blue: 0
}) })
}
var intersection = Entities.findRayIntersection(pickRayBacked, true); var intersection = Entities.findRayIntersection(pickRayBacked, true);
@ -336,7 +325,6 @@ function MyController(hand, triggerAction) {
var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection);
this.grabbedEntity = intersection.entityID; this.grabbedEntity = intersection.entityID;
//this code will disabled the beam for the opposite hand of the one that grabbed it if the entity says so //this code will disabled the beam for the opposite hand of the one that grabbed it if the entity says so
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA);
if (grabbableData["turnOffOppositeBeam"] === true) { if (grabbableData["turnOffOppositeBeam"] === true) {
@ -345,7 +333,6 @@ function MyController(hand, triggerAction) {
} else { } else {
disabledHand = RIGHT_HAND; disabledHand = RIGHT_HAND;
} }
} else { } else {
disabledHand = 'none'; disabledHand = 'none';
} }
@ -380,6 +367,37 @@ function MyController(hand, triggerAction) {
} }
} }
} }
if (this.grabbedEntity === null) {
// forward ray test failed, try sphere test.
var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
var minDistance = PICK_MAX_DISTANCE;
var i, props, distance, grabbableData;
for (i = 0; i < nearbyEntities.length; i++) {
var grabbableDataForCandidate =
getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA);
if (grabbableDataForCandidate.grabbable === false) {
continue;
}
var propsForCandidate =
Entities.getEntityProperties(nearbyEntities[i], ["position", "name", "collisionsWillMove", "locked"]);
distance = Vec3.distance(propsForCandidate.position, handPosition);
if (distance < minDistance && propsForCandidate.name !== "pointer") {
this.grabbedEntity = nearbyEntities[i];
minDistance = distance;
props = propsForCandidate;
grabbableData = grabbableDataForCandidate;
}
}
if (this.grabbedEntity === null) {
return;
} else if (props.locked === 0 && props.collisionsWillMove === 1) {
this.setState(STATE_NEAR_GRABBING);
} else if (props.collisionsWillMove === 0 && grabbableData.wantsTrigger) {
// We have grabbed a non-physical object, so we want to trigger a non-colliding event as opposed to a grab event
this.setState(STATE_NEAR_GRABBING_NON_COLLIDING);
}
}
}; };
this.distanceHolding = function() { this.distanceHolding = function() {
@ -441,7 +459,8 @@ function MyController(hand, triggerAction) {
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
// the action was set up on a previous call. update the targets. // the action was set up on a previous call. update the targets.
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR); var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) *
DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR);
// how far did avatar move this timestep? // how far did avatar move this timestep?
var currentPosition = MyAvatar.position; var currentPosition = MyAvatar.position;
var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition);
@ -491,7 +510,10 @@ function MyController(hand, triggerAction) {
this.currentObjectTime = now; this.currentObjectTime = now;
// this doubles hand rotation // this doubles hand rotation
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, handRotation, DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), Quat.inverse(this.handPreviousRotation)); var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation,
handRotation,
DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
Quat.inverse(this.handPreviousRotation));
this.handPreviousRotation = handRotation; this.handPreviousRotation = handRotation;
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
@ -526,7 +548,8 @@ function MyController(hand, triggerAction) {
this.lineOff(); this.lineOff();
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation", "gravity", "ignoreForCollisions"]); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity,
["position", "rotation", "gravity", "ignoreForCollisions"]);
this.activateEntity(this.grabbedEntity, grabbedProperties); this.activateEntity(this.grabbedEntity, grabbedProperties);
var handRotation = this.getHandRotation(); var handRotation = this.getHandRotation();
@ -764,9 +787,9 @@ function MyController(hand, triggerAction) {
this.release = function() { this.release = function() {
if(this.hand!==disabledHand){ if(this.hand !== disabledHand){
//release the disabled hand when we let go with the main one //release the disabled hand when we let go with the main one
disabledHand='none'; disabledHand = 'none';
} }
this.lineOff(); this.lineOff();

View file

@ -36,6 +36,10 @@ var hoop = Entities.addEntity({
y: 3.99, y: 3.99,
z: 3.79 z: 3.79
}, },
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
})
compoundShapeURL: hoopCollisionHullURL compoundShapeURL: hoopCollisionHullURL
}); });

View file

@ -22,8 +22,6 @@ var DIAMETER = 0.30;
var RESET_DISTANCE = 1; var RESET_DISTANCE = 1;
var MINIMUM_MOVE_LENGTH = 0.05; var MINIMUM_MOVE_LENGTH = 0.05;
var GRABBABLE_DATA_KEY = "grabbableKey";
var rackStartPosition = var rackStartPosition =
Vec3.sum(MyAvatar.position, Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation, { Vec3.multiplyQbyV(MyAvatar.orientation, {
@ -53,19 +51,17 @@ var rack = Entities.addEntity({
ignoreForCollisions: false, ignoreForCollisions: false,
collisionSoundURL: collisionSoundURL, collisionSoundURL: collisionSoundURL,
compoundShapeURL: rackCollisionHullURL, compoundShapeURL: rackCollisionHullURL,
// scriptURL: rackScriptURL userData: JSON.stringify({
}); grabbableKey: {
setEntityCustomData(GRABBABLE_DATA_KEY, rack, {
grabbable: false grabbable: false
}
})
}); });
var nonCollidingBalls = []; var balls = [];
var collidingBalls = [];
var originalBallPositions = []; var originalBallPositions = [];
function createCollidingBalls() { function createBalls() {
var position = rackStartPosition; var position = rackStartPosition;
var i; var i;
@ -76,9 +72,9 @@ function createCollidingBalls() {
z: position.z + (DIAMETER) - (DIAMETER * i) z: position.z + (DIAMETER) - (DIAMETER * i)
}; };
var collidingBall = Entities.addEntity({ var ball = Entities.addEntity({
type: "Model", type: "Model",
name: 'Colliding Basketball', name: 'Hifi-Basketball',
shapeType: 'Sphere', shapeType: 'Sphere',
position: ballPosition, position: ballPosition,
dimensions: { dimensions: {
@ -96,16 +92,21 @@ function createCollidingBalls() {
collisionsWillMove: true, collisionsWillMove: true,
ignoreForCollisions: false, ignoreForCollisions: false,
modelURL: basketballURL, modelURL: basketballURL,
userData: JSON.stringify({
grabbableKey: {
invertSolidWhileHeld: true
}
})
}); });
collidingBalls.push(collidingBall); balls.push(ball);
originalBallPositions.push(position); originalBallPositions.push(position);
} }
} }
function testBallDistanceFromStart() { function testBallDistanceFromStart() {
var resetCount = 0; var resetCount = 0;
collidingBalls.forEach(function(ball, index) { balls.forEach(function(ball, index) {
var currentPosition = Entities.getEntityProperties(ball, "position").position; var currentPosition = Entities.getEntityProperties(ball, "position").position;
var originalPosition = originalBallPositions[index]; var originalPosition = originalBallPositions[index];
var distance = Vec3.subtract(originalPosition, currentPosition); var distance = Vec3.subtract(originalPosition, currentPosition);
@ -117,8 +118,8 @@ function testBallDistanceFromStart() {
if (moving < MINIMUM_MOVE_LENGTH) { if (moving < MINIMUM_MOVE_LENGTH) {
resetCount++; resetCount++;
if (resetCount === NUMBER_OF_BALLS) { if (resetCount === NUMBER_OF_BALLS) {
deleteCollidingBalls(); deleteBalls();
createCollidingBalls(); createBalls();
} }
} }
}, 200) }, 200)
@ -128,19 +129,19 @@ function testBallDistanceFromStart() {
function deleteEntity(entityID) { function deleteEntity(entityID) {
if (entityID === rack) { if (entityID === rack) {
deleteCollidingBalls(); deleteBalls();
Script.clearInterval(distanceCheckInterval); Script.clearInterval(distanceCheckInterval);
Entities.deletingEntity.disconnect(deleteEntity); Entities.deletingEntity.disconnect(deleteEntity);
} }
} }
function deleteCollidingBalls() { function deleteBalls() {
while (collidingBalls.length > 0) { while (balls.length > 0) {
Entities.deleteEntity(collidingBalls.pop()); Entities.deleteEntity(balls.pop());
} }
} }
createCollidingBalls(); createBalls();
Entities.deletingEntity.connect(deleteEntity); Entities.deletingEntity.connect(deleteEntity);
var distanceCheckInterval = Script.setInterval(testBallDistanceFromStart, 1000); var distanceCheckInterval = Script.setInterval(testBallDistanceFromStart, 1000);

View file

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

View file

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

View file

@ -280,18 +280,18 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn
} }
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) { RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) {
QVector<QUuid> entities = qVectorQUuidFromScriptValue(entityIdsToInclude); QVector<EntityItemID> entities = qVectorEntityItemIDFromScriptValue(entityIdsToInclude);
return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking, entities); return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking, entities);
} }
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) { RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) {
const QVector<QUuid>& entities = qVectorQUuidFromScriptValue(entityIdsToInclude); const QVector<EntityItemID>& entities = qVectorEntityItemIDFromScriptValue(entityIdsToInclude);
return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entities); return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entities);
} }
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray,
Octree::lockType lockType, Octree::lockType lockType,
bool precisionPicking, const QVector<QUuid>& entityIdsToInclude) { bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude) {
RayToEntityIntersectionResult result; RayToEntityIntersectionResult result;

View file

@ -186,7 +186,7 @@ private:
/// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode /// 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, RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
bool precisionPicking, const QVector<QUuid>& entityIdsToInclude); bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude);
EntityTreePointer _entityTree; EntityTreePointer _entityTree;
EntitiesScriptEngineProvider* _entitiesScriptEngine = nullptr; EntitiesScriptEngineProvider* _entitiesScriptEngine = nullptr;

View file

@ -448,6 +448,53 @@ bool EntityTree::findNearPointOperation(OctreeElementPointer element, void* extr
// if this element doesn't contain the point, then none of its children can contain the point, so stop searching // if this element doesn't contain the point, then none of its children can contain the point, so stop searching
return false; return false;
} }
// combines the ray cast arguments into a single object
class RayArgs {
public:
glm::vec3 origin;
glm::vec3 direction;
OctreeElementPointer& element;
float& distance;
BoxFace& face;
glm::vec3& surfaceNormal;
const QVector<EntityItemID>& entityIdsToInclude;
void** intersectedObject;
bool found;
bool precisionPicking;
};
bool findRayIntersectionOp(OctreeElementPointer element, void* extraData) {
RayArgs* args = static_cast<RayArgs*>(extraData);
bool keepSearching = true;
EntityTreeElementPointer entityTreeElementPointer = std::dynamic_pointer_cast<EntityTreeElement>(element);
if (entityTreeElementPointer ->findRayIntersection(args->origin, args->direction, keepSearching,
args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude,
args->intersectedObject, args->precisionPicking)) {
args->found = true;
}
return keepSearching;
}
bool EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, void** intersectedObject,
Octree::lockType lockType, bool* accurateResult, bool precisionPicking) {
RayArgs args = { origin, direction, element, distance, face, surfaceNormal, entityIdsToInclude, intersectedObject, false, precisionPicking };
distance = FLT_MAX;
bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&]{
recurseTreeWithOperation(findRayIntersectionOp, &args);
}, requireLock);
if (accurateResult) {
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
}
return args.found;
}
EntityItemPointer EntityTree::findClosestEntity(glm::vec3 position, float targetRadius) { EntityItemPointer EntityTree::findClosestEntity(glm::vec3 position, float targetRadius) {
FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX };

View file

@ -80,6 +80,14 @@ public:
virtual int processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, virtual int processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength,
const SharedNodePointer& senderNode); const SharedNodePointer& senderNode);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& node, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
const QVector<EntityItemID>& entityIdsToInclude = QVector<EntityItemID>(),
void** intersectedObject = NULL,
Octree::lockType lockType = Octree::TryLock,
bool* accurateResult = NULL,
bool precisionPicking = false);
virtual bool rootElementHasData() const { return true; } virtual bool rootElementHasData() const { return true; }
// the root at least needs to store the number of entities in the packet/buffer // the root at least needs to store the number of entities in the packet/buffer

View file

@ -493,9 +493,50 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
return false; return false;
} }
bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking) {
keepSearching = true; // assume that we will continue searching after this.
float distanceToElementCube = std::numeric_limits<float>::max();
float distanceToElementDetails = distance;
BoxFace localFace;
glm::vec3 localSurfaceNormal;
// if the ray doesn't intersect with our cube, we can stop searching!
if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal)) {
keepSearching = false; // no point in continuing to search
return false; // we did not intersect
}
// by default, we only allow intersections with leaves with content
if (!canRayIntersect()) {
return false; // we don't intersect with non-leaves, and we keep searching
}
// if the distance to the element cube is not less than the current best distance, then it's not possible
// for any details inside the cube to be closer so we don't need to consider them.
if (_cube.contains(origin) || distanceToElementCube < distance) {
if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
face, localSurfaceNormal, entityIdsToInclude, intersectedObject, precisionPicking, distanceToElementCube)) {
if (distanceToElementDetails < distance) {
distance = distanceToElementDetails;
face = localFace;
surfaceNormal = localSurfaceNormal;
return true;
}
}
}
return false;
}
bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching,
OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
const QVector<QUuid>& entityIdsToInclude, void** intersectedObject, bool precisionPicking, float distanceToElementCube) { const QVector<EntityItemID>& entityIdsToInclude, void** intersectedObject, bool precisionPicking, float distanceToElementCube) {
// only called if we do intersect our bounding cube, but find if we actually intersect with entities... // only called if we do intersect our bounding cube, but find if we actually intersect with entities...
int entityNumber = 0; int entityNumber = 0;
@ -609,25 +650,83 @@ EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const
void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector<EntityItemPointer>& foundEntities) const { void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector<EntityItemPointer>& foundEntities) const {
float compareRadius = searchRadius * searchRadius; float compareRadius = searchRadius * searchRadius;
forEachEntity([&](EntityItemPointer entity) { forEachEntity([&](EntityItemPointer entity) {
// For iteration like this, avoid the use of square roots by comparing distances squared
float distanceSquared = glm::length2(entity->getPosition() - searchPosition); AABox entityBox = entity->getAABox();
float otherRadius = entity->getRadius();
if (distanceSquared < (compareRadius + (otherRadius * otherRadius))) { // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case
glm::vec3 penetration;
if (entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) {
// FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better
// FIXME - consider allowing the entity to determine penetration so that
// entities could presumably dull actuall hull testing if they wanted to
// determine the worldToEntityMatrix that doesn't include scale because
// we're going to use the registration aware aa box in the entity frame
glm::mat4 rotation = glm::mat4_cast(entity->getRotation());
glm::mat4 translation = glm::translate(entity->getPosition());
glm::mat4 entityToWorldMatrix = translation * rotation;
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 dimensions = entity->getDimensions();
glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint);
AABox entityFrameBox(corner, dimensions);
glm::vec3 entityFrameSearchPosition = glm::vec3(worldToEntityMatrix * glm::vec4(searchPosition, 1.0f));
if (entityFrameBox.findSpherePenetration(entityFrameSearchPosition, searchRadius, penetration)) {
foundEntities.push_back(entity);
}
}
});
}
void EntityTreeElement::getEntities(const AACube& cube, QVector<EntityItemPointer>& foundEntities) {
forEachEntity([&](EntityItemPointer entity) {
AABox entityBox = entity->getAABox();
// FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better
// FIXME - consider allowing the entity to determine penetration so that
// entities could presumably dull actuall hull testing if they wanted to
// FIXME - is there an easy way to translate the search cube into something in the
// entity frame that can be easily tested against?
// simple algorithm is probably:
// if target box is fully inside search box == yes
// if search box is fully inside target box == yes
// for each face of search box:
// translate the triangles of the face into the box frame
// test the triangles of the face against the box?
// if translated search face triangle intersect target box
// add to result
//
// If the entities AABox touches the search cube then consider it to be found
if (entityBox.touches(cube)) {
foundEntities.push_back(entity); foundEntities.push_back(entity);
} }
}); });
} }
// TODO: change this to use better bounding shape for entity than sphere void EntityTreeElement::getEntities(const AABox& box, QVector<EntityItemPointer>& foundEntities) {
void EntityTreeElement::getEntities(const AACube& box, QVector<EntityItemPointer>& foundEntities) {
AACube entityCube;
forEachEntity([&](EntityItemPointer entity) { forEachEntity([&](EntityItemPointer entity) {
float radius = entity->getRadius(); AABox entityBox = entity->getAABox();
// NOTE: we actually do cube-cube collision queries here, which is sloppy but good enough for now // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better
// TODO: decide whether to replace entityCube-cube query with sphere-cube (requires a square root // FIXME - consider allowing the entity to determine penetration so that
// but will be slightly more accurate). // entities could presumably dull actuall hull testing if they wanted to
entityCube.setBox(entity->getPosition() - glm::vec3(radius), 2.0f * radius); // FIXME - is there an easy way to translate the search cube into something in the
if (entityCube.touches(box)) { // entity frame that can be easily tested against?
// simple algorithm is probably:
// if target box is fully inside search box == yes
// if search box is fully inside target box == yes
// for each face of search box:
// translate the triangles of the face into the box frame
// test the triangles of the face against the box?
// if translated search face triangle intersect target box
// add to result
//
// If the entities AABox touches the search cube then consider it to be found
if (entityBox.touches(box)) {
foundEntities.push_back(entity); foundEntities.push_back(entity);
} }
}); });

View file

@ -142,11 +142,14 @@ public:
virtual bool deleteApproved() const { return !hasEntities(); } virtual bool deleteApproved() const { return !hasEntities(); }
virtual bool canRayIntersect() const { return hasEntities(); } virtual bool canRayIntersect() const { return hasEntities(); }
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& node, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
void** intersectedObject = NULL, bool precisionPicking = false);
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<QUuid>& entityIdsToInclude, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking, float distanceToElementCube); void** intersectedObject, bool precisionPicking, float distanceToElementCube);
virtual bool findSpherePenetration(const glm::vec3& center, float radius, virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const; glm::vec3& penetration, void** penetratedObject) const;
@ -180,7 +183,12 @@ public:
/// finds all entities that touch a box /// finds all entities that touch a box
/// \param box the query box /// \param box the query box
/// \param entities[out] vector of non-const EntityItemPointer /// \param entities[out] vector of non-const EntityItemPointer
void getEntities(const AACube& box, QVector<EntityItemPointer>& foundEntities); void getEntities(const AACube& cube, QVector<EntityItemPointer>& foundEntities);
/// finds all entities that touch a box
/// \param box the query box
/// \param entities[out] vector of non-const EntityItemPointer
void getEntities(const AABox& box, QVector<EntityItemPointer>& foundEntities);
EntityItemPointer getEntityWithID(uint32_t id) const; EntityItemPointer getEntityWithID(uint32_t id) const;
EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const;

View file

@ -123,7 +123,8 @@ bool PolyLineEntityItem::setNormals(const QVector<glm::vec3>& normals) {
_vertices.clear(); _vertices.clear();
glm::vec3 v1, v2, tangent, binormal, point; glm::vec3 v1, v2, tangent, binormal, point;
for (int i = 0; i < minVectorSize - 1; i++) { int finalIndex = minVectorSize -1;
for (int i = 0; i < finalIndex; i++) {
float width = _strokeWidths.at(i); float width = _strokeWidths.at(i);
point = _points.at(i); point = _points.at(i);
@ -138,7 +139,7 @@ bool PolyLineEntityItem::setNormals(const QVector<glm::vec3>& normals) {
_vertices << v1 << v2; _vertices << v1 << v2;
} }
//for last point we can just assume binormals are same since it represents last two vertices of quad //for last point we can just assume binormals are same since it represents last two vertices of quad
point = _points.at(minVectorSize - 1); point = _points.at(finalIndex);
v1 = point + binormal; v1 = point + binormal;
v2 = point - binormal; v2 = point - binormal;
_vertices << v1 << v2; _vertices << v1 << v2;

View file

@ -694,50 +694,7 @@ OctreeElementPointer Octree::getOrCreateChildElementContaining(const AACube& box
return getRoot()->getOrCreateChildElementContaining(box); return getRoot()->getOrCreateChildElementContaining(box);
} }
// combines the ray cast arguments into a single object
class RayArgs {
public:
glm::vec3 origin;
glm::vec3 direction;
OctreeElementPointer& element;
float& distance;
BoxFace& face;
glm::vec3& surfaceNormal;
const QVector<QUuid>& entityIdsToInclude;
void** intersectedObject;
bool found;
bool precisionPicking;
};
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->entityIdsToInclude,
args->intersectedObject, args->precisionPicking)) {
args->found = true;
}
return keepSearching;
}
bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
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, entityIdsToInclude, intersectedObject, false, precisionPicking};
distance = FLT_MAX;
bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&]{
recurseTreeWithOperation(findRayIntersectionOp, &args);
}, requireLock);
if (accurateResult) {
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
}
return args.found;
}
class SphereArgs { class SphereArgs {
public: public:

View file

@ -298,13 +298,6 @@ public:
TryLock TryLock
} lockType; } lockType;
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,
bool precisionPicking = false);
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject = NULL, bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject = NULL,
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);

View file

@ -573,64 +573,6 @@ 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, const QVector<QUuid>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking) {
keepSearching = true; // assume that we will continue searching after this.
float distanceToElementCube = std::numeric_limits<float>::max();
float distanceToElementDetails = distance;
BoxFace localFace;
glm::vec3 localSurfaceNormal;
// if the ray doesn't intersect with our cube, we can stop searching!
if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal)) {
keepSearching = false; // no point in continuing to search
return false; // we did not intersect
}
// by default, we only allow intersections with leaves with content
if (!canRayIntersect()) {
return false; // we don't intersect with non-leaves, and we keep searching
}
// if the distance to the element cube is not less than the current best distance, then it's not possible
// for any details inside the cube to be closer so we don't need to consider them.
if (_cube.contains(origin) || distanceToElementCube < distance) {
if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
face, localSurfaceNormal, entityIdsToInclude, intersectedObject, precisionPicking, distanceToElementCube)) {
if (distanceToElementDetails < distance) {
distance = distanceToElementDetails;
face = localFace;
surfaceNormal = localSurfaceNormal;
return true;
}
}
}
return false;
}
bool OctreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<QUuid>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking, float distanceToElementCube) {
// we did hit this element, so calculate appropriate distances
if (hasContent()) {
element = shared_from_this();
distance = distanceToElementCube;
if (intersectedObject) {
*intersectedObject = this;
}
keepSearching = false;
return true; // we did intersect
}
return false; // we did not intersect
}
bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius, bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const { glm::vec3& penetration, void** penetratedObject) const {

View file

@ -118,16 +118,6 @@ public:
virtual bool deleteApproved() const { return true; } virtual bool deleteApproved() const { return true; }
virtual bool canRayIntersect() const { return isLeaf(); } 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, 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, const QVector<QUuid>& entityIdsToInclude,
void** intersectedObject, bool precisionPicking, float distanceToElementCube);
/// \param center center of sphere in meters /// \param center center of sphere in meters
/// \param radius radius of sphere in meters /// \param radius radius of sphere in meters
/// \param[out] penetration pointing into cube from sphere /// \param[out] penetration pointing into cube from sphere

View file

@ -13,7 +13,6 @@
#include <QUrl> #include <QUrl>
#include <QUuid> #include <QUuid>
#include <QRect> #include <QRect>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include "RegisteredMetaTypes.h" #include "RegisteredMetaTypes.h"
@ -119,6 +118,21 @@ QVector<QUuid> qVectorQUuidFromScriptValue(const QScriptValue& array) {
return newVector; return newVector;
} }
QVector<EntityItemID> qVectorEntityItemIDFromScriptValue(const QScriptValue& array) {
if (!array.isArray()) {
return QVector<EntityItemID>();
}
QVector<EntityItemID> newVector;
int length = array.property("length").toInteger();
newVector.reserve(length);
for (int i = 0; i < length; i++) {
QString uuidAsString = array.property(i).toString();
EntityItemID fromString(uuidAsString);
newVector << fromString;
}
return newVector;
}
QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector<float>& vector) { QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector<float>& vector) {
QScriptValue array = engine->newArray(); QScriptValue array = engine->newArray();
for (int i = 0; i < vector.size(); i++) { for (int i = 0; i < vector.size(); i++) {

View file

@ -14,6 +14,7 @@
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
#include <QtCore/QUuid> #include <QtCore/QUuid>
#include "../../entities/src/EntityItemID.h"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
@ -66,6 +67,7 @@ void qVectorFloatFromScriptValue(const QScriptValue& array, QVector<float>& vect
QVector<float> qVectorFloatFromScriptValue(const QScriptValue& array); QVector<float> qVectorFloatFromScriptValue(const QScriptValue& array);
QVector<QUuid> qVectorQUuidFromScriptValue(const QScriptValue& array); QVector<QUuid> qVectorQUuidFromScriptValue(const QScriptValue& array);
QVector<EntityItemID> qVectorEntityItemIDFromScriptValue(const QScriptValue& array);
class PickRay { class PickRay {
public: public:

View file

@ -321,7 +321,7 @@
resetMe: { resetMe: {
resetMe: true resetMe: true
}, },
grabbable: { grabbableKey: {
invertSolidWhileHeld: true invertSolidWhileHeld: true
} }
}) })

View file

@ -294,7 +294,7 @@ MasterReset = function() {
resetMe: { resetMe: {
resetMe: true resetMe: true
}, },
grabbable: { grabbableKey: {
invertSolidWhileHeld: true invertSolidWhileHeld: true
} }
}) })