Merge pull request #8158 from sethalves/raypick-avatars

Raypick avatars
This commit is contained in:
Brad Hefta-Gaub 2016-07-12 15:27:30 -07:00 committed by GitHub
commit 819cab4aab
9 changed files with 142 additions and 1 deletions

View file

@ -1085,6 +1085,15 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
shapeInfo.setOffset(uniformScale * _skeletonModel->getBoundingCapsuleOffset());
}
void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
ShapeInfo shapeInfo;
computeShapeInfo(shapeInfo);
glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight
start = getPosition() - glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset();
end = getPosition() + glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset();
radius = halfExtents.x;
}
void Avatar::setMotionState(AvatarMotionState* motionState) {
_motionState = motionState;
}

View file

@ -154,6 +154,7 @@ public:
virtual void rebuildCollisionShape();
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
AvatarMotionState* getMotionState() { return _motionState; }

View file

@ -398,3 +398,76 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID)
return findAvatar(sessionID);
}
RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude,
const QScriptValue& avatarIdsToDiscard) {
RayToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(const_cast<AvatarManager*>(this), "findRayIntersection", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(RayToAvatarIntersectionResult, result),
Q_ARG(const PickRay&, ray),
Q_ARG(const QScriptValue&, avatarIdsToInclude),
Q_ARG(const QScriptValue&, avatarIdsToDiscard));
return result;
}
QVector<EntityItemID> avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude);
QVector<EntityItemID> avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard);
glm::vec3 normDirection = glm::normalize(ray.direction);
for (auto avatarData : _avatarHash) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {
continue;
}
float distance;
BoxFace face;
glm::vec3 surfaceNormal;
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
// It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to
// do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code
// intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking
// against the avatar is sort-of right, but you likely wont be able to pick against the arms.
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
// AABox avatarBounds = avatarModel->getRenderableMeshBound();
// if (!avatarBounds.findRayIntersection(ray.origin, normDirection, distance, face, surfaceNormal)) {
// // ray doesn't intersect avatar's bounding-box
// continue;
// }
glm::vec3 start;
glm::vec3 end;
float radius;
avatar->getCapsule(start, end, radius);
bool intersects = findRayCapsuleIntersection(ray.origin, normDirection, start, end, radius, distance);
if (!intersects) {
// ray doesn't intersect avatar's capsule
continue;
}
QString extraInfo;
intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection,
distance, face, surfaceNormal, extraInfo, true);
if (intersects && (!result.intersects || distance < result.distance)) {
result.intersects = true;
result.avatarID = avatar->getID();
result.distance = distance;
}
}
if (result.intersects) {
result.intersection = ray.origin + normDirection * result.distance;
}
return result;
}

View file

@ -70,6 +70,10 @@ public:
void addAvatarToSimulation(Avatar* avatar);
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude = QScriptValue(),
const QScriptValue& avatarIdsToDiscard = QScriptValue());
public slots:
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
void updateAvatarRenderStatus(bool shouldRenderAvatars);

View file

@ -34,6 +34,7 @@
#include <StreamUtils.h>
#include <UUID.h>
#include <shared/JSONHelpers.h>
#include <ShapeInfo.h>
#include "AvatarLogging.h"
@ -1681,3 +1682,25 @@ AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
});
return result;
}
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID);
obj.setProperty("avatarID", avatarIDValue);
obj.setProperty("distance", value.distance);
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
return obj;
}
void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& value) {
value.intersects = object.property("intersects").toVariant().toBool();
QScriptValue avatarIDValue = object.property("avatarID");
quuidFromScriptValue(avatarIDValue, value.avatarID);
value.distance = object.property("distance").toVariant().toFloat();
QScriptValue intersection = object.property("intersection");
if (intersection.isValid()) {
vec3FromScriptValue(intersection, value.intersection);
}
}

View file

@ -495,4 +495,19 @@ public:
void registerAvatarTypes(QScriptEngine* engine);
class RayToAvatarIntersectionResult {
public:
RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {}
bool intersects;
QUuid avatarID;
float distance;
glm::vec3 intersection;
};
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results);
void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results);
#endif // hifi_AvatarData_h

View file

@ -20,6 +20,7 @@
#include <PathUtils.h>
#include <PerfStat.h>
#include <ViewFrustum.h>
#include <GLMHelpers.h>
#include "AbstractViewStateInterface.h"
#include "MeshPartPayload.h"

View file

@ -465,6 +465,7 @@ void ScriptEngine::init() {
qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly);
qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue);
qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue);
qScriptRegisterMetaType(this, RayToAvatarIntersectionResultToScriptValue, RayToAvatarIntersectionResultFromScriptValue);
qScriptRegisterSequenceMetaType<QVector<QUuid>>(this);
qScriptRegisterSequenceMetaType<QVector<EntityItemID>>(this);

View file

@ -1984,10 +1984,24 @@ function MyController(hand) {
var noVelocity = false;
if (this.grabbedEntity !== null) {
// If this looks like the release after adjusting something still held in the other hand, print the position
// and rotation of the held thing to help content creators set the userData.
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {});
if (grabData.refCount > 1) {
grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]);
if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) {
print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" +
'[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y +
', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x +
', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z +
', "w":' + grabbedProperties.localRotation.w + '}]');
}
}
if (this.actionID !== null) {
Entities.deleteAction(this.grabbedEntity, this.actionID);
// sometimes we want things to stay right where they are when we let go.
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {});
var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
if (releaseVelocityData.disableReleaseVelocity === true ||
// this next line allowed both: