mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-05-30 04:11:22 +02:00
- Divided the option "Avatar leaning behavior" into two options that work more usefully: "Allow my avatar to stand" and "Allow my avatar to lean" (PreferencesDialog.cpp). Made the necessary fixes so that the avatar can be set to stand only when the user is standing (more details below). - The logic controlling the direction of MyAvatar's action motor is now centralised in calculateScaledDirection (was previously split between there and updateMotors). calculateScaledDirection now returns a velocity in world space. - CharacterController::FollowHelper now uses separate follow timers for rotation, horizontal and vertical (previously followed all three based on the longest of their follow times). Where appropriate, FollowHelper can now snap immediately to the desired rotation/horizontal/vertical independently (see FOLLOW_TIME_IMMEDIATE_SNAP). - FollowHelper::FollowType has therefore moved to CharacterController::FollowType. - MyAvatar::FollowHelper::postPhysicsUpdate: If MyAvatar is not allowed to stand when the user is sitting, this now avoids recentring the body based on the head height. - Removed Q_PROPERTY(MyAvatar::SitStandModelType, as the sitting/standing/leaning model uses different enums now (see setAllowAvatarStandingPreference, setAllowAvatarLeaningPreference). - Removed Q_PROPERTY(bool isSitStandStateLocked which is no longer used, because we now always track the user's real-world sit/stand state, regardless of what we're doing with it. - MyAvatar::FollowHelper::shouldActivateHorizontal: If MyAvatar is not allowed to lean, this now returns true to recentre the footing if the head is outside the base of support. - MyAvatar::FollowHelper::shouldActivateHorizontalCG: If MyAvatar is not allowed to lean, this now always returns true to recentre the footing. Rearranged to avoid computing values that weren't used depending on the conditions. Resolved some duplicated code. - MyAvatar::setUserRecenterModel previously set HMDLeanRecenterEnabled based on the chosen mode, but it got reset when getting out of a sit. Now HMDLeanRecenterEnabled is only controlled by the scripts. - Added Rig::getUnscaledHipsHeight (like getUnscaledEyeHeight). Refactored a little to avoid duplicated code. Added DEFAULT_AVATAR_HIPS_HEIGHT which is the value that Rig::getUnscaledHipsHeight returns when using the default avatar. - Fix for recentring not behaving as requested by the user after getting up from click-to-sit (always behaving like 'Auto') : MyAvatar::endSit now passes false to centerBody for 'forceFollowYPos'. - Fix for incorrect vertical position of the avatar and viewpoint after changing lean recentre mode while not standing in the real world: MyAvatar::setAllowAvatarStandingPreference now calls centerBody with false for 'forceFollowYPos'. - computeHipsInSensorFrame: The code now matches the comments in that it only skips the dampening of the hips rotation if the centre-of-gravity model is being used.
487 lines
22 KiB
C++
Executable file
487 lines
22 KiB
C++
Executable file
//
|
|
// MyCharacterController.h
|
|
// interface/src/avatar
|
|
//
|
|
// Created by AndrewMeadows 2015.10.21
|
|
// 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
|
|
//
|
|
|
|
#include "MyCharacterController.h"
|
|
|
|
#include <BulletUtil.h>
|
|
#include "BulletCollision/NarrowPhaseCollision/btRaycastCallback.h"
|
|
|
|
#include "MyAvatar.h"
|
|
#include "DetailedMotionState.h"
|
|
|
|
// TODO: make avatars stand on steep slope
|
|
// TODO: make avatars not snag on low ceilings
|
|
|
|
|
|
void MyCharacterController::RayShotgunResult::reset() {
|
|
hitFraction = 1.0f;
|
|
walkable = true;
|
|
}
|
|
|
|
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar,
|
|
const FollowTimePerType& followTimeRemainingPerType) :
|
|
CharacterController(followTimeRemainingPerType) {
|
|
|
|
assert(avatar);
|
|
_avatar = avatar;
|
|
updateShapeIfNecessary();
|
|
}
|
|
|
|
MyCharacterController::~MyCharacterController() {
|
|
}
|
|
|
|
void MyCharacterController::addToWorld() {
|
|
CharacterController::addToWorld();
|
|
if (_rigidBody) {
|
|
initRayShotgun(_physicsEngine->getDynamicsWorld());
|
|
}
|
|
}
|
|
|
|
void MyCharacterController::updateShapeIfNecessary() {
|
|
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
|
_pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE;
|
|
if (_radius > 0.0f) {
|
|
// create RigidBody if it doesn't exist
|
|
if (!_rigidBody) {
|
|
btCollisionShape* shape = computeShape();
|
|
btScalar mass = 1.0f;
|
|
btVector3 inertia(1.0f, 1.0f, 1.0f);
|
|
_rigidBody = new btRigidBody(mass, nullptr, shape, inertia);
|
|
} else {
|
|
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
|
if (shape) {
|
|
delete shape;
|
|
}
|
|
shape = computeShape();
|
|
_rigidBody->setCollisionShape(shape);
|
|
}
|
|
updateMassProperties();
|
|
|
|
_rigidBody->setSleepingThresholds(0.0f, 0.0f);
|
|
_rigidBody->setAngularFactor(0.0f);
|
|
_rigidBody->setWorldTransform(btTransform(glmToBullet(_avatar->getWorldOrientation()),
|
|
glmToBullet(_avatar->getWorldPosition())));
|
|
_rigidBody->setDamping(0.0f, 0.0f);
|
|
if (_state == State::Hover) {
|
|
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
|
} else {
|
|
_rigidBody->setGravity(_gravity * _currentUp);
|
|
}
|
|
_rigidBody->setCollisionFlags(_rigidBody->getCollisionFlags() &
|
|
~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT));
|
|
} else {
|
|
// TODO: handle this failure case
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result) {
|
|
btVector3 rayDirection = glmToBullet(step);
|
|
btScalar stepLength = rayDirection.length();
|
|
if (stepLength < FLT_EPSILON) {
|
|
return false;
|
|
}
|
|
rayDirection /= stepLength;
|
|
|
|
// get _ghost ready for ray traces
|
|
btTransform transform = _rigidBody->getWorldTransform();
|
|
btVector3 newPosition = glmToBullet(position);
|
|
transform.setOrigin(newPosition);
|
|
_ghost.setWorldTransform(transform);
|
|
btMatrix3x3 rotation = transform.getBasis();
|
|
_ghost.refreshOverlappingPairCache();
|
|
|
|
CharacterRayResult rayResult(&_ghost);
|
|
CharacterRayResult closestRayResult(&_ghost);
|
|
btVector3 rayStart;
|
|
btVector3 rayEnd;
|
|
|
|
// compute rotation that will orient local ray start points to face step direction
|
|
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
|
|
btVector3 adjustedDirection = rayDirection - rayDirection.dot(_currentUp) * _currentUp;
|
|
btVector3 axis = forward.cross(adjustedDirection);
|
|
btScalar lengthAxis = axis.length();
|
|
if (lengthAxis > FLT_EPSILON) {
|
|
// we're walking sideways
|
|
btScalar cosAngle = lengthAxis / adjustedDirection.length();
|
|
if (cosAngle < 1.0f) {
|
|
btScalar angle = acosf(cosAngle);
|
|
if (rayDirection.dot(forward) < 0.0f) {
|
|
angle = PI - angle;
|
|
}
|
|
axis /= lengthAxis;
|
|
rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation;
|
|
}
|
|
} else if (rayDirection.dot(forward) < 0.0f) {
|
|
// we're walking backwards
|
|
rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation;
|
|
}
|
|
|
|
// scan the top
|
|
// NOTE: if we scan an extra distance forward we can detect flat surfaces that are too steep to walk on.
|
|
// The approximate extra distance can be derived with trigonometry.
|
|
//
|
|
// minimumForward = [ (maxStepHeight + radius / cosTheta - radius) * (cosTheta / sinTheta) - radius ]
|
|
//
|
|
// where: theta = max angle between floor normal and vertical
|
|
//
|
|
// if stepLength is not long enough we can add the difference.
|
|
//
|
|
btScalar cosTheta = _minFloorNormalDotUp;
|
|
btScalar sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
|
|
const btScalar MIN_FORWARD_SLOP = 0.12f; // HACK: not sure why this is necessary to detect steepest walkable slope
|
|
btScalar forwardSlop = (_maxStepHeight + _radius / cosTheta - _radius) * (cosTheta / sinTheta) - (_radius + stepLength) + MIN_FORWARD_SLOP;
|
|
if (forwardSlop < 0.0f) {
|
|
// BIG step, no slop necessary
|
|
forwardSlop = 0.0f;
|
|
}
|
|
|
|
const btScalar backSlop = 0.04f;
|
|
for (int32_t i = 0; i < _topPoints.size(); ++i) {
|
|
rayStart = newPosition + rotation * _topPoints[i] - backSlop * rayDirection;
|
|
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
|
|
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
|
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
|
closestRayResult = rayResult;
|
|
}
|
|
if (result.walkable) {
|
|
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
|
|
result.walkable = false;
|
|
// the top scan wasn't walkable so don't bother scanning the bottom
|
|
// remove both forwardSlop and backSlop
|
|
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
|
|
return result.hitFraction < 1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (_state == State::Hover) {
|
|
// scan the bottom just like the top
|
|
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
|
|
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
|
|
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
|
|
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
|
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
|
closestRayResult = rayResult;
|
|
}
|
|
if (result.walkable) {
|
|
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
|
|
result.walkable = false;
|
|
// the bottom scan wasn't walkable
|
|
// remove both forwardSlop and backSlop
|
|
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
|
|
return result.hitFraction < 1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// scan the bottom looking for nearest step point
|
|
// remove forwardSlop
|
|
result.hitFraction = (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop)) / (backSlop + stepLength);
|
|
|
|
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
|
|
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
|
|
rayEnd = rayStart + (backSlop + stepLength) * rayDirection;
|
|
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
|
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
|
closestRayResult = rayResult;
|
|
}
|
|
}
|
|
}
|
|
// remove backSlop
|
|
// NOTE: backSlop removal can produce a NEGATIVE hitFraction!
|
|
// which means the shape is actually in interpenetration
|
|
result.hitFraction = ((closestRayResult.m_closestHitFraction * (backSlop + stepLength)) - backSlop) / stepLength;
|
|
}
|
|
return result.hitFraction < 1.0f;
|
|
}
|
|
|
|
int32_t MyCharacterController::computeCollisionMask() const {
|
|
int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR;
|
|
if (_collisionless && _collisionlessAllowed) {
|
|
collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS;
|
|
} else if (!_collideWithOtherAvatars) {
|
|
collisionMask &= ~BULLET_COLLISION_GROUP_OTHER_AVATAR;
|
|
}
|
|
return collisionMask;
|
|
}
|
|
|
|
void MyCharacterController::handleChangedCollisionMask() {
|
|
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) {
|
|
// ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody
|
|
// but we don't do it here. Instead we set some flags to remind us to do it later.
|
|
_pendingFlags |= (PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_ADD_TO_SIMULATION);
|
|
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK;
|
|
updateCurrentGravity();
|
|
}
|
|
}
|
|
|
|
bool MyCharacterController::needsSafeLandingSupport() const {
|
|
return _isStuck && _numStuckSubsteps >= NUM_SUBSTEPS_FOR_SAFE_LANDING_RETRY;
|
|
}
|
|
|
|
btConvexHullShape* MyCharacterController::computeShape() const {
|
|
// HACK: the avatar collides using convex hull with a collision margin equal to
|
|
// the old capsule radius. Two points define a capsule and additional points are
|
|
// spread out at chest level to produce a slight taper toward the feet. This
|
|
// makes the avatar more likely to collide with vertical walls at a higher point
|
|
// and thus less likely to produce a single-point collision manifold below the
|
|
// _maxStepHeight when walking into against vertical surfaces --> fixes a bug
|
|
// where the "walk up steps" feature would allow the avatar to walk up vertical
|
|
// walls.
|
|
const int32_t NUM_POINTS = 6;
|
|
btVector3 points[NUM_POINTS];
|
|
btVector3 xAxis = btVector3(1.0f, 0.0f, 0.0f);
|
|
btVector3 yAxis = btVector3(0.0f, 1.0f, 0.0f);
|
|
btVector3 zAxis = btVector3(0.0f, 0.0f, 1.0f);
|
|
points[0] = _halfHeight * yAxis;
|
|
points[1] = -_halfHeight * yAxis;
|
|
points[2] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * zAxis;
|
|
points[3] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * zAxis;
|
|
points[4] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * xAxis;
|
|
points[5] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * xAxis;
|
|
btConvexHullShape* shape = new btConvexHullShape(reinterpret_cast<btScalar*>(points), NUM_POINTS);
|
|
shape->setMargin(_radius);
|
|
return shape;
|
|
}
|
|
|
|
void MyCharacterController::initRayShotgun(const btCollisionWorld* world) {
|
|
// In order to trace rays out from the avatar's shape surface we need to know where the start points are in
|
|
// the local-frame. Since the avatar shape is somewhat irregular computing these points by hand is a hassle
|
|
// so instead we ray-trace backwards to the avatar to find them.
|
|
//
|
|
// We trace back a regular grid (see below) of points against the shape and keep any that hit.
|
|
// ___
|
|
// + / + \ +
|
|
// |+ +|
|
|
// +| + | +
|
|
// |+ +|
|
|
// +| + | +
|
|
// |+ +|
|
|
// + \ + / +
|
|
// ---
|
|
// The shotgun will send rays out from these same points to see if the avatar's shape can proceed through space.
|
|
|
|
// helper class for simple ray-traces against character
|
|
class MeOnlyResultCallback : public btCollisionWorld::ClosestRayResultCallback {
|
|
public:
|
|
MeOnlyResultCallback (btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
|
|
_me = me;
|
|
m_collisionFilterGroup = BULLET_COLLISION_GROUP_DYNAMIC;
|
|
m_collisionFilterMask = BULLET_COLLISION_MASK_DYNAMIC;
|
|
}
|
|
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) override {
|
|
if (rayResult.m_collisionObject != _me) {
|
|
return 1.0f;
|
|
}
|
|
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
|
}
|
|
btRigidBody* _me;
|
|
};
|
|
|
|
const btScalar fullHalfHeight = _radius + _halfHeight;
|
|
const btScalar divisionLine = -fullHalfHeight + _maxStepHeight; // line between top and bottom
|
|
const btScalar topHeight = fullHalfHeight - divisionLine;
|
|
const btScalar slop = 0.02f;
|
|
|
|
const int32_t NUM_ROWS = 5; // must be odd number > 1
|
|
const int32_t NUM_COLUMNS = 5; // must be odd number > 1
|
|
btVector3 reach = (2.0f * _radius) * btVector3(0.0f, 0.0f, 1.0f);
|
|
|
|
{ // top points
|
|
_topPoints.clear();
|
|
_topPoints.reserve(NUM_ROWS * NUM_COLUMNS);
|
|
btScalar stepY = (topHeight - slop) / (btScalar)(NUM_ROWS - 1);
|
|
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
|
|
|
|
btTransform transform = _rigidBody->getWorldTransform();
|
|
btVector3 position = transform.getOrigin();
|
|
btMatrix3x3 rotation = transform.getBasis();
|
|
|
|
for (int32_t i = 0; i < NUM_ROWS; ++i) {
|
|
int32_t maxJ = NUM_COLUMNS;
|
|
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
|
|
if (i % 2 == 1) {
|
|
// odd rows have one less point and start a halfStep closer
|
|
maxJ -= 1;
|
|
offsetX += 0.5f * stepX;
|
|
}
|
|
for (int32_t j = 0; j < maxJ; ++j) {
|
|
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, divisionLine + (btScalar)(i) * stepY, 0.0f);
|
|
btVector3 localRayStart = localRayEnd - reach;
|
|
MeOnlyResultCallback result(_rigidBody);
|
|
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
|
|
if (result.m_closestHitFraction < 1.0f) {
|
|
_topPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{ // bottom points
|
|
_bottomPoints.clear();
|
|
_bottomPoints.reserve(NUM_ROWS * NUM_COLUMNS);
|
|
|
|
btScalar steepestStepHitHeight = (_radius + 0.04f) * (1.0f - DEFAULT_MIN_FLOOR_NORMAL_DOT_UP);
|
|
btScalar stepY = (_maxStepHeight - slop - steepestStepHitHeight) / (btScalar)(NUM_ROWS - 1);
|
|
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
|
|
|
|
btTransform transform = _rigidBody->getWorldTransform();
|
|
btVector3 position = transform.getOrigin();
|
|
btMatrix3x3 rotation = transform.getBasis();
|
|
|
|
for (int32_t i = 0; i < NUM_ROWS; ++i) {
|
|
int32_t maxJ = NUM_COLUMNS;
|
|
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
|
|
if (i % 2 == 1) {
|
|
// odd rows have one less point and start a halfStep closer
|
|
maxJ -= 1;
|
|
offsetX += 0.5f * stepX;
|
|
}
|
|
for (int32_t j = 0; j < maxJ; ++j) {
|
|
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, (divisionLine - slop) - (btScalar)(i) * stepY, 0.0f);
|
|
btVector3 localRayStart = localRayEnd - reach;
|
|
MeOnlyResultCallback result(_rigidBody);
|
|
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
|
|
if (result.m_closestHitFraction < 1.0f) {
|
|
_bottomPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MyCharacterController::updateMassProperties() {
|
|
assert(_rigidBody);
|
|
// the inertia tensor of a capsule with Y-axis of symmetry, radius R and cylinder height H is:
|
|
// Ix = density * (volumeCylinder * (H^2 / 12 + R^2 / 4) + volumeSphere * (2R^2 / 5 + H^2 / 2 + 3HR / 8))
|
|
// Iy = density * (volumeCylinder * (R^2 / 2) + volumeSphere * (2R^2 / 5)
|
|
btScalar r2 = _radius * _radius;
|
|
btScalar h2 = 4.0f * _halfHeight * _halfHeight;
|
|
btScalar volumeSphere = 4.0f * PI * r2 * _radius / 3.0f;
|
|
btScalar volumeCylinder = TWO_PI * r2 * 2.0f * _halfHeight;
|
|
btScalar cylinderXZ = volumeCylinder * (h2 / 12.0f + r2 / 4.0f);
|
|
btScalar capsXZ = volumeSphere * (2.0f * r2 / 5.0f + h2 / 2.0f + 6.0f * _halfHeight * _radius / 8.0f);
|
|
btScalar inertiaXZ = _density * (cylinderXZ + capsXZ);
|
|
btScalar inertiaY = _density * ((volumeCylinder * r2 / 2.0f) + volumeSphere * (2.0f * r2 / 5.0f));
|
|
btVector3 inertia(inertiaXZ, inertiaY, inertiaXZ);
|
|
|
|
btScalar mass = _density * (volumeCylinder + volumeSphere);
|
|
|
|
_rigidBody->setMassProps(mass, inertia);
|
|
}
|
|
|
|
const btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int32_t jointIndex) {
|
|
ShapeInfo shapeInfo;
|
|
_avatar->computeDetailedShapeInfo(shapeInfo, jointIndex);
|
|
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
|
|
const btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
|
return shape;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int32_t jointIndex) {
|
|
const btCollisionShape* shape = createDetailedCollisionShapeForJoint(jointIndex);
|
|
if (shape) {
|
|
DetailedMotionState* motionState = new DetailedMotionState(_avatar, shape, jointIndex);
|
|
motionState->setMass(_avatar->computeMass());
|
|
return motionState;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void MyCharacterController::clearDetailedMotionStates() {
|
|
// we don't actually clear the MotionStates here
|
|
// instead we twiddle some flags as a signal of what to do later
|
|
_pendingFlags |= PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
|
|
// We make sure we don't add them again
|
|
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
|
|
}
|
|
|
|
void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
|
|
for (auto& detailedMotionState : _detailedMotionStates) {
|
|
detailedMotionState->forceActive();
|
|
}
|
|
if (_pendingFlags & PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION) {
|
|
_pendingFlags &= ~PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
|
|
for (auto& detailedMotionState : _detailedMotionStates) {
|
|
transaction.objectsToRemove.push_back(detailedMotionState);
|
|
}
|
|
// NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove
|
|
// See AvatarManager::handleProcessedPhysicsTransaction()
|
|
_detailedMotionStates.clear();
|
|
}
|
|
if (_pendingFlags & PENDING_FLAG_ADD_DETAILED_TO_SIMULATION) {
|
|
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
|
|
for (int32_t i = 0; i < _avatar->getJointCount(); i++) {
|
|
auto dMotionState = createDetailedMotionStateForJoint(i);
|
|
if (dMotionState) {
|
|
_detailedMotionStates.push_back(dMotionState);
|
|
transaction.objectsToAdd.push_back(dMotionState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class DetailedRayResultCallback : public btCollisionWorld::AllHitsRayResultCallback {
|
|
public:
|
|
DetailedRayResultCallback()
|
|
: btCollisionWorld::AllHitsRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
|
|
// the RayResultCallback's group and mask must match MY_AVATAR
|
|
m_collisionFilterGroup = BULLET_COLLISION_GROUP_DETAILED_RAY;
|
|
m_collisionFilterMask = BULLET_COLLISION_MASK_DETAILED_RAY;
|
|
}
|
|
|
|
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
|
|
return AllHitsRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
|
}
|
|
};
|
|
|
|
std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction,
|
|
const btScalar& length, const QVector<uint>& jointsToExclude) const {
|
|
std::vector<RayAvatarResult> foundAvatars;
|
|
if (_physicsEngine) {
|
|
btVector3 end = origin + length * direction;
|
|
DetailedRayResultCallback rayCallback = DetailedRayResultCallback();
|
|
rayCallback.m_flags |= btTriangleRaycastCallback::kF_KeepUnflippedNormal;
|
|
rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest;
|
|
_physicsEngine->getDynamicsWorld()->rayTest(origin, end, rayCallback);
|
|
if (rayCallback.m_hitFractions.size() > 0) {
|
|
foundAvatars.reserve(rayCallback.m_hitFractions.size());
|
|
for (int32_t i = 0; i < rayCallback.m_hitFractions.size(); i++) {
|
|
auto object = rayCallback.m_collisionObjects[i];
|
|
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(object->getUserPointer());
|
|
if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) {
|
|
DetailedMotionState* detailedMotionState = dynamic_cast<DetailedMotionState*>(motionState);
|
|
MyCharacterController::RayAvatarResult result;
|
|
result._intersect = true;
|
|
result._intersectWithAvatar = detailedMotionState->getAvatarID();
|
|
result._intersectionPoint = bulletToGLM(rayCallback.m_hitPointWorld[i]);
|
|
result._intersectionNormal = bulletToGLM(rayCallback.m_hitNormalWorld[i]);
|
|
result._distance = length * rayCallback.m_hitFractions[i];
|
|
result._intersectWithJoint = detailedMotionState->getJointIndex();
|
|
result._isBound = detailedMotionState->getIsBound(result._boundJoints);
|
|
btVector3 center;
|
|
btScalar radius;
|
|
detailedMotionState->getShape()->getBoundingSphere(center, radius);
|
|
result._maxDistance = (float)radius;
|
|
foundAvatars.push_back(result);
|
|
}
|
|
}
|
|
std::sort(foundAvatars.begin(), foundAvatars.end(), [](const RayAvatarResult& resultA, const RayAvatarResult& resultB) {
|
|
return resultA._distance < resultB._distance;
|
|
});
|
|
}
|
|
}
|
|
return foundAvatars;
|
|
}
|