mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 13:33:38 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into island
This commit is contained in:
commit
4600173faa
29 changed files with 655 additions and 1154 deletions
|
@ -103,7 +103,13 @@ if (APPLE)
|
|||
endif()
|
||||
|
||||
# create the executable, make it a bundle on OS X
|
||||
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
|
||||
if (APPLE)
|
||||
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
|
||||
elseif(WIN32)
|
||||
add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM})
|
||||
else()
|
||||
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
|
||||
endif()
|
||||
|
||||
# set up the external glm library
|
||||
add_dependency_external_projects(glm bullet)
|
||||
|
|
|
@ -565,8 +565,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
}
|
||||
|
||||
void Application::aboutToQuit() {
|
||||
_aboutToQuit = true;
|
||||
emit beforeAboutToQuit();
|
||||
|
||||
_aboutToQuit = true;
|
||||
cleanupBeforeQuit();
|
||||
}
|
||||
|
||||
|
@ -1599,6 +1600,10 @@ void Application::setFullscreen(bool fullscreen) {
|
|||
Menu::getInstance()->getActionForOption(MenuOption::Fullscreen)->setChecked(fullscreen);
|
||||
}
|
||||
|
||||
// The following code block is useful on platforms that can have a visible
|
||||
// app menu in a fullscreen window. However the OSX mechanism hides the
|
||||
// application menu for fullscreen apps, so the check is not required.
|
||||
#ifndef Q_OS_MAC
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) {
|
||||
if (fullscreen) {
|
||||
// Menu hide() disables menu commands, and show() after hide() doesn't work with Rift VR display.
|
||||
|
@ -1621,6 +1626,7 @@ void Application::setFullscreen(bool fullscreen) {
|
|||
_window->menuBar()->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Work around Qt bug that prevents floating menus being shown when in fullscreen mode.
|
||||
// https://bugreports.qt.io/browse/QTBUG-41883
|
||||
|
|
|
@ -337,6 +337,8 @@ signals:
|
|||
|
||||
void faceURLChanged(const QString& newValue);
|
||||
void skeletonURLChanged(const QString& newValue);
|
||||
|
||||
void beforeAboutToQuit();
|
||||
|
||||
public slots:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
|
|
|
@ -1116,7 +1116,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool hasFloor) {
|
||||
glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool isHovering) {
|
||||
if (! (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED)) {
|
||||
return localVelocity;
|
||||
}
|
||||
|
@ -1137,7 +1137,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
|
|||
if (_isPushing || isThrust ||
|
||||
(_scriptedMotorTimescale < MAX_KEYBOARD_MOTOR_TIMESCALE &&
|
||||
_motionBehaviors | AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) {
|
||||
// we don't want to break if anything is pushing the avatar around
|
||||
// we don't want to brake if something is pushing the avatar around
|
||||
timescale = _keyboardMotorTimescale;
|
||||
_isBraking = false;
|
||||
} else {
|
||||
|
@ -1168,14 +1168,8 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
|
|||
if (directionLength > EPSILON) {
|
||||
direction /= directionLength;
|
||||
|
||||
if (hasFloor) {
|
||||
// we're walking --> simple exponential decay toward target walk speed
|
||||
const float WALK_ACCELERATION_TIMESCALE = 0.7f; // seconds to decrease delta to 1/e
|
||||
_keyboardMotorVelocity = MAX_WALKING_SPEED * direction;
|
||||
motorEfficiency = glm::clamp(deltaTime / WALK_ACCELERATION_TIMESCALE, 0.0f, 1.0f);
|
||||
|
||||
} else {
|
||||
// we're flying --> more complex curve
|
||||
if (isHovering) {
|
||||
// we're flying --> complex acceleration curve with high max speed
|
||||
float motorSpeed = glm::length(_keyboardMotorVelocity);
|
||||
float finalMaxMotorSpeed = _scale * MAX_KEYBOARD_MOTOR_SPEED;
|
||||
float speedGrowthTimescale = 2.0f;
|
||||
|
@ -1191,6 +1185,12 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
|
|||
motorSpeed = finalMaxMotorSpeed;
|
||||
}
|
||||
_keyboardMotorVelocity = motorSpeed * direction;
|
||||
|
||||
} else {
|
||||
// we're using a floor --> simple exponential decay toward target walk speed
|
||||
const float WALK_ACCELERATION_TIMESCALE = 0.7f; // seconds to decrease delta to 1/e
|
||||
_keyboardMotorVelocity = MAX_WALKING_SPEED * direction;
|
||||
motorEfficiency = glm::clamp(deltaTime / WALK_ACCELERATION_TIMESCALE, 0.0f, 1.0f);
|
||||
}
|
||||
_isPushing = true;
|
||||
}
|
||||
|
@ -1198,7 +1198,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
|
|||
} else {
|
||||
_keyboardMotorVelocity = glm::vec3(0.0f);
|
||||
newLocalVelocity = (1.0f - motorEfficiency) * localVelocity;
|
||||
if (hasFloor && !_wasPushing) {
|
||||
if (!isHovering && !_wasPushing) {
|
||||
float speed = glm::length(newLocalVelocity);
|
||||
if (speed > MIN_AVATAR_SPEED) {
|
||||
// add small constant friction to help avatar drift to a stop sooner at low speeds
|
||||
|
@ -1238,8 +1238,8 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
glm::quat rotation = getHead()->getCameraOrientation();
|
||||
glm::vec3 localVelocity = glm::inverse(rotation) * _velocity;
|
||||
|
||||
bool isOnGround = _characterController.onGround();
|
||||
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isOnGround);
|
||||
bool isHovering = _characterController.isHovering();
|
||||
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isHovering);
|
||||
newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity);
|
||||
|
||||
// rotate back into world-frame
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#define hifi_MyAvatar_h
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include <CharacterController.h>
|
||||
#include <DynamicCharacterController.h>
|
||||
|
||||
#include "Avatar.h"
|
||||
|
||||
|
@ -122,7 +122,7 @@ public:
|
|||
|
||||
virtual glm::vec3 getSkeletonPosition() const;
|
||||
void updateLocalAABox();
|
||||
CharacterController* getCharacterController() { return &_characterController; }
|
||||
DynamicCharacterController* getCharacterController() { return &_characterController; }
|
||||
void updateCharacterController();
|
||||
|
||||
void clearJointAnimationPriorities();
|
||||
|
@ -203,7 +203,7 @@ private:
|
|||
int _scriptedMotorFrame;
|
||||
quint32 _motionBehaviors;
|
||||
|
||||
CharacterController _characterController;
|
||||
DynamicCharacterController _characterController;
|
||||
|
||||
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
||||
glm::vec3 _targetAvatarPosition;
|
||||
|
@ -224,7 +224,7 @@ private:
|
|||
|
||||
// private methods
|
||||
void updateOrientation(float deltaTime);
|
||||
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor);
|
||||
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool isHovering);
|
||||
glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity);
|
||||
void updatePosition(float deltaTime);
|
||||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
|
|
|
@ -87,7 +87,7 @@ HMDToolsDialog::HMDToolsDialog(QWidget* parent) :
|
|||
}
|
||||
|
||||
// when the application is about to quit, leave HDM mode
|
||||
connect(Application::getInstance(), SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()));
|
||||
connect(Application::getInstance(), SIGNAL(beforeAboutToQuit()), this, SLOT(aboutToQuit()));
|
||||
|
||||
// keep track of changes to the number of screens
|
||||
connect(QApplication::desktop(), &QDesktopWidget::screenCountChanged, this, &HMDToolsDialog::screenCountChanged);
|
||||
|
@ -184,8 +184,8 @@ void HMDToolsDialog::leaveHDMMode() {
|
|||
_switchModeButton->setText("Enter HMD Mode");
|
||||
_debugDetails->setText(getDebugDetails());
|
||||
|
||||
Application::getInstance()->setFullscreen(false);
|
||||
Application::getInstance()->setEnableVRMode(false);
|
||||
Application::getInstance()->setFullscreen(false);
|
||||
Application::getInstance()->getWindow()->activateWindow();
|
||||
|
||||
if (_wasMoved) {
|
||||
|
|
|
@ -458,6 +458,18 @@ FBXNode parseFBX(QIODevice* device) {
|
|||
return top;
|
||||
}
|
||||
|
||||
QVector<glm::vec4> createVec4Vector(const QVector<double>& doubleVector) {
|
||||
QVector<glm::vec4> values;
|
||||
for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 4 * 4); it != end; ) {
|
||||
float x = *it++;
|
||||
float y = *it++;
|
||||
float z = *it++;
|
||||
float w = *it++;
|
||||
values.append(glm::vec4(x, y, z, w));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
|
||||
QVector<glm::vec3> values;
|
||||
for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 3 * 3); it != end; ) {
|
||||
|
@ -739,6 +751,11 @@ public:
|
|||
bool normalsByVertex;
|
||||
QVector<glm::vec3> normals;
|
||||
QVector<int> normalIndices;
|
||||
|
||||
bool colorsByVertex;
|
||||
QVector<glm::vec4> colors;
|
||||
QVector<int> colorIndices;
|
||||
|
||||
QVector<glm::vec2> texCoords;
|
||||
QVector<int> texCoordIndices;
|
||||
|
||||
|
@ -776,6 +793,23 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
glm::vec4 color;
|
||||
bool hasColors = (data.colors.size() > 1);
|
||||
if (hasColors) {
|
||||
int colorIndex = data.colorsByVertex ? vertexIndex : index;
|
||||
if (data.colorIndices.isEmpty()) {
|
||||
if (colorIndex < data.colors.size()) {
|
||||
color = data.colors.at(colorIndex);
|
||||
}
|
||||
} else if (colorIndex < data.colorIndices.size()) {
|
||||
colorIndex = data.colorIndices.at(colorIndex);
|
||||
if (colorIndex >= 0 && colorIndex < data.colors.size()) {
|
||||
color = data.colors.at(colorIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.texCoordIndices.isEmpty()) {
|
||||
if (index < data.texCoords.size()) {
|
||||
vertex.texCoord = data.texCoords.at(index);
|
||||
|
@ -810,6 +844,9 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
|
|||
data.extracted.mesh.vertices.append(position);
|
||||
data.extracted.mesh.normals.append(normal);
|
||||
data.extracted.mesh.texCoords.append(vertex.texCoord);
|
||||
if (hasColors) {
|
||||
data.extracted.mesh.colors.append(glm::vec3(color));
|
||||
}
|
||||
if (hasMoreTexcoords) {
|
||||
data.extracted.mesh.texCoords1.append(vertex.texCoord1);
|
||||
}
|
||||
|
@ -852,6 +889,28 @@ ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex) {
|
|||
// hack to work around wacky Makehuman exports
|
||||
data.normalsByVertex = true;
|
||||
}
|
||||
} else if (child.name == "LayerElementColor") {
|
||||
data.colorsByVertex = false;
|
||||
bool indexToDirect = false;
|
||||
foreach (const FBXNode& subdata, child.children) {
|
||||
if (subdata.name == "Colors") {
|
||||
data.colors = createVec4Vector(getDoubleVector(subdata));
|
||||
|
||||
} else if (subdata.name == "ColorsIndex") {
|
||||
data.colorIndices = getIntVector(subdata);
|
||||
|
||||
} else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == "ByVertice") {
|
||||
data.colorsByVertex = true;
|
||||
|
||||
} else if (subdata.name == "ReferenceInformationType" && subdata.properties.at(0) == "IndexToDirect") {
|
||||
indexToDirect = true;
|
||||
}
|
||||
}
|
||||
if (indexToDirect && data.normalIndices.isEmpty()) {
|
||||
// hack to work around wacky Makehuman exports
|
||||
data.colorsByVertex = true;
|
||||
}
|
||||
|
||||
} else if (child.name == "LayerElementUV") {
|
||||
if (child.properties.at(0).toInt() == 0) {
|
||||
AttributeData attrib;
|
||||
|
|
|
@ -1,931 +0,0 @@
|
|||
/*
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com
|
||||
2015.03.25 -- modified by Andrew Meadows andrew@highfidelity.io
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated
|
||||
but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "CharacterController.h"
|
||||
|
||||
const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
|
||||
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
|
||||
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
|
||||
const uint32_t PENDING_FLAG_JUMP = 1U << 3;
|
||||
|
||||
// static helper method
|
||||
static btVector3 getNormalizedVector(const btVector3& v) {
|
||||
// NOTE: check the length first, then normalize
|
||||
// --> avoids assert when trying to normalize zero-length vectors
|
||||
btScalar vLength = v.length();
|
||||
if (vLength < FLT_EPSILON) {
|
||||
return btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
btVector3 n = v;
|
||||
n /= vLength;
|
||||
return n;
|
||||
}
|
||||
|
||||
class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback {
|
||||
public:
|
||||
btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) :
|
||||
btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
|
||||
_me = me;
|
||||
}
|
||||
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) {
|
||||
if (rayResult.m_collisionObject == _me) {
|
||||
return 1.0f;
|
||||
}
|
||||
return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace);
|
||||
}
|
||||
protected:
|
||||
btCollisionObject* _me;
|
||||
};
|
||||
|
||||
|
||||
class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
|
||||
public:
|
||||
btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot)
|
||||
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
|
||||
, _me(me)
|
||||
, _up(up)
|
||||
, _minSlopeDot(minSlopeDot)
|
||||
{
|
||||
}
|
||||
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) {
|
||||
if (convexResult.m_hitCollisionObject == _me) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
|
||||
if (!convexResult.m_hitCollisionObject->hasContactResponse()) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
|
||||
btVector3 hitNormalWorld;
|
||||
if (normalInWorldSpace) {
|
||||
hitNormalWorld = convexResult.m_hitNormalLocal;
|
||||
} else {
|
||||
///need to transform normal into worldspace
|
||||
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
|
||||
}
|
||||
|
||||
// Note: hitNormalWorld points into character, away from object
|
||||
// and _up points opposite to movement
|
||||
|
||||
btScalar dotUp = _up.dot(hitNormalWorld);
|
||||
if (dotUp < _minSlopeDot) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
|
||||
return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
|
||||
}
|
||||
|
||||
protected:
|
||||
btCollisionObject* _me;
|
||||
const btVector3 _up;
|
||||
btScalar _minSlopeDot;
|
||||
};
|
||||
|
||||
class StepDownConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
|
||||
// special convex sweep callback for character during the stepDown() phase
|
||||
public:
|
||||
StepDownConvexResultCallback(btCollisionObject* me,
|
||||
const btVector3& up,
|
||||
const btVector3& start,
|
||||
const btVector3& step,
|
||||
const btVector3& pushDirection,
|
||||
btScalar minSlopeDot,
|
||||
btScalar radius,
|
||||
btScalar halfHeight)
|
||||
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
|
||||
, _me(me)
|
||||
, _up(up)
|
||||
, _start(start)
|
||||
, _step(step)
|
||||
, _pushDirection(pushDirection)
|
||||
, _minSlopeDot(minSlopeDot)
|
||||
, _radius(radius)
|
||||
, _halfHeight(halfHeight)
|
||||
{
|
||||
}
|
||||
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) {
|
||||
if (convexResult.m_hitCollisionObject == _me) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
|
||||
if (!convexResult.m_hitCollisionObject->hasContactResponse()) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
|
||||
btVector3 hitNormalWorld;
|
||||
if (normalInWorldSpace) {
|
||||
hitNormalWorld = convexResult.m_hitNormalLocal;
|
||||
} else {
|
||||
///need to transform normal into worldspace
|
||||
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal;
|
||||
}
|
||||
|
||||
// Note: hitNormalWorld points into character, away from object
|
||||
// and _up points opposite to movement
|
||||
|
||||
btScalar dotUp = _up.dot(hitNormalWorld);
|
||||
if (dotUp < _minSlopeDot) {
|
||||
if (hitNormalWorld.dot(_pushDirection) > 0.0f) {
|
||||
// ignore hits that push in same direction as character is moving
|
||||
// which helps character NOT snag when stepping off ledges
|
||||
return btScalar(1.0f);
|
||||
}
|
||||
|
||||
// compute the angle between "down" and the line from character center to "hit" point
|
||||
btVector3 fractionalStep = convexResult.m_hitFraction * _step;
|
||||
btVector3 localHit = convexResult.m_hitPointLocal - _start + fractionalStep;
|
||||
btScalar angle = localHit.angle(-_up);
|
||||
|
||||
// compute a maxAngle based on size of _step
|
||||
btVector3 side(_radius, - (_halfHeight - _step.length() + fractionalStep.dot(_up)), 0.0f);
|
||||
btScalar maxAngle = side.angle(-_up);
|
||||
|
||||
// Ignore hits that are larger than maxAngle. Effectively what is happening here is:
|
||||
// we're ignoring hits at contacts that have non-vertical normals... if they hit higher
|
||||
// than the character's "feet". Ignoring the contact allows the character to slide down
|
||||
// for these hits. In other words, vertical walls against the character's torso will
|
||||
// not prevent them from "stepping down" to find the floor.
|
||||
if (angle > maxAngle) {
|
||||
return btScalar(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
btScalar fraction = ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
|
||||
return fraction;
|
||||
}
|
||||
|
||||
protected:
|
||||
btCollisionObject* _me;
|
||||
const btVector3 _up;
|
||||
btVector3 _start;
|
||||
btVector3 _step;
|
||||
btVector3 _pushDirection;
|
||||
btScalar _minSlopeDot;
|
||||
btScalar _radius;
|
||||
btScalar _halfHeight;
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'
|
||||
*
|
||||
* from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html
|
||||
*/
|
||||
btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal) {
|
||||
return direction - (btScalar(2.0) * direction.dot(normal)) * normal;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the portion of 'direction' that is parallel to 'normal'
|
||||
*/
|
||||
btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal) {
|
||||
btScalar magnitude = direction.dot(normal);
|
||||
return normal * magnitude;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the portion of 'direction' that is perpindicular to 'normal'
|
||||
*/
|
||||
btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal) {
|
||||
return direction - parallelComponent(direction, normal);
|
||||
}
|
||||
|
||||
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
|
||||
const float DEFAULT_GRAVITY = 5.0f;
|
||||
const float TERMINAL_VELOCITY = 55.0f;
|
||||
const float JUMP_SPEED = 3.5f;
|
||||
|
||||
CharacterController::CharacterController(AvatarData* avatarData) {
|
||||
assert(avatarData);
|
||||
_avatarData = avatarData;
|
||||
|
||||
_enabled = false;
|
||||
_ghostObject = NULL;
|
||||
_convexShape = NULL;
|
||||
|
||||
_addedMargin = 0.02f;
|
||||
_walkDirection.setValue(0.0f,0.0f,0.0f);
|
||||
_velocityTimeInterval = 0.0f;
|
||||
_verticalVelocity = 0.0f;
|
||||
_verticalOffset = 0.0f;
|
||||
_gravity = DEFAULT_GRAVITY;
|
||||
_maxFallSpeed = TERMINAL_VELOCITY;
|
||||
_jumpSpeed = JUMP_SPEED;
|
||||
_isOnGround = false;
|
||||
_isJumping = false;
|
||||
_isHovering = true;
|
||||
_jumpToHoverStart = 0;
|
||||
setMaxSlope(btRadians(45.0f));
|
||||
_lastStepUp = 0.0f;
|
||||
|
||||
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
||||
updateShapeIfNecessary();
|
||||
}
|
||||
|
||||
CharacterController::~CharacterController() {
|
||||
delete _ghostObject;
|
||||
_ghostObject = NULL;
|
||||
delete _convexShape;
|
||||
_convexShape = NULL;
|
||||
// make sure you remove this Character from its DynamicsWorld before reaching this spot
|
||||
assert(_dynamicsWorld == NULL);
|
||||
}
|
||||
|
||||
btPairCachingGhostObject* CharacterController::getGhostObject() {
|
||||
return _ghostObject;
|
||||
}
|
||||
|
||||
bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorld) {
|
||||
BT_PROFILE("recoverFromPenetration");
|
||||
// Here we must refresh the overlapping paircache as the penetrating movement itself or the
|
||||
// previous recovery iteration might have used setWorldTransform and pushed us into an object
|
||||
// that is not in the previous cache contents from the last timestep, as will happen if we
|
||||
// are pushed into a new AABB overlap. Unhandled this means the next convex sweep gets stuck.
|
||||
//
|
||||
// Do this by calling the broadphase's setAabb with the moved AABB, this will update the broadphase
|
||||
// paircache and the ghostobject's internal paircache at the same time. /BW
|
||||
|
||||
btVector3 minAabb, maxAabb;
|
||||
_convexShape->getAabb(_ghostObject->getWorldTransform(), minAabb, maxAabb);
|
||||
collisionWorld->getBroadphase()->setAabb(_ghostObject->getBroadphaseHandle(),
|
||||
minAabb,
|
||||
maxAabb,
|
||||
collisionWorld->getDispatcher());
|
||||
|
||||
bool penetration = false;
|
||||
|
||||
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
|
||||
|
||||
_currentPosition = _ghostObject->getWorldTransform().getOrigin();
|
||||
|
||||
btVector3 currentPosition = _currentPosition;
|
||||
|
||||
btScalar maxPen = btScalar(0.0);
|
||||
for (int i = 0; i < _ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) {
|
||||
_manifoldArray.resize(0);
|
||||
|
||||
btBroadphasePair* collisionPair = &_ghostObject->getOverlappingPairCache()->getOverlappingPairArray()[i];
|
||||
|
||||
btCollisionObject* obj0 = static_cast<btCollisionObject*>(collisionPair->m_pProxy0->m_clientObject);
|
||||
btCollisionObject* obj1 = static_cast<btCollisionObject*>(collisionPair->m_pProxy1->m_clientObject);
|
||||
|
||||
if ((obj0 && !obj0->hasContactResponse()) || (obj1 && !obj1->hasContactResponse())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collisionPair->m_algorithm) {
|
||||
collisionPair->m_algorithm->getAllContactManifolds(_manifoldArray);
|
||||
}
|
||||
|
||||
for (int j = 0;j < _manifoldArray.size(); j++) {
|
||||
btPersistentManifold* manifold = _manifoldArray[j];
|
||||
btScalar directionSign = (manifold->getBody0() == _ghostObject) ? btScalar(1.0) : btScalar(-1.0);
|
||||
for (int p = 0;p < manifold->getNumContacts(); p++) {
|
||||
const btManifoldPoint&pt = manifold->getContactPoint(p);
|
||||
|
||||
btScalar dist = pt.getDistance();
|
||||
|
||||
if (dist < 0.0) {
|
||||
bool useContact = true;
|
||||
btVector3 normal = pt.m_normalWorldOnB;
|
||||
normal *= directionSign; // always points from object to character
|
||||
|
||||
btScalar normalDotUp = normal.dot(_currentUp);
|
||||
if (normalDotUp < _maxSlopeCosine) {
|
||||
// this contact has a non-vertical normal... might need to ignored
|
||||
btVector3 collisionPoint;
|
||||
if (directionSign > 0.0) {
|
||||
collisionPoint = pt.getPositionWorldOnB();
|
||||
} else {
|
||||
collisionPoint = pt.getPositionWorldOnA();
|
||||
}
|
||||
|
||||
// we do math in frame where character base is origin
|
||||
btVector3 characterBase = currentPosition - (_radius + _halfHeight) * _currentUp;
|
||||
collisionPoint -= characterBase;
|
||||
btScalar collisionHeight = collisionPoint.dot(_currentUp);
|
||||
|
||||
if (collisionHeight < _lastStepUp) {
|
||||
// This contact is below the lastStepUp, so we ignore it for penetration resolution,
|
||||
// otherwise it may prevent the character from getting close enough to find any available
|
||||
// horizontal foothold that would allow it to climbe the ledge. In other words, we're
|
||||
// making the character's "feet" soft for collisions against steps, but not floors.
|
||||
useContact = false;
|
||||
}
|
||||
}
|
||||
if (useContact) {
|
||||
|
||||
if (dist < maxPen) {
|
||||
maxPen = dist;
|
||||
_floorNormal = normal;
|
||||
}
|
||||
const btScalar INCREMENTAL_RESOLUTION_FACTOR = 0.2f;
|
||||
_currentPosition += normal * (fabsf(dist) * INCREMENTAL_RESOLUTION_FACTOR);
|
||||
penetration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btTransform newTrans = _ghostObject->getWorldTransform();
|
||||
newTrans.setOrigin(_currentPosition);
|
||||
_ghostObject->setWorldTransform(newTrans);
|
||||
return penetration;
|
||||
}
|
||||
|
||||
|
||||
void CharacterController::scanDown(btCollisionWorld* world) {
|
||||
BT_PROFILE("scanDown");
|
||||
// we test with downward raycast and if we don't find floor close enough then turn on "hover"
|
||||
btKinematicClosestNotMeRayResultCallback callback(_ghostObject);
|
||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
btVector3 start = _currentPosition;
|
||||
const btScalar MAX_SCAN_HEIGHT = 20.0f + _halfHeight + _radius; // closest possible floor for disabling hover
|
||||
const btScalar MIN_HOVER_HEIGHT = 3.0f + _halfHeight + _radius; // distance to floor for enabling hover
|
||||
btVector3 end = start - MAX_SCAN_HEIGHT * _currentUp;
|
||||
|
||||
world->rayTest(start, end, callback);
|
||||
if (!callback.hasHit()) {
|
||||
_isHovering = true;
|
||||
} else if (_isHovering && callback.m_closestHitFraction * MAX_SCAN_HEIGHT < MIN_HOVER_HEIGHT) {
|
||||
_isHovering = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::stepUp(btCollisionWorld* world) {
|
||||
BT_PROFILE("stepUp");
|
||||
// phase 1: up
|
||||
|
||||
// compute start and end
|
||||
btTransform start, end;
|
||||
start.setIdentity();
|
||||
start.setOrigin(_currentPosition + _currentUp * (_convexShape->getMargin() + _addedMargin));
|
||||
|
||||
_targetPosition = _currentPosition + _currentUp * _stepUpHeight;
|
||||
end.setIdentity();
|
||||
end.setOrigin(_targetPosition);
|
||||
|
||||
// sweep up
|
||||
btVector3 sweepDirNegative = - _currentUp;
|
||||
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071));
|
||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
_ghostObject->convexSweepTest(_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
|
||||
|
||||
if (callback.hasHit()) {
|
||||
// we hit something, so zero our vertical velocity
|
||||
_verticalVelocity = 0.0f;
|
||||
_verticalOffset = 0.0f;
|
||||
|
||||
// Only modify the position if the hit was a slope and not a wall or ceiling.
|
||||
if (callback.m_hitNormalWorld.dot(_currentUp) > 0.0f) {
|
||||
_lastStepUp = _stepUpHeight * callback.m_closestHitFraction;
|
||||
_currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction);
|
||||
} else {
|
||||
_lastStepUp = _stepUpHeight;
|
||||
_currentPosition = _targetPosition;
|
||||
}
|
||||
} else {
|
||||
_currentPosition = _targetPosition;
|
||||
_lastStepUp = _stepUpHeight;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) {
|
||||
btVector3 movementDirection = _targetPosition - _currentPosition;
|
||||
btScalar movementLength = movementDirection.length();
|
||||
if (movementLength > SIMD_EPSILON) {
|
||||
movementDirection.normalize();
|
||||
|
||||
btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal);
|
||||
reflectDir.normalize();
|
||||
|
||||
btVector3 parallelDir, perpindicularDir;
|
||||
|
||||
parallelDir = parallelComponent(reflectDir, hitNormal);
|
||||
perpindicularDir = perpindicularComponent(reflectDir, hitNormal);
|
||||
|
||||
_targetPosition = _currentPosition;
|
||||
//if (tangentMag != 0.0) {
|
||||
if (0) {
|
||||
btVector3 parComponent = parallelDir * btScalar(tangentMag * movementLength);
|
||||
_targetPosition += parComponent;
|
||||
}
|
||||
|
||||
if (normalMag != 0.0) {
|
||||
btVector3 perpComponent = perpindicularDir * btScalar(normalMag * movementLength);
|
||||
_targetPosition += perpComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::stepForward(btCollisionWorld* collisionWorld, const btVector3& movement) {
|
||||
BT_PROFILE("stepForward");
|
||||
// phase 2: forward
|
||||
_targetPosition = _currentPosition + movement;
|
||||
|
||||
btTransform start, end;
|
||||
start.setIdentity();
|
||||
end.setIdentity();
|
||||
|
||||
/* TODO: experiment with this to see if we can use this to help direct motion when a floor is available
|
||||
if (_touchingContact) {
|
||||
if (_normalizedDirection.dot(_floorNormal) < btScalar(0.0)) {
|
||||
updateTargetPositionBasedOnCollision(_floorNormal, 1.0f, 1.0f);
|
||||
}
|
||||
}*/
|
||||
|
||||
// modify shape's margin for the sweeps
|
||||
btScalar margin = _convexShape->getMargin();
|
||||
_convexShape->setMargin(margin + _addedMargin);
|
||||
|
||||
const btScalar MIN_STEP_DISTANCE_SQUARED = 1.0e-6f;
|
||||
btVector3 step = _targetPosition - _currentPosition;
|
||||
btScalar stepLength2 = step.length2();
|
||||
int maxIter = 10;
|
||||
|
||||
while (stepLength2 > MIN_STEP_DISTANCE_SQUARED && maxIter-- > 0) {
|
||||
start.setOrigin(_currentPosition);
|
||||
end.setOrigin(_targetPosition);
|
||||
|
||||
// sweep forward
|
||||
btVector3 sweepDirNegative(_currentPosition - _targetPosition);
|
||||
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0));
|
||||
callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
_ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
||||
|
||||
if (callback.hasHit()) {
|
||||
// we hit soemthing!
|
||||
// Compute new target position by removing portion cut-off by collision, which will produce a new target
|
||||
// that is the closest approach of the the obstacle plane to the original target.
|
||||
step = _targetPosition - _currentPosition;
|
||||
btScalar stepDotNormal = step.dot(callback.m_hitNormalWorld); // we expect this dot to be negative
|
||||
step += (stepDotNormal * (1.0f - callback.m_closestHitFraction)) * callback.m_hitNormalWorld;
|
||||
_targetPosition = _currentPosition + step;
|
||||
|
||||
stepLength2 = step.length2();
|
||||
} else {
|
||||
// we swept to the end without hitting anything
|
||||
_currentPosition = _targetPosition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// restore shape's margin
|
||||
_convexShape->setMargin(margin);
|
||||
}
|
||||
|
||||
void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt) {
|
||||
BT_PROFILE("stepDown");
|
||||
// phase 3: down
|
||||
//
|
||||
// The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase.
|
||||
// If it hits a ledge then it stops otherwise it makes another sweep down in search of a floor within
|
||||
// reach of the character's feet.
|
||||
|
||||
// first sweep for ledge
|
||||
btVector3 step = (_verticalVelocity * dt - _lastStepUp) * _currentUp;
|
||||
|
||||
StepDownConvexResultCallback callback(_ghostObject,
|
||||
_currentUp,
|
||||
_currentPosition, step,
|
||||
_walkDirection,
|
||||
_maxSlopeCosine,
|
||||
_radius, _halfHeight);
|
||||
callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
btTransform start, end;
|
||||
start.setIdentity();
|
||||
end.setIdentity();
|
||||
|
||||
start.setOrigin(_currentPosition);
|
||||
_targetPosition = _currentPosition + step;
|
||||
end.setOrigin(_targetPosition);
|
||||
_ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
||||
|
||||
_isOnGround = false;
|
||||
if (callback.hasHit()) {
|
||||
_currentPosition += callback.m_closestHitFraction * step;
|
||||
_verticalVelocity = 0.0f;
|
||||
_verticalOffset = 0.0f;
|
||||
_isJumping = false;
|
||||
_isHovering = false;
|
||||
_isOnGround = true;
|
||||
} else if (!_isJumping) {
|
||||
// sweep again for floor within downStep threshold
|
||||
step = -_stepDownHeight * _currentUp;
|
||||
StepDownConvexResultCallback callback2 (_ghostObject,
|
||||
_currentUp,
|
||||
_currentPosition, step,
|
||||
_walkDirection,
|
||||
_maxSlopeCosine,
|
||||
_radius, _halfHeight);
|
||||
|
||||
callback2.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
callback2.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
_currentPosition = _targetPosition;
|
||||
_targetPosition = _currentPosition + step;
|
||||
|
||||
start.setOrigin(_currentPosition);
|
||||
end.setOrigin(_targetPosition);
|
||||
_ghostObject->convexSweepTest(_convexShape, start, end, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
||||
|
||||
if (callback2.hasHit()) {
|
||||
_currentPosition += callback2.m_closestHitFraction * step;
|
||||
_verticalVelocity = 0.0f;
|
||||
_verticalOffset = 0.0f;
|
||||
_isJumping = false;
|
||||
_isHovering = false;
|
||||
_isOnGround = true;
|
||||
} else {
|
||||
// nothing to step down on
|
||||
_lastStepUp = 0.0f;
|
||||
}
|
||||
} else {
|
||||
// we're jumping, and didn't hit anything, so our target position is where we would have fallen to
|
||||
_currentPosition = _targetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setWalkDirection(const btVector3& walkDirection) {
|
||||
// This must be implemented to satisfy base-class interface but does nothing.
|
||||
// Use setVelocityForTimeInterval() instead.
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval) {
|
||||
_walkDirection = velocity;
|
||||
_normalizedDirection = getNormalizedVector(_walkDirection);
|
||||
_velocityTimeInterval += timeInterval;
|
||||
}
|
||||
|
||||
void CharacterController::reset(btCollisionWorld* collisionWorld) {
|
||||
_verticalVelocity = 0.0;
|
||||
_verticalOffset = 0.0;
|
||||
_isOnGround = false;
|
||||
_isJumping = false;
|
||||
_isHovering = true;
|
||||
_walkDirection.setValue(0,0,0);
|
||||
_velocityTimeInterval = 0.0;
|
||||
|
||||
//clear pair cache
|
||||
btHashedOverlappingPairCache *cache = _ghostObject->getOverlappingPairCache();
|
||||
while (cache->getOverlappingPairArray().size() > 0) {
|
||||
cache->removeOverlappingPair(cache->getOverlappingPairArray()[0].m_pProxy0,
|
||||
cache->getOverlappingPairArray()[0].m_pProxy1,
|
||||
collisionWorld->getDispatcher());
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::warp(const btVector3& origin) {
|
||||
btTransform xform;
|
||||
xform.setIdentity();
|
||||
xform.setOrigin(origin);
|
||||
_ghostObject->setWorldTransform(xform);
|
||||
}
|
||||
|
||||
|
||||
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
||||
BT_PROFILE("preStep");
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
int numPenetrationLoops = 0;
|
||||
_touchingContact = false;
|
||||
while (recoverFromPenetration(collisionWorld)) {
|
||||
numPenetrationLoops++;
|
||||
_touchingContact = true;
|
||||
if (numPenetrationLoops > 4) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// the CharacterController algorithm can only change the position,
|
||||
// so we don't bother to pull the rotation out of the transform
|
||||
const btTransform& transform = _ghostObject->getWorldTransform();
|
||||
_currentPosition = transform.getOrigin();
|
||||
}
|
||||
|
||||
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
|
||||
BT_PROFILE("playerStep");
|
||||
if (!_enabled) {
|
||||
return; // no motion
|
||||
}
|
||||
|
||||
// Update fall velocity.
|
||||
if (_isHovering) {
|
||||
const btScalar MIN_HOVER_VERTICAL_VELOCITY = 0.1f;
|
||||
if (fabsf(_verticalVelocity) < MIN_HOVER_VERTICAL_VELOCITY) {
|
||||
_verticalVelocity = 0.0f;
|
||||
} else {
|
||||
const btScalar HOVER_RELAXATION_TIMESCALE = 0.8f;
|
||||
_verticalVelocity *= (1.0f - dt / HOVER_RELAXATION_TIMESCALE);
|
||||
}
|
||||
} else {
|
||||
_verticalVelocity -= _gravity * dt;
|
||||
if (_verticalVelocity > _jumpSpeed) {
|
||||
_verticalVelocity = _jumpSpeed;
|
||||
} else if (_verticalVelocity < -_maxFallSpeed) {
|
||||
_verticalVelocity = -_maxFallSpeed;
|
||||
}
|
||||
}
|
||||
_verticalOffset = _verticalVelocity * dt;
|
||||
|
||||
btTransform xform;
|
||||
xform = _ghostObject->getWorldTransform();
|
||||
|
||||
// the algorithm is as follows:
|
||||
// (1) step the character up a little bit so that its forward step doesn't hit the floor
|
||||
// (2) step the character forward
|
||||
// (3) step the character down looking for new ledges, the original floor, or a floor one step below where we started
|
||||
|
||||
scanDown(collisionWorld);
|
||||
|
||||
stepUp(collisionWorld);
|
||||
|
||||
// compute substep and decrement total interval
|
||||
btScalar dtMoving = (dt < _velocityTimeInterval) ? dt : _velocityTimeInterval;
|
||||
_velocityTimeInterval -= dt;
|
||||
_stepDt += dt;
|
||||
|
||||
// stepForward substep
|
||||
btVector3 move = _walkDirection * dtMoving;
|
||||
stepForward(collisionWorld, move);
|
||||
|
||||
stepDown(collisionWorld, dt);
|
||||
|
||||
xform.setOrigin(_currentPosition);
|
||||
_ghostObject->setWorldTransform(xform);
|
||||
}
|
||||
|
||||
void CharacterController::setMaxFallSpeed(btScalar speed) {
|
||||
_maxFallSpeed = speed;
|
||||
}
|
||||
|
||||
void CharacterController::setJumpSpeed(btScalar jumpSpeed) {
|
||||
_jumpSpeed = jumpSpeed;
|
||||
}
|
||||
|
||||
void CharacterController::setMaxJumpHeight(btScalar maxJumpHeight) {
|
||||
_maxJumpHeight = maxJumpHeight;
|
||||
}
|
||||
|
||||
bool CharacterController::canJump() const {
|
||||
return _isOnGround;
|
||||
}
|
||||
|
||||
void CharacterController::jump() {
|
||||
_pendingFlags |= PENDING_FLAG_JUMP;
|
||||
|
||||
// check for case where user is holding down "jump" key...
|
||||
// we'll eventually tansition to "hover"
|
||||
if (!_isHovering) {
|
||||
if (!_isJumping) {
|
||||
_jumpToHoverStart = usecTimestampNow();
|
||||
} else {
|
||||
quint64 now = usecTimestampNow();
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = USECS_PER_SECOND;
|
||||
if (now - _jumpToHoverStart > JUMP_TO_HOVER_PERIOD) {
|
||||
_isHovering = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setGravity(btScalar gravity) {
|
||||
_gravity = gravity;
|
||||
}
|
||||
|
||||
btScalar CharacterController::getGravity() const {
|
||||
return _gravity;
|
||||
}
|
||||
|
||||
void CharacterController::setMaxSlope(btScalar slopeRadians) {
|
||||
_maxSlopeRadians = slopeRadians;
|
||||
_maxSlopeCosine = btCos(slopeRadians);
|
||||
}
|
||||
|
||||
btScalar CharacterController::getMaxSlope() const {
|
||||
return _maxSlopeRadians;
|
||||
}
|
||||
|
||||
bool CharacterController::onGround() const {
|
||||
return _isOnGround;
|
||||
}
|
||||
|
||||
void CharacterController::debugDraw(btIDebugDraw* debugDrawer) {
|
||||
}
|
||||
|
||||
void CharacterController::setUpInterpolate(bool value) {
|
||||
// This method is required by btCharacterControllerInterface, but it does nothing.
|
||||
// What it used to do was determine whether stepUp() would: stop where it hit the ceiling
|
||||
// (interpolate = true, and now default behavior) or happily penetrate objects above the avatar.
|
||||
}
|
||||
|
||||
void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) {
|
||||
_boxScale = scale;
|
||||
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
float radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
float halfHeight = 0.5f * _boxScale.y - radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (halfHeight < MIN_HALF_HEIGHT) {
|
||||
halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
|
||||
// compare dimensions
|
||||
float radiusDelta = glm::abs(radius - _radius);
|
||||
float heightDelta = glm::abs(halfHeight - _halfHeight);
|
||||
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
|
||||
// shape hasn't changed --> nothing to do
|
||||
} else {
|
||||
if (_dynamicsWorld) {
|
||||
// must REMOVE from world prior to shape update
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
|
||||
// only need to ADD back when we happen to be enabled
|
||||
if (_enabled) {
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
}
|
||||
|
||||
// it's ok to change offset immediately -- there are no thread safety issues here
|
||||
_shapeLocalOffset = corner + 0.5f * _boxScale;
|
||||
}
|
||||
|
||||
bool CharacterController::needsAddition() const {
|
||||
return (bool)(_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION);
|
||||
}
|
||||
|
||||
bool CharacterController::needsRemoval() const {
|
||||
return (bool)(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION);
|
||||
}
|
||||
|
||||
void CharacterController::setEnabled(bool enabled) {
|
||||
if (enabled != _enabled) {
|
||||
if (enabled) {
|
||||
// Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit.
|
||||
// Setting the ADD bit here works for all cases so we don't even bother checking other bits.
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
_isHovering = true;
|
||||
_verticalVelocity = 0.0f;
|
||||
} else {
|
||||
if (_dynamicsWorld) {
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
_isOnGround = false;
|
||||
}
|
||||
_enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
if (_dynamicsWorld != world) {
|
||||
if (_dynamicsWorld) {
|
||||
if (_ghostObject) {
|
||||
_dynamicsWorld->removeCollisionObject(_ghostObject);
|
||||
_dynamicsWorld->removeAction(this);
|
||||
}
|
||||
_dynamicsWorld = NULL;
|
||||
}
|
||||
if (world && _ghostObject) {
|
||||
_dynamicsWorld = world;
|
||||
_pendingFlags &= ~ PENDING_FLAG_JUMP;
|
||||
_dynamicsWorld->addCollisionObject(_ghostObject,
|
||||
btBroadphaseProxy::CharacterFilter,
|
||||
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||
_dynamicsWorld->addAction(this);
|
||||
reset(_dynamicsWorld);
|
||||
}
|
||||
}
|
||||
if (_dynamicsWorld) {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
// shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
} else {
|
||||
_pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
} else {
|
||||
_pendingFlags &= ~ PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::updateShapeIfNecessary() {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
assert(!(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION));
|
||||
_pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE;
|
||||
// make sure there is NO pending removal from simulation at this point
|
||||
// (don't want to delete _ghostObject out from under the simulation)
|
||||
// delete shape and GhostObject
|
||||
delete _ghostObject;
|
||||
_ghostObject = NULL;
|
||||
delete _convexShape;
|
||||
_convexShape = NULL;
|
||||
|
||||
// compute new dimensions from avatar's bounding box
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
_radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
_halfHeight = 0.5f * _boxScale.y - _radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (_halfHeight < MIN_HALF_HEIGHT) {
|
||||
_halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
// NOTE: _shapeLocalOffset is already computed
|
||||
|
||||
if (_radius > 0.0f) {
|
||||
// create new ghost
|
||||
_ghostObject = new btPairCachingGhostObject();
|
||||
_ghostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
|
||||
glmToBullet(_avatarData->getPosition())));
|
||||
// stepHeight affects the heights of ledges that the character can ascend
|
||||
_stepUpHeight = _radius + 0.25f * _halfHeight + 0.1f;
|
||||
_stepDownHeight = _radius;
|
||||
|
||||
// create new shape
|
||||
_convexShape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
|
||||
_ghostObject->setCollisionShape(_convexShape);
|
||||
_ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
||||
} else {
|
||||
// TODO: handle this failure case
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::preSimulation(btScalar timeStep) {
|
||||
BT_PROFILE("preSimulation");
|
||||
if (_enabled && _dynamicsWorld) {
|
||||
glm::quat rotation = _avatarData->getOrientation();
|
||||
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset;
|
||||
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
|
||||
|
||||
_ghostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position)));
|
||||
setVelocityForTimeInterval(walkVelocity, timeStep);
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_pendingFlags &= ~ PENDING_FLAG_JUMP;
|
||||
if (canJump()) {
|
||||
_verticalVelocity = _jumpSpeed;
|
||||
_isJumping = true;
|
||||
}
|
||||
}
|
||||
// remember last position so we can throttle the total motion from the next step
|
||||
_lastPosition = position;
|
||||
_stepDt = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::postSimulation() {
|
||||
BT_PROFILE("postSimulation");
|
||||
if (_enabled && _ghostObject) {
|
||||
const btTransform& avatarTransform = _ghostObject->getWorldTransform();
|
||||
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
|
||||
glm::vec3 position = bulletToGLM(avatarTransform.getOrigin());
|
||||
|
||||
// cap the velocity of the step so that the character doesn't POP! so hard on steps
|
||||
glm::vec3 finalStep = position - _lastPosition;
|
||||
btVector3 finalVelocity = _walkDirection;
|
||||
finalVelocity += _verticalVelocity * _currentUp;
|
||||
const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec
|
||||
btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt;
|
||||
btScalar stepLength = glm::length(finalStep);
|
||||
if (stepLength > maxStepLength) {
|
||||
position = _lastPosition + (maxStepLength / stepLength) * finalStep;
|
||||
// NOTE: we don't need to move ghostObject to throttled position unless
|
||||
// we want to support do async ray-traces/collision-queries against character
|
||||
}
|
||||
|
||||
_avatarData->setOrientation(rotation);
|
||||
_avatarData->setPosition(position - rotation * _shapeLocalOffset);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com
|
||||
2015.03.25 -- modified by Andrew Meadows andrew@highfidelity.io
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but
|
||||
is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef hifi_CharacterController_h
|
||||
#define hifi_CharacterController_h
|
||||
|
||||
#include <AvatarData.h>
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
||||
#include <BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h>
|
||||
|
||||
|
||||
class btConvexShape;
|
||||
class btCollisionWorld;
|
||||
class btCollisionDispatcher;
|
||||
class btPairCachingGhostObject;
|
||||
|
||||
///CharacterController is a custom version of btKinematicCharacterController
|
||||
|
||||
///btKinematicCharacterController is an object that supports a sliding motion in a world.
|
||||
///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations.
|
||||
///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user.
|
||||
|
||||
|
||||
ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface
|
||||
{
|
||||
protected:
|
||||
///this is the desired walk direction, set by the user
|
||||
btVector3 _walkDirection;
|
||||
btVector3 _normalizedDirection;
|
||||
|
||||
//some internal variables
|
||||
btVector3 _currentPosition;
|
||||
btVector3 _currentUp;
|
||||
btVector3 _targetPosition;
|
||||
glm::vec3 _lastPosition;
|
||||
btVector3 _floorNormal; // points from object to character
|
||||
|
||||
glm::vec3 _shapeLocalOffset;
|
||||
glm::vec3 _boxScale; // used to compute capsule shape
|
||||
|
||||
AvatarData* _avatarData = NULL;
|
||||
btPairCachingGhostObject* _ghostObject = NULL;
|
||||
|
||||
btConvexShape* _convexShape;//is also in _ghostObject, but it needs to be convex, so we store it here to avoid upcast
|
||||
btScalar _radius;
|
||||
btScalar _halfHeight;
|
||||
|
||||
btScalar _verticalVelocity;
|
||||
btScalar _verticalOffset; // fall distance from velocity this frame
|
||||
btScalar _maxFallSpeed;
|
||||
btScalar _jumpSpeed;
|
||||
btScalar _maxJumpHeight;
|
||||
btScalar _maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
|
||||
btScalar _maxSlopeCosine; // Cosine equivalent of _maxSlopeRadians (calculated once when set, for optimization)
|
||||
btScalar _gravity;
|
||||
|
||||
btScalar _stepUpHeight; // height of stepUp prior to stepForward
|
||||
btScalar _stepDownHeight; // height of stepDown
|
||||
|
||||
btScalar _addedMargin;//@todo: remove this and fix the code
|
||||
|
||||
btScalar _lastStepUp;
|
||||
|
||||
///keep track of the contact manifolds
|
||||
btManifoldArray _manifoldArray;
|
||||
|
||||
bool _touchingContact;
|
||||
|
||||
bool _enabled;
|
||||
bool _isOnGround;
|
||||
bool _isJumping;
|
||||
bool _isHovering;
|
||||
quint64 _jumpToHoverStart;
|
||||
btScalar _velocityTimeInterval;
|
||||
btScalar _stepDt;
|
||||
uint32_t _pendingFlags;
|
||||
|
||||
btDynamicsWorld* _dynamicsWorld = NULL;
|
||||
|
||||
btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal);
|
||||
btVector3 parallelComponent(const btVector3& direction, const btVector3& normal);
|
||||
btVector3 perpindicularComponent(const btVector3& direction, const btVector3& normal);
|
||||
|
||||
bool recoverFromPenetration(btCollisionWorld* collisionWorld);
|
||||
void scanDown(btCollisionWorld* collisionWorld);
|
||||
void stepUp(btCollisionWorld* collisionWorld);
|
||||
void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0));
|
||||
void stepForward(btCollisionWorld* collisionWorld, const btVector3& walkMove);
|
||||
void stepDown(btCollisionWorld* collisionWorld, btScalar dt);
|
||||
void createShapeAndGhost();
|
||||
public:
|
||||
|
||||
BT_DECLARE_ALIGNED_ALLOCATOR();
|
||||
|
||||
CharacterController(AvatarData* avatarData);
|
||||
~CharacterController();
|
||||
|
||||
|
||||
///btActionInterface interface
|
||||
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) {
|
||||
preStep(collisionWorld);
|
||||
playerStep(collisionWorld, deltaTime);
|
||||
}
|
||||
|
||||
///btActionInterface interface
|
||||
void debugDraw(btIDebugDraw* debugDrawer);
|
||||
|
||||
/// This should probably be called setPositionIncrementPerSimulatorStep.
|
||||
/// This is neither a direction nor a velocity, but the amount to
|
||||
/// increment the position each simulation iteration, regardless
|
||||
/// of dt.
|
||||
/// This call will reset any velocity set by setVelocityForTimeInterval().
|
||||
virtual void setWalkDirection(const btVector3& walkDirection);
|
||||
|
||||
/// Caller provides a velocity with which the character should move for
|
||||
/// the given time period. After the time period, velocity is reset
|
||||
/// to zero.
|
||||
/// This call will reset any walk direction set by setWalkDirection().
|
||||
/// Negative time intervals will result in no motion.
|
||||
virtual void setVelocityForTimeInterval(const btVector3& velocity,
|
||||
btScalar timeInterval);
|
||||
|
||||
virtual void reset(btCollisionWorld* collisionWorld );
|
||||
virtual void warp(const btVector3& origin);
|
||||
|
||||
virtual void preStep(btCollisionWorld* collisionWorld);
|
||||
virtual void playerStep(btCollisionWorld* collisionWorld, btScalar dt);
|
||||
|
||||
virtual bool canJump() const;
|
||||
virtual void jump();
|
||||
virtual bool onGround() const;
|
||||
|
||||
void setMaxFallSpeed(btScalar speed);
|
||||
void setJumpSpeed(btScalar jumpSpeed);
|
||||
void setMaxJumpHeight(btScalar maxJumpHeight);
|
||||
|
||||
void setGravity(btScalar gravity);
|
||||
btScalar getGravity() const;
|
||||
|
||||
/// The max slope determines the maximum angle that the controller can walk up.
|
||||
/// The slope angle is measured in radians.
|
||||
void setMaxSlope(btScalar slopeRadians);
|
||||
btScalar getMaxSlope() const;
|
||||
|
||||
btPairCachingGhostObject* getGhostObject();
|
||||
|
||||
void setUpInterpolate(bool value);
|
||||
|
||||
bool needsRemoval() const;
|
||||
bool needsAddition() const;
|
||||
void setEnabled(bool enabled);
|
||||
bool isEnabled() const { return _enabled; }
|
||||
void setDynamicsWorld(btDynamicsWorld* world);
|
||||
|
||||
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
bool needsShapeUpdate() const;
|
||||
void updateShapeIfNecessary();
|
||||
|
||||
void preSimulation(btScalar timeStep);
|
||||
void postSimulation();
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterController_h
|
413
libraries/physics/src/DynamicCharacterController.cpp
Normal file
413
libraries/physics/src/DynamicCharacterController.cpp
Normal file
|
@ -0,0 +1,413 @@
|
|||
#include <BulletCollision/CollisionShapes/btMultiSphereShape.h>
|
||||
#include <BulletDynamics/Dynamics/btRigidBody.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
#include <LinearMath/btDefaultMotionState.h>
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "DynamicCharacterController.h"
|
||||
|
||||
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
|
||||
const float DEFAULT_GRAVITY = -5.0f;
|
||||
const float TERMINAL_VELOCITY = 55.0f;
|
||||
const float JUMP_SPEED = 3.5f;
|
||||
|
||||
const float MAX_FALL_HEIGHT = 20.0f;
|
||||
const float MIN_HOVER_HEIGHT = 3.0f;
|
||||
|
||||
const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
|
||||
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
|
||||
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
|
||||
const uint32_t PENDING_FLAG_JUMP = 1U << 3;
|
||||
|
||||
// TODO: improve walking up steps
|
||||
// TODO: make avatars able to walk up and down steps/slopes
|
||||
// TODO: make avatars stand on steep slope
|
||||
// TODO: make avatars not snag on low ceilings
|
||||
|
||||
// helper class for simple ray-traces from character
|
||||
class ClosestNotMe : public btCollisionWorld::ClosestRayResultCallback {
|
||||
public:
|
||||
ClosestNotMe(btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
|
||||
_me = me;
|
||||
}
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) {
|
||||
if (rayResult.m_collisionObject == _me) {
|
||||
return 1.0f;
|
||||
}
|
||||
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace
|
||||
);
|
||||
}
|
||||
protected:
|
||||
btRigidBody* _me;
|
||||
};
|
||||
|
||||
DynamicCharacterController::DynamicCharacterController(AvatarData* avatarData) {
|
||||
_halfHeight = 1.0f;
|
||||
_shape = NULL;
|
||||
_rigidBody = NULL;
|
||||
|
||||
assert(avatarData);
|
||||
_avatarData = avatarData;
|
||||
|
||||
_enabled = false;
|
||||
|
||||
_floorDistance = MAX_FALL_HEIGHT;
|
||||
|
||||
_walkVelocity.setValue(0.0f,0.0f,0.0f);
|
||||
_jumpSpeed = JUMP_SPEED;
|
||||
_isOnGround = false;
|
||||
_isJumping = false;
|
||||
_isFalling = false;
|
||||
_isHovering = true;
|
||||
_isPushingUp = false;
|
||||
_jumpToHoverStart = 0;
|
||||
|
||||
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
||||
updateShapeIfNecessary();
|
||||
}
|
||||
|
||||
DynamicCharacterController::~DynamicCharacterController() {
|
||||
}
|
||||
|
||||
// virtual
|
||||
void DynamicCharacterController::setWalkDirection(const btVector3& walkDirection) {
|
||||
// do nothing -- walkVelocity is upated in preSimulation()
|
||||
//_walkVelocity = walkDirection;
|
||||
}
|
||||
|
||||
void DynamicCharacterController::preStep(btCollisionWorld* collisionWorld) {
|
||||
// trace a ray straight down to see if we're standing on the ground
|
||||
const btTransform& xform = _rigidBody->getWorldTransform();
|
||||
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = xform.getOrigin() - _halfHeight * _currentUp;
|
||||
|
||||
// rayEnd is some short distance outside bottom sphere
|
||||
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
|
||||
btScalar rayLength = _radius + FLOOR_PROXIMITY_THRESHOLD;
|
||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||
|
||||
// scan down for nearby floor
|
||||
ClosestNotMe rayCallback(_rigidBody);
|
||||
rayCallback.m_closestHitFraction = 1.0f;
|
||||
collisionWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
if (rayCallback.hasHit()) {
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::playerStep(btCollisionWorld* dynaWorld,btScalar dt) {
|
||||
btVector3 actualVelocity = _rigidBody->getLinearVelocity();
|
||||
btScalar actualSpeed = actualVelocity.length();
|
||||
|
||||
btVector3 desiredVelocity = _walkVelocity;
|
||||
btScalar desiredSpeed = desiredVelocity.length();
|
||||
|
||||
const btScalar MIN_UP_PUSH = 0.1f;
|
||||
if (desiredVelocity.dot(_currentUp) < MIN_UP_PUSH) {
|
||||
_isPushingUp = false;
|
||||
}
|
||||
|
||||
const btScalar MIN_SPEED = 0.001f;
|
||||
if (_isHovering) {
|
||||
if (desiredSpeed < MIN_SPEED) {
|
||||
if (actualSpeed < MIN_SPEED) {
|
||||
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
|
||||
btScalar tau = glm::max(dt / HOVER_BRAKING_TIMESCALE, 1.0f);
|
||||
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
|
||||
}
|
||||
} else {
|
||||
const btScalar HOVER_ACCELERATION_TIMESCALE = 0.1f;
|
||||
btScalar tau = dt / HOVER_ACCELERATION_TIMESCALE;
|
||||
_rigidBody->setLinearVelocity(actualVelocity - tau * (actualVelocity - desiredVelocity));
|
||||
}
|
||||
} else {
|
||||
if (onGround()) {
|
||||
// walking on ground
|
||||
if (desiredSpeed < MIN_SPEED) {
|
||||
if (actualSpeed < MIN_SPEED) {
|
||||
_rigidBody->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
const btScalar HOVER_BRAKING_TIMESCALE = 0.1f;
|
||||
btScalar tau = dt / HOVER_BRAKING_TIMESCALE;
|
||||
_rigidBody->setLinearVelocity((1.0f - tau) * actualVelocity);
|
||||
}
|
||||
} else {
|
||||
// TODO: modify desiredVelocity using floor normal
|
||||
const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f;
|
||||
btScalar tau = dt / WALK_ACCELERATION_TIMESCALE;
|
||||
btVector3 velocityCorrection = tau * (desiredVelocity - actualVelocity);
|
||||
// subtract vertical component
|
||||
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
|
||||
_rigidBody->setLinearVelocity(actualVelocity + velocityCorrection);
|
||||
}
|
||||
} else {
|
||||
// transitioning to flying
|
||||
btVector3 velocityCorrection = desiredVelocity - actualVelocity;
|
||||
const btScalar FLY_ACCELERATION_TIMESCALE = 0.2f;
|
||||
btScalar tau = dt / FLY_ACCELERATION_TIMESCALE;
|
||||
if (!_isPushingUp) {
|
||||
// actually falling --> compute a different velocity attenuation factor
|
||||
const btScalar FALL_ACCELERATION_TIMESCALE = 2.0f;
|
||||
tau = dt / FALL_ACCELERATION_TIMESCALE;
|
||||
// zero vertical component
|
||||
velocityCorrection -= velocityCorrection.dot(_currentUp) * _currentUp;
|
||||
}
|
||||
_rigidBody->setLinearVelocity(actualVelocity + tau * velocityCorrection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::jump() {
|
||||
// check for case where user is holding down "jump" key...
|
||||
// we'll eventually tansition to "hover"
|
||||
if (!_isJumping) {
|
||||
if (!_isHovering) {
|
||||
_jumpToHoverStart = usecTimestampNow();
|
||||
_pendingFlags |= PENDING_FLAG_JUMP;
|
||||
}
|
||||
} else {
|
||||
quint64 now = usecTimestampNow();
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 75 * (USECS_PER_SECOND / 100);
|
||||
if (now - _jumpToHoverStart > JUMP_TO_HOVER_PERIOD) {
|
||||
_isPushingUp = true;
|
||||
setHovering(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicCharacterController::onGround() const {
|
||||
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
|
||||
return _floorDistance < FLOOR_PROXIMITY_THRESHOLD;
|
||||
}
|
||||
|
||||
void DynamicCharacterController::setHovering(bool hover) {
|
||||
if (hover != _isHovering) {
|
||||
_isHovering = hover;
|
||||
_isJumping = false;
|
||||
|
||||
if (_rigidBody) {
|
||||
if (hover) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
_rigidBody->setGravity(DEFAULT_GRAVITY * _currentUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) {
|
||||
_boxScale = scale;
|
||||
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
float radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
float halfHeight = 0.5f * _boxScale.y - radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (halfHeight < MIN_HALF_HEIGHT) {
|
||||
halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
|
||||
// compare dimensions
|
||||
float radiusDelta = glm::abs(radius - _radius);
|
||||
float heightDelta = glm::abs(halfHeight - _halfHeight);
|
||||
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
|
||||
// shape hasn't changed --> nothing to do
|
||||
} else {
|
||||
if (_dynamicsWorld) {
|
||||
// must REMOVE from world prior to shape update
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
|
||||
// only need to ADD back when we happen to be enabled
|
||||
if (_enabled) {
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
}
|
||||
|
||||
// it's ok to change offset immediately -- there are no thread safety issues here
|
||||
_shapeLocalOffset = corner + 0.5f * _boxScale;
|
||||
}
|
||||
|
||||
bool DynamicCharacterController::needsRemoval() const {
|
||||
return (bool)(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION);
|
||||
}
|
||||
|
||||
bool DynamicCharacterController::needsAddition() const {
|
||||
return (bool)(_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION);
|
||||
}
|
||||
|
||||
void DynamicCharacterController::setEnabled(bool enabled) {
|
||||
if (enabled != _enabled) {
|
||||
if (enabled) {
|
||||
// Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit.
|
||||
// Setting the ADD bit here works for all cases so we don't even bother checking other bits.
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
setHovering(true);
|
||||
} else {
|
||||
if (_dynamicsWorld) {
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
_isOnGround = false;
|
||||
}
|
||||
_enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
if (_dynamicsWorld != world) {
|
||||
if (_dynamicsWorld) {
|
||||
if (_rigidBody) {
|
||||
_dynamicsWorld->removeRigidBody(_rigidBody);
|
||||
_dynamicsWorld->removeAction(this);
|
||||
}
|
||||
_dynamicsWorld = NULL;
|
||||
}
|
||||
if (world && _rigidBody) {
|
||||
_dynamicsWorld = world;
|
||||
_pendingFlags &= ~ PENDING_FLAG_JUMP;
|
||||
_dynamicsWorld->addRigidBody(_rigidBody);
|
||||
_dynamicsWorld->addAction(this);
|
||||
//reset(_dynamicsWorld);
|
||||
}
|
||||
}
|
||||
if (_dynamicsWorld) {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
// shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
} else {
|
||||
_pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
} else {
|
||||
_pendingFlags &= ~ PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::updateShapeIfNecessary() {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
// make sure there is NO pending removal from simulation at this point
|
||||
// (don't want to delete _rigidBody out from under the simulation)
|
||||
assert(!(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION));
|
||||
_pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE;
|
||||
// delete shape and RigidBody
|
||||
delete _rigidBody;
|
||||
_rigidBody = NULL;
|
||||
delete _shape;
|
||||
_shape = NULL;
|
||||
|
||||
// compute new dimensions from avatar's bounding box
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
_radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
_halfHeight = 0.5f * _boxScale.y - _radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (_halfHeight < MIN_HALF_HEIGHT) {
|
||||
_halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
// NOTE: _shapeLocalOffset is already computed
|
||||
|
||||
if (_radius > 0.0f) {
|
||||
// create new shape
|
||||
_shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
|
||||
|
||||
// create new body
|
||||
float mass = 1.0f;
|
||||
btVector3 inertia(1.0f, 1.0f, 1.0f);
|
||||
_rigidBody = new btRigidBody(mass, NULL, _shape, inertia);
|
||||
_rigidBody->setSleepingThresholds (0.0f, 0.0f);
|
||||
_rigidBody->setAngularFactor (0.0f);
|
||||
_rigidBody->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
|
||||
glmToBullet(_avatarData->getPosition())));
|
||||
if (_isHovering) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
_rigidBody->setGravity(DEFAULT_GRAVITY * _currentUp);
|
||||
}
|
||||
//_rigidBody->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
||||
} else {
|
||||
// TODO: handle this failure case
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::updateUpAxis(const glm::quat& rotation) {
|
||||
btVector3 oldUp = _currentUp;
|
||||
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
if (!_isHovering) {
|
||||
const btScalar MIN_UP_ERROR = 0.01f;
|
||||
if (oldUp.distance(_currentUp) > MIN_UP_ERROR) {
|
||||
_rigidBody->setGravity(DEFAULT_GRAVITY * _currentUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::preSimulation(btScalar timeStep) {
|
||||
if (_enabled && _dynamicsWorld) {
|
||||
glm::quat rotation = _avatarData->getOrientation();
|
||||
|
||||
// TODO: update gravity if up has changed
|
||||
updateUpAxis(rotation);
|
||||
|
||||
glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset;
|
||||
_rigidBody->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position)));
|
||||
|
||||
// the rotation is dictated by AvatarData
|
||||
btTransform xform = _rigidBody->getWorldTransform();
|
||||
xform.setRotation(glmToBullet(rotation));
|
||||
_rigidBody->setWorldTransform(xform);
|
||||
|
||||
// scan for distant floor
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = xform.getOrigin() - _halfHeight * _currentUp;
|
||||
|
||||
// rayEnd is straight down MAX_FALL_HEIGHT
|
||||
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||
|
||||
ClosestNotMe rayCallback(_rigidBody);
|
||||
rayCallback.m_closestHitFraction = 1.0f;
|
||||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
if (rayCallback.hasHit()) {
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
|
||||
const btScalar MIN_HOVER_HEIGHT = 3.0f;
|
||||
if (_isHovering && _floorDistance < MIN_HOVER_HEIGHT && !_isPushingUp) {
|
||||
setHovering(false);
|
||||
}
|
||||
// TODO: use collision events rather than ray-trace test to disable jumping
|
||||
const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
|
||||
if (_floorDistance < JUMP_PROXIMITY_THRESHOLD) {
|
||||
_isJumping = false;
|
||||
}
|
||||
} else {
|
||||
_floorDistance = FLT_MAX;
|
||||
setHovering(true);
|
||||
}
|
||||
|
||||
_walkVelocity = glmToBullet(_avatarData->getVelocity());
|
||||
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_pendingFlags &= ~ PENDING_FLAG_JUMP;
|
||||
if (onGround()) {
|
||||
_isJumping = true;
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
velocity += _jumpSpeed * _currentUp;
|
||||
_rigidBody->setLinearVelocity(velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicCharacterController::postSimulation() {
|
||||
if (_enabled && _rigidBody) {
|
||||
const btTransform& avatarTransform = _rigidBody->getWorldTransform();
|
||||
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
|
||||
glm::vec3 position = bulletToGLM(avatarTransform.getOrigin());
|
||||
|
||||
_avatarData->setOrientation(rotation);
|
||||
_avatarData->setPosition(position - rotation * _shapeLocalOffset);
|
||||
}
|
||||
}
|
||||
|
95
libraries/physics/src/DynamicCharacterController.h
Normal file
95
libraries/physics/src/DynamicCharacterController.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
#ifndef hifi_DynamicCharacterController_h
|
||||
#define hifi_DynamicCharacterController_h
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
||||
|
||||
#include <AvatarData.h>
|
||||
|
||||
class btCollisionShape;
|
||||
class btRigidBody;
|
||||
class btCollisionWorld;
|
||||
|
||||
const int NUM_CHARACTER_CONTROLLER_RAYS = 2;
|
||||
|
||||
///DynamicCharacterController is obsolete/unsupported at the moment
|
||||
class DynamicCharacterController : public btCharacterControllerInterface
|
||||
{
|
||||
protected:
|
||||
btScalar _halfHeight;
|
||||
btScalar _radius;
|
||||
btCollisionShape* _shape;
|
||||
btRigidBody* _rigidBody;
|
||||
|
||||
btVector3 _currentUp;
|
||||
|
||||
btScalar _floorDistance;
|
||||
|
||||
btVector3 _walkVelocity;
|
||||
btScalar _gravity;
|
||||
|
||||
glm::vec3 _shapeLocalOffset;
|
||||
glm::vec3 _boxScale; // used to compute capsule shape
|
||||
AvatarData* _avatarData = NULL;
|
||||
|
||||
bool _enabled;
|
||||
bool _isOnGround;
|
||||
bool _isJumping;
|
||||
bool _isFalling;
|
||||
bool _isHovering;
|
||||
bool _isPushingUp;
|
||||
quint64 _jumpToHoverStart;
|
||||
uint32_t _pendingFlags;
|
||||
|
||||
btDynamicsWorld* _dynamicsWorld = NULL;
|
||||
|
||||
btScalar _jumpSpeed;
|
||||
|
||||
public:
|
||||
DynamicCharacterController(AvatarData* avatarData);
|
||||
~DynamicCharacterController ();
|
||||
|
||||
virtual void setWalkDirection(const btVector3& walkDirection);
|
||||
virtual void setVelocityForTimeInterval(const btVector3 &velocity, btScalar timeInterval) { assert(false); }
|
||||
|
||||
// TODO: implement these when needed
|
||||
virtual void reset(btCollisionWorld* collisionWorld) { }
|
||||
virtual void warp(const btVector3& origin) { }
|
||||
virtual void debugDraw(btIDebugDraw* debugDrawer) { }
|
||||
virtual void setUpInterpolate(bool value) { }
|
||||
|
||||
btCollisionObject* getCollisionObject() { return _rigidBody; }
|
||||
|
||||
///btActionInterface interface
|
||||
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) {
|
||||
preStep(collisionWorld);
|
||||
playerStep(collisionWorld, deltaTime);
|
||||
}
|
||||
|
||||
virtual void preStep(btCollisionWorld* collisionWorld);
|
||||
virtual void playerStep(btCollisionWorld* collisionWorld, btScalar dt);
|
||||
|
||||
virtual bool canJump() const { assert(false); return false; } // never call this
|
||||
virtual void jump(); // call this every frame the jump button is pressed
|
||||
virtual bool onGround() const;
|
||||
bool isHovering() const { return _isHovering; }
|
||||
void setHovering(bool enabled);
|
||||
|
||||
bool needsRemoval() const;
|
||||
bool needsAddition() const;
|
||||
void setEnabled(bool enabled);
|
||||
bool isEnabled() const { return _enabled; }
|
||||
void setDynamicsWorld(btDynamicsWorld* world);
|
||||
|
||||
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
bool needsShapeUpdate() const;
|
||||
void updateShapeIfNecessary();
|
||||
|
||||
void preSimulation(btScalar timeStep);
|
||||
void postSimulation();
|
||||
|
||||
protected:
|
||||
void updateUpAxis(const glm::quat& rotation);
|
||||
};
|
||||
|
||||
#endif // hifi_DynamicCharacterController_h
|
|
@ -628,7 +628,7 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
|||
return true;
|
||||
}
|
||||
|
||||
void PhysicsEngine::setCharacterController(CharacterController* character) {
|
||||
void PhysicsEngine::setCharacterController(DynamicCharacterController* character) {
|
||||
if (_characterController != character) {
|
||||
lock();
|
||||
if (_characterController) {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <EntitySimulation.h>
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "CharacterController.h"
|
||||
#include "DynamicCharacterController.h"
|
||||
#include "ContactInfo.h"
|
||||
#include "EntityMotionState.h"
|
||||
#include "ShapeManager.h"
|
||||
|
@ -84,7 +84,7 @@ public:
|
|||
/// process queue of changed from external sources
|
||||
void relayIncomingChangesToSimulation();
|
||||
|
||||
void setCharacterController(CharacterController* character);
|
||||
void setCharacterController(DynamicCharacterController* character);
|
||||
|
||||
void dumpNextStats() { _dumpNextStats = true; }
|
||||
|
||||
|
@ -122,7 +122,7 @@ private:
|
|||
uint32_t _lastNumSubstepsAtUpdateInternal = 0;
|
||||
|
||||
/// character collisions
|
||||
CharacterController* _characterController = NULL;
|
||||
DynamicCharacterController* _characterController = NULL;
|
||||
|
||||
bool _dumpNextStats = false;
|
||||
};
|
||||
|
|
|
@ -2505,7 +2505,7 @@ int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMod
|
|||
}
|
||||
|
||||
if (mesh.colors.isEmpty()) {
|
||||
GLBATCH(glColor4f)(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLBATCH(glColor4f)(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
qint64 offset = 0;
|
||||
|
|
|
@ -21,6 +21,9 @@ uniform sampler2D diffuseMap;
|
|||
// the interpolated normal
|
||||
varying vec4 normal;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
|
||||
void main(void) {
|
||||
// Fetch diffuse map
|
||||
vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st);
|
||||
|
@ -30,7 +33,7 @@ void main(void) {
|
|||
packDeferredFragment(
|
||||
normalize(normal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
getMaterialSpecular(mat),
|
||||
getMaterialShininess(mat));
|
||||
}
|
||||
|
|
|
@ -19,14 +19,15 @@ const int MAX_TEXCOORDS = 2;
|
|||
|
||||
uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
|
||||
|
||||
|
||||
// the interpolated normal
|
||||
varying vec4 normal;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
|
||||
// pass along the diffuse color
|
||||
gl_FrontColor = gl_Color;
|
||||
color = gl_Color.xyz;
|
||||
|
||||
// and the texture coordinates
|
||||
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
|
||||
|
|
|
@ -26,6 +26,8 @@ uniform vec2 emissiveParams;
|
|||
// the interpolated normal
|
||||
varying vec4 normal;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
// the interpolated texcoord1
|
||||
varying vec2 interpolatedTexcoord1;
|
||||
|
||||
|
@ -39,7 +41,7 @@ void main(void) {
|
|||
packDeferredFragmentLightmap(
|
||||
normalize(normal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
getMaterialSpecular(mat),
|
||||
getMaterialShininess(mat),
|
||||
(vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb));
|
||||
|
|
|
@ -28,9 +28,12 @@ varying vec4 normal;
|
|||
// the interpolated texcoord1
|
||||
varying vec2 interpolatedTexcoord1;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
|
||||
void main(void) {
|
||||
// pass along the diffuse color
|
||||
gl_FrontColor = gl_Color;
|
||||
color = gl_Color.xyz;
|
||||
|
||||
// and the texture coordinates
|
||||
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
|
||||
|
|
|
@ -34,6 +34,8 @@ varying vec4 interpolatedTangent;
|
|||
|
||||
varying vec2 interpolatedTexcoord1;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
// compute the view normal from the various bits
|
||||
vec3 normalizedNormal = normalize(vec3(interpolatedNormal));
|
||||
|
@ -52,7 +54,7 @@ void main(void) {
|
|||
packDeferredFragmentLightmap(
|
||||
normalize(viewNormal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
getMaterialSpecular(mat),
|
||||
getMaterialShininess(mat),
|
||||
(vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb));
|
||||
|
|
|
@ -34,13 +34,15 @@ varying vec4 interpolatedTangent;
|
|||
// the interpolated texcoord1
|
||||
varying vec2 interpolatedTexcoord1;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
// transform and store the normal and tangent for interpolation
|
||||
//interpolatedNormal = gl_ModelViewMatrix * vec4(gl_Normal, 0.0);
|
||||
//interpolatedTangent = gl_ModelViewMatrix * vec4(tangent, 0.0);
|
||||
|
||||
// pass along the diffuse color
|
||||
gl_FrontColor = gl_Color;
|
||||
color = gl_Color.xyz;
|
||||
|
||||
// and the texture coordinates
|
||||
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
|
||||
|
|
|
@ -37,6 +37,9 @@ varying vec4 interpolatedTangent;
|
|||
|
||||
varying vec2 interpolatedTexcoord1;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
|
||||
void main(void) {
|
||||
// compute the view normal from the various bits
|
||||
vec3 normalizedNormal = normalize(vec3(interpolatedNormal));
|
||||
|
@ -56,7 +59,7 @@ void main(void) {
|
|||
packDeferredFragmentLightmap(
|
||||
normalize(viewNormal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
specular, // no use of getMaterialSpecular(mat)
|
||||
getMaterialShininess(mat),
|
||||
(vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb));
|
||||
|
|
|
@ -31,6 +31,8 @@ varying vec4 normal;
|
|||
|
||||
varying vec2 interpolatedTexcoord1;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
// set the diffuse, normal, specular data
|
||||
vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st);
|
||||
|
@ -42,7 +44,7 @@ void main(void) {
|
|||
packDeferredFragmentLightmap(
|
||||
normalize(normal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
specular, // no use of getMaterialSpecular(mat)
|
||||
getMaterialShininess(mat),
|
||||
(vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb));
|
||||
|
|
|
@ -28,6 +28,8 @@ varying vec4 interpolatedNormal;
|
|||
// the interpolated tangent
|
||||
varying vec4 interpolatedTangent;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
// compute the view normal from the various bits
|
||||
vec3 normalizedNormal = normalize(vec3(interpolatedNormal));
|
||||
|
@ -44,7 +46,7 @@ void main(void) {
|
|||
packDeferredFragment(
|
||||
normalize(viewNormal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
getMaterialSpecular(mat),
|
||||
getMaterialShininess(mat));
|
||||
}
|
||||
|
|
|
@ -29,13 +29,15 @@ varying vec4 interpolatedNormal;
|
|||
// the interpolated tangent
|
||||
varying vec4 interpolatedTangent;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
// transform and store the normal and tangent for interpolation
|
||||
//interpolatedNormal = gl_ModelViewMatrix * vec4(gl_Normal, 0.0);
|
||||
//interpolatedTangent = gl_ModelViewMatrix * vec4(tangent, 0.0);
|
||||
|
||||
// pass along the diffuse color
|
||||
gl_FrontColor = gl_Color;
|
||||
color = gl_Color.xyz;
|
||||
|
||||
// and the texture coordinates
|
||||
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
|
||||
|
|
|
@ -31,6 +31,8 @@ varying vec4 interpolatedNormal;
|
|||
// the interpolated tangent
|
||||
varying vec4 interpolatedTangent;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
// compute the view normal from the various bits
|
||||
vec3 normalizedNormal = normalize(vec3(interpolatedNormal));
|
||||
|
@ -49,7 +51,7 @@ void main(void) {
|
|||
packDeferredFragment(
|
||||
normalize(viewNormal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
specular, //getMaterialSpecular(mat),
|
||||
getMaterialShininess(mat));
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ uniform sampler2D specularMap;
|
|||
// the interpolated normal
|
||||
varying vec4 normal;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
// set the diffuse, normal, specular data
|
||||
vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st);
|
||||
|
@ -35,7 +37,7 @@ void main(void) {
|
|||
packDeferredFragment(
|
||||
normalize(normal.xyz),
|
||||
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
specular, //getMaterialSpecular(mat),
|
||||
getMaterialShininess(mat));
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ uniform sampler2D diffuseMap;
|
|||
|
||||
varying vec4 normal;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
|
||||
// Fetch diffuse map
|
||||
|
@ -31,7 +33,7 @@ void main(void) {
|
|||
packDeferredFragmentTranslucent(
|
||||
normalize(normal.xyz),
|
||||
getMaterialOpacity(mat) * diffuse.a,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb,
|
||||
getMaterialDiffuse(mat) * diffuse.rgb * color,
|
||||
getMaterialSpecular(mat),
|
||||
getMaterialShininess(mat));
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ attribute vec4 clusterWeights;
|
|||
// the interpolated normal
|
||||
varying vec4 normal;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
normal = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
@ -39,7 +41,7 @@ void main(void) {
|
|||
}
|
||||
|
||||
// pass along the diffuse color
|
||||
gl_FrontColor = gl_Color;
|
||||
color = gl_Color.xyz;
|
||||
|
||||
// and the texture coordinates
|
||||
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
|
||||
|
|
|
@ -34,6 +34,8 @@ varying vec4 interpolatedNormal;
|
|||
// the interpolated tangent
|
||||
varying vec4 interpolatedTangent;
|
||||
|
||||
varying vec3 color;
|
||||
|
||||
void main(void) {
|
||||
vec4 interpolatedPosition = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
interpolatedNormal = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
@ -47,7 +49,7 @@ void main(void) {
|
|||
}
|
||||
|
||||
// pass along the diffuse color
|
||||
gl_FrontColor = gl_Color;
|
||||
color = gl_Color.xyz;
|
||||
|
||||
// and the texture coordinates
|
||||
gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0);
|
||||
|
|
Loading…
Reference in a new issue