merge fix

This commit is contained in:
Philip Rosedale 2014-05-05 16:21:52 -07:00
commit da0b36ed5c
34 changed files with 799 additions and 369 deletions

View file

@ -39,7 +39,7 @@ you to run the full stack of the virtual world.
In order to set up your own virtual world, you need to set up and run your own
local "domain".
The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, and meta-voxels.
The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models.
Follow the instructions in the [build guide](BUILD.md) to build the various components.
@ -57,7 +57,7 @@ Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal win
This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option.
./assignment-client -n 5
./assignment-client -n 6
To test things out you'll want to run the Interface client.

View file

@ -369,7 +369,7 @@ void AudioMixer::sendStatsPacket() {
statsObject["average_mixes_per_listener"] = 0.0;
}
// ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
_sumListeners = 0;
_sumMixes = 0;

View file

@ -331,8 +331,9 @@ function ScaleSelector() {
});
this.setScale = function(scale) {
this.scale = scale;
this.power = Math.floor(Math.log(scale));
this.power = Math.floor(Math.log(scale) / Math.log(2));
rescaleImport();
this.update();
}
this.show = function(doShow) {
@ -835,7 +836,6 @@ function showPreviewLines() {
if (copyScale) {
scaleSelector.setScale(intersection.voxel.s);
scaleSelector.update();
}
moveTools();
} else {

View file

@ -60,7 +60,7 @@ function getNewVoxelPosition() {
}
function keyPressEvent(event) {
//print("event.text=" + event.text);
debugPrint("event.text=" + event.text);
if (event.text == "ESC") {
if (leftRecentlyDeleted) {
leftRecentlyDeleted = false;
@ -82,7 +82,8 @@ function keyPressEvent(event) {
};
newModel = Models.addModel(properties);
} else if (event.text == "DELETE") {
} else if (event.text == "DELETE" || event.text == "BACKSPACE") {
if (leftModelAlreadyInHand) {
print("want to delete leftHandModel=" + leftHandModel);
Models.deleteModel(leftHandModel);
@ -141,6 +142,7 @@ function checkControllerSide(whichSide) {
var BUTTON_FWD;
var BUTTON_3;
var palmPosition;
var palmRotation;
var modelAlreadyInHand;
var handMessage;
@ -148,12 +150,14 @@ function checkControllerSide(whichSide) {
BUTTON_FWD = LEFT_BUTTON_FWD;
BUTTON_3 = LEFT_BUTTON_3;
palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
palmRotation = Controller.getSpatialControlRawRotation(LEFT_PALM);
modelAlreadyInHand = leftModelAlreadyInHand;
handMessage = "LEFT";
} else {
BUTTON_FWD = RIGHT_BUTTON_FWD;
BUTTON_3 = RIGHT_BUTTON_3;
palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
palmRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM);
modelAlreadyInHand = rightModelAlreadyInHand;
handMessage = "RIGHT";
}
@ -168,8 +172,7 @@ function checkControllerSide(whichSide) {
if (closestModel.isKnownID) {
//debugPrint
print(handMessage + " HAND- CAUGHT SOMETHING!!");
debugPrint(handMessage + " HAND- CAUGHT SOMETHING!!");
if (whichSide == LEFT_PALM) {
leftModelAlreadyInHand = true;
@ -184,9 +187,10 @@ function checkControllerSide(whichSide) {
y: modelPosition.y,
z: modelPosition.z },
radius: modelRadius,
modelRotation: palmRotation,
};
print(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
debugPrint(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
Models.editModel(closestModel, properties);
@ -208,10 +212,11 @@ function checkControllerSide(whichSide) {
y: modelPosition.y,
z: modelPosition.z },
radius: modelRadius,
modelRotation: palmRotation,
modelURL: modelURLs[currentModelURL]
};
print("modelRadius=" +modelRadius);
debugPrint("modelRadius=" +modelRadius);
newModel = Models.addModel(properties);
if (whichSide == LEFT_PALM) {
@ -244,16 +249,16 @@ function checkControllerSide(whichSide) {
// If holding the model keep it in the palm
if (grabButtonPressed) {
//debugPrint
print(">>>>> " + handMessage + "-MODEL IN HAND, grabbing, hold and move");
debugPrint(">>>>> " + handMessage + "-MODEL IN HAND, grabbing, hold and move");
var modelPosition = getModelHoldPosition(whichSide);
var properties = { position: { x: modelPosition.x,
y: modelPosition.y,
z: modelPosition.z },
radius: modelRadius,
modelRotation: palmRotation,
};
print(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
debugPrint(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
Models.editModel(handModel, properties);
} else {

View file

@ -178,6 +178,10 @@ function disableArtificialGravity() {
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
updateButton(3, false);
}
// call this immediately so that avatar doesn't fall before voxel data arrives
// Ideally we would only do this on LOGIN, not when starting the script
// in the middle of a session.
disableArtificialGravity();
function enableArtificialGravity() {
// NOTE: setting the gravity automatically sets the AVATAR_MOTION_OBEY_LOCAL_GRAVITY behavior bit.
@ -276,7 +280,6 @@ function update(deltaTime) {
}
Script.update.connect(update);
// we also handle click detection in our mousePressEvent()
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});

View file

@ -561,42 +561,6 @@ void Application::paintGL() {
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) {
glm::vec3 planeNormal = _myCamera.getTargetRotation() * IDENTITY_FRONT;
const float BASE_PUSHBACK_RADIUS = 0.25f;
float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS;
glm::vec4 plane(planeNormal, -glm::dot(planeNormal, _myCamera.getTargetPosition()) - pushbackRadius);
// push camera out of any intersecting avatars
foreach (const AvatarSharedPointer& avatarData, _avatarManager.getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarData.data());
if (avatar->isMyAvatar()) {
continue;
}
if (glm::distance(avatar->getPosition(), _myCamera.getTargetPosition()) >
avatar->getBoundingRadius() + pushbackRadius) {
continue;
}
float angle = angleBetween(avatar->getPosition() - _myCamera.getTargetPosition(), planeNormal);
if (angle > PI_OVER_TWO) {
continue;
}
float scale = 1.0f - angle / PI_OVER_TWO;
scale = qMin(1.0f, scale * 2.5f);
static CollisionList collisions(64);
collisions.clear();
if (!avatar->findPlaneCollisions(plane, collisions)) {
continue;
}
for (int i = 0; i < collisions.size(); i++) {
pushback = qMax(pushback, glm::length(collisions.getCollision(i)->_penetration) * scale);
}
}
const float MAX_PUSHBACK = 0.35f;
pushback = qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale());
const float BASE_PUSHBACK_FOCAL_LENGTH = 0.5f;
pushbackFocalLength = BASE_PUSHBACK_FOCAL_LENGTH * _myAvatar->getScale();
}
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());

View file

@ -23,7 +23,6 @@
#include "InterfaceConfig.h"
#include "Util.h"
const int NUM_BBALLS = 200;
class BuckyBalls {
@ -42,9 +41,7 @@ private:
float _bballRadius[NUM_BBALLS];
float _bballColliding[NUM_BBALLS];
int _bballElement[NUM_BBALLS];
int _bballIsGrabbed[2];
int _bballIsGrabbed[2];
};
#endif // hifi_BuckyBalls_h

View file

@ -10,6 +10,7 @@
//
#include "MainWindow.h"
#include "Menu.h"
#include <QEvent>
#include <QMoveEvent>
@ -56,6 +57,10 @@ void MainWindow::changeEvent(QEvent* event) {
} else {
emit windowShown(true);
}
if (isFullScreen() != Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen)) {
Menu::getInstance()->setIsOptionChecked(MenuOption::Fullscreen, isFullScreen());
}
} else if (event->type() == QEvent::ActivationChange) {
if (isActiveWindow()) {
emit windowShown(true);

View file

@ -190,7 +190,7 @@ Menu::Menu() :
addDisabledActionAndSeparator(editMenu, "Physics");
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, true,
avatar, SLOT(updateMotionBehaviors()));
avatar, SLOT(updateMotionBehaviorsFromMenu()));
addAvatarCollisionSubMenu(editMenu);

View file

@ -153,14 +153,14 @@ bool ModelUploader::zip() {
// mixamo/autodesk defaults
if (!mapping.contains(SCALE_FIELD)) {
mapping.insert(SCALE_FIELD, 15.0);
mapping.insert(SCALE_FIELD, geometry.author == "www.makehuman.org" ? 150.0 : 15.0);
}
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
if (!joints.contains("jointEyeLeft")) {
joints.insert("jointEyeLeft", "LeftEye");
joints.insert("jointEyeLeft", geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye");
}
if (!joints.contains("jointEyeRight")) {
joints.insert("jointEyeRight", "RightEye");
joints.insert("jointEyeRight", geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye");
}
if (!joints.contains("jointNeck")) {
joints.insert("jointNeck", "Neck");
@ -172,7 +172,8 @@ bool ModelUploader::zip() {
joints.insert("jointLean", "Spine");
}
if (!joints.contains("jointHead")) {
joints.insert("jointHead", geometry.applicationName == "mixamo.com" ? "HeadTop_End" : "HeadEnd");
const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd";
joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head");
}
if (!joints.contains("jointLeftHand")) {
joints.insert("jointLeftHand", "LeftHand");

View file

@ -29,11 +29,11 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
neckPosition = owningAvatar->getPosition();
}
setTranslation(neckPosition);
glm::quat neckRotation;
if (!owningAvatar->getSkeletonModel().getNeckRotation(neckRotation)) {
neckRotation = owningAvatar->getOrientation();
glm::quat neckParentRotation;
if (!owningAvatar->getSkeletonModel().getNeckParentRotation(neckParentRotation)) {
neckParentRotation = owningAvatar->getOrientation();
}
setRotation(neckRotation);
setRotation(neckParentRotation);
const float MODEL_SCALE = 0.0006f;
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE);

View file

@ -13,7 +13,6 @@
#include <NodeList.h>
#include <GeometryUtil.h>
#include <StreamUtils.h>
#include "Application.h"
#include "Avatar.h"

View file

@ -45,7 +45,13 @@ const float PITCH_SPEED = 100.0f; // degrees/sec
const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions
const float COLLISION_RADIUS_SCALE = 0.125f;
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f;
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
// to properly follow avatar size.
float DEFAULT_MOTOR_TIMESCALE = 0.25f;
float MAX_AVATAR_SPEED = 300.0f;
float MAX_MOTOR_SPEED = MAX_AVATAR_SPEED;
MyAvatar::MyAvatar() :
Avatar(),
@ -55,13 +61,15 @@ MyAvatar::MyAvatar() :
_shouldJump(false),
_gravity(0.0f, -1.0f, 0.0f),
_distanceToNearestAvatar(std::numeric_limits<float>::max()),
_lastCollisionPosition(0, 0, 0),
_speedBrakes(false),
_wasPushing(false),
_isPushing(false),
_thrust(0.0f),
_isThrustOn(false),
_thrustMultiplier(1.0f),
_motionBehaviors(0),
_motorVelocity(0.0f),
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
_maxMotorSpeed(MAX_MOTOR_SPEED),
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
_lastBodyPenetration(0.0f),
_lastFloorContactPoint(0.0f),
_lookAtTargetAvatar(),
_shouldRender(true),
_billboardValid(false),
@ -115,130 +123,56 @@ void MyAvatar::update(float deltaTime) {
void MyAvatar::simulate(float deltaTime) {
glm::quat orientation = getOrientation();
if (_scale != _targetScale) {
float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale;
setScale(scale);
Application::getInstance()->getCamera()->setScale(scale);
}
// Collect thrust forces from keyboard and devices
updateThrust(deltaTime);
// update the movement of the hand and process handshaking with other avatars...
updateHandMovementAndTouching(deltaTime);
// apply gravity
// For gravity, always move the avatar by the amount driven by gravity, so that the collision
// routines will detect it and collide every frame when pulled by gravity to a surface
const float MIN_DISTANCE_AFTER_COLLISION_FOR_GRAVITY = 0.02f;
if (glm::length(_position - _lastCollisionPosition) > MIN_DISTANCE_AFTER_COLLISION_FOR_GRAVITY) {
updateOrientation(deltaTime);
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) +
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
bool walkingOnFloor = false;
float gravityLength = glm::length(_gravity);
if (gravityLength > EPSILON) {
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
glm::vec3 startCap;
boundingShape.getStartPoint(startCap);
glm::vec3 bottomOfBoundingCapsule = startCap + (boundingShape.getRadius() / gravityLength) * _gravity;
float fallThreshold = 2.f * deltaTime * gravityLength;
walkingOnFloor = (glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint) < fallThreshold);
}
if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f ||
! walkingOnFloor) {
// apply gravity
_velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime);
}
// update motor and thrust
updateMotorFromKeyboard(deltaTime, walkingOnFloor);
applyMotor(deltaTime);
applyThrust(deltaTime);
// add thrust to velocity
_velocity += _thrust * deltaTime;
// update body yaw by body yaw delta
orientation = orientation * glm::quat(glm::radians(
glm::vec3(_bodyPitchDelta, _bodyYawDelta, _bodyRollDelta) * deltaTime));
// decay body rotation momentum
const float BODY_SPIN_FRICTION = 7.5f;
float bodySpinMomentum = 1.0f - BODY_SPIN_FRICTION * deltaTime;
if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; }
_bodyPitchDelta *= bodySpinMomentum;
_bodyYawDelta *= bodySpinMomentum;
_bodyRollDelta *= bodySpinMomentum;
float MINIMUM_ROTATION_RATE = 2.0f;
if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.0f; }
if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.0f; }
if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.0f; }
const float MAX_STATIC_FRICTION_SPEED = 0.5f;
const float STATIC_FRICTION_STRENGTH = _scale * 20.0f;
applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_SPEED, STATIC_FRICTION_STRENGTH);
// Damp avatar velocity
const float LINEAR_DAMPING_STRENGTH = 0.5f;
const float SPEED_BRAKE_POWER = _scale * 10.0f;
const float SQUARED_DAMPING_STRENGTH = 0.007f;
const float SLOW_NEAR_RADIUS = 5.0f;
float linearDamping = LINEAR_DAMPING_STRENGTH;
const float NEAR_AVATAR_DAMPING_FACTOR = 50.0f;
if (_distanceToNearestAvatar < _scale * SLOW_NEAR_RADIUS) {
linearDamping *= 1.0f + NEAR_AVATAR_DAMPING_FACTOR *
((SLOW_NEAR_RADIUS - _distanceToNearestAvatar) / SLOW_NEAR_RADIUS);
}
if (_speedBrakes) {
applyDamping(deltaTime, _velocity, linearDamping * SPEED_BRAKE_POWER, SQUARED_DAMPING_STRENGTH * SPEED_BRAKE_POWER);
} else {
applyDamping(deltaTime, _velocity, linearDamping, SQUARED_DAMPING_STRENGTH);
}
if (OculusManager::isConnected()) {
// these angles will be in radians
float yaw, pitch, roll;
OculusManager::getEulerAngles(yaw, pitch, roll);
// ... so they need to be converted to degrees before we do math...
// The neck is limited in how much it can yaw, so we check its relative
// yaw from the body and yaw the body if necessary.
yaw *= DEGREES_PER_RADIAN;
float bodyToHeadYaw = yaw - _oculusYawOffset;
const float MAX_NECK_YAW = 85.0f; // degrees
if ((fabs(bodyToHeadYaw) > 2.0f * MAX_NECK_YAW) && (yaw * _oculusYawOffset < 0.0f)) {
// We've wrapped around the range for yaw so adjust
// the measured yaw to be relative to _oculusYawOffset.
if (yaw > 0.0f) {
yaw -= 360.0f;
} else {
yaw += 360.0f;
}
bodyToHeadYaw = yaw - _oculusYawOffset;
// update position
if (glm::length2(_velocity) < EPSILON) {
_velocity = glm::vec3(0.0f);
} else {
_position += _velocity * deltaTime;
}
float delta = fabs(bodyToHeadYaw) - MAX_NECK_YAW;
if (delta > 0.0f) {
yaw = MAX_NECK_YAW;
if (bodyToHeadYaw < 0.0f) {
delta *= -1.0f;
bodyToHeadYaw = -MAX_NECK_YAW;
} else {
bodyToHeadYaw = MAX_NECK_YAW;
}
// constrain _oculusYawOffset to be within range [-180,180]
_oculusYawOffset = fmod((_oculusYawOffset + delta) + 180.0f, 360.0f) - 180.0f;
// We must adjust the body orientation using a delta rotation (rather than
// doing yaw math) because the body's yaw ranges are not the same
// as what the Oculus API provides.
glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f);
glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS);
orientation = orientation * bodyCorrection;
}
Head* head = getHead();
head->setBaseYaw(bodyToHeadYaw);
head->setBasePitch(pitch * DEGREES_PER_RADIAN);
head->setBaseRoll(roll * DEGREES_PER_RADIAN);
}
// update the euler angles
setOrientation(orientation);
// update moving flag based on speed
const float MOVING_SPEED_THRESHOLD = 0.01f;
float speed = glm::length(_velocity);
_moving = speed > MOVING_SPEED_THRESHOLD;
_moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD;
updateChatCircle(deltaTime);
_position += _velocity * deltaTime;
// update avatar skeleton and simulate hand and head
getHand()->collideAgainstOurself();
getHand()->simulate(deltaTime, true);
@ -261,9 +195,6 @@ void MyAvatar::simulate(float deltaTime) {
head->setScale(_scale);
head->simulate(deltaTime, true);
// Zero thrust out now that we've added it to velocity in this frame
_thrust *= glm::vec3(0.0f);
// now that we're done stepping the avatar forward in time, compute new collisions
if (_collisionGroups != 0) {
Camera* myCamera = Application::getInstance()->getCamera();
@ -620,6 +551,214 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend
(glm::length(cameraPosition - head->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale);
}
void MyAvatar::updateOrientation(float deltaTime) {
// Gather rotation information from keyboard
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
// update body yaw by body yaw delta
glm::quat orientation = getOrientation() * glm::quat(glm::radians(
glm::vec3(_bodyPitchDelta, _bodyYawDelta, _bodyRollDelta) * deltaTime));
// decay body rotation momentum
const float BODY_SPIN_FRICTION = 7.5f;
float bodySpinMomentum = 1.0f - BODY_SPIN_FRICTION * deltaTime;
if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; }
_bodyPitchDelta *= bodySpinMomentum;
_bodyYawDelta *= bodySpinMomentum;
_bodyRollDelta *= bodySpinMomentum;
float MINIMUM_ROTATION_RATE = 2.0f;
if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.0f; }
if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.0f; }
if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.0f; }
if (OculusManager::isConnected()) {
// these angles will be in radians
float yaw, pitch, roll;
OculusManager::getEulerAngles(yaw, pitch, roll);
// ... so they need to be converted to degrees before we do math...
// The neck is limited in how much it can yaw, so we check its relative
// yaw from the body and yaw the body if necessary.
yaw *= DEGREES_PER_RADIAN;
float bodyToHeadYaw = yaw - _oculusYawOffset;
const float MAX_NECK_YAW = 85.0f; // degrees
if ((fabs(bodyToHeadYaw) > 2.0f * MAX_NECK_YAW) && (yaw * _oculusYawOffset < 0.0f)) {
// We've wrapped around the range for yaw so adjust
// the measured yaw to be relative to _oculusYawOffset.
if (yaw > 0.0f) {
yaw -= 360.0f;
} else {
yaw += 360.0f;
}
bodyToHeadYaw = yaw - _oculusYawOffset;
}
float delta = fabs(bodyToHeadYaw) - MAX_NECK_YAW;
if (delta > 0.0f) {
yaw = MAX_NECK_YAW;
if (bodyToHeadYaw < 0.0f) {
delta *= -1.0f;
bodyToHeadYaw = -MAX_NECK_YAW;
} else {
bodyToHeadYaw = MAX_NECK_YAW;
}
// constrain _oculusYawOffset to be within range [-180,180]
_oculusYawOffset = fmod((_oculusYawOffset + delta) + 180.0f, 360.0f) - 180.0f;
// We must adjust the body orientation using a delta rotation (rather than
// doing yaw math) because the body's yaw ranges are not the same
// as what the Oculus API provides.
glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f);
glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS);
orientation = orientation * bodyCorrection;
}
Head* head = getHead();
head->setBaseYaw(bodyToHeadYaw);
head->setBasePitch(pitch * DEGREES_PER_RADIAN);
head->setBaseRoll(roll * DEGREES_PER_RADIAN);
}
// update the euler angles
setOrientation(orientation);
}
void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) {
// Increase motor velocity until its length is equal to _maxMotorSpeed.
if (!(_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED)) {
// nothing to do
return;
}
glm::vec3 localVelocity = _velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
glm::quat orientation = getHead()->getCameraOrientation();
localVelocity = glm::inverse(orientation) * _velocity;
}
// Compute keyboard input
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
// Compute motor magnitude
if (directionLength > EPSILON) {
direction /= directionLength;
// the finalMotorSpeed depends on whether we are walking or not
const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f;
const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed;
float motorLength = glm::length(_motorVelocity);
if (motorLength < MIN_KEYBOARD_CONTROL_SPEED) {
// an active keyboard motor should never be slower than this
_motorVelocity = MIN_KEYBOARD_CONTROL_SPEED * direction;
} else {
float MOTOR_LENGTH_TIMESCALE = 1.5f;
float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f);
float INCREASE_FACTOR = 2.0f;
//_motorVelocity *= 1.0f + tau * INCREASE_FACTOR;
motorLength *= 1.0f + tau * INCREASE_FACTOR;
if (motorLength > finalMaxMotorSpeed) {
motorLength = finalMaxMotorSpeed;
}
_motorVelocity = motorLength * direction;
}
_isPushing = true;
} else {
// motor opposes motion (wants to be at rest)
_motorVelocity = - localVelocity;
}
}
float MyAvatar::computeMotorTimescale() {
// The timescale of the motor is the approximate time it takes for the motor to
// accomplish its intended velocity. A short timescale makes the motor strong,
// and a long timescale makes it weak. The value of timescale to use depends
// on what the motor is doing:
//
// (1) braking --> short timescale (aggressive motor assertion)
// (2) pushing --> medium timescale (mild motor assertion)
// (3) inactive --> long timescale (gentle friction for low speeds)
//
// TODO: recover extra braking behavior when flying close to nearest avatar
float MIN_MOTOR_TIMESCALE = 0.125f;
float MAX_MOTOR_TIMESCALE = 0.5f;
float MIN_BRAKE_SPEED = 0.4f;
float timescale = MAX_MOTOR_TIMESCALE;
float speed = glm::length(_velocity);
bool areThrusting = (glm::length2(_thrust) > EPSILON);
if (_wasPushing && !(_isPushing || areThrusting) && speed > MIN_BRAKE_SPEED) {
// we don't change _wasPushing for this case -->
// keeps the brakes on until we go below MIN_BRAKE_SPEED
timescale = MIN_MOTOR_TIMESCALE;
} else {
if (_isPushing) {
timescale = _motorTimescale;
}
_wasPushing = _isPushing || areThrusting;
}
_isPushing = false;
return timescale;
}
void MyAvatar::applyMotor(float deltaTime) {
if (!( _motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED)) {
// nothing to do --> early exit
return;
}
glm::vec3 targetVelocity = _motorVelocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
// rotate _motorVelocity into world frame
glm::quat rotation = getOrientation();
targetVelocity = rotation * _motorVelocity;
}
glm::vec3 targetDirection(0.f);
if (glm::length2(targetVelocity) > EPSILON) {
targetDirection = glm::normalize(targetVelocity);
}
glm::vec3 deltaVelocity = targetVelocity - _velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY && glm::length2(_gravity) > EPSILON) {
// For now we subtract the component parallel to gravity but what we need to do is:
// TODO: subtract the component perp to the local surface normal (motor only pushes in surface plane).
glm::vec3 gravityDirection = glm::normalize(_gravity);
glm::vec3 parallelDelta = glm::dot(deltaVelocity, gravityDirection) * gravityDirection;
if (glm::dot(targetVelocity, _velocity) > 0.0f) {
// remove parallel part from deltaVelocity
deltaVelocity -= parallelDelta;
}
}
// simple critical damping
float timescale = computeMotorTimescale();
float tau = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
_velocity += tau * deltaVelocity;
}
void MyAvatar::applyThrust(float deltaTime) {
_velocity += _thrust * deltaTime;
float speed = glm::length(_velocity);
// cap the speed that thrust can achieve
if (speed > MAX_AVATAR_SPEED) {
_velocity *= MAX_AVATAR_SPEED / speed;
}
// zero thrust so we don't pile up thrust from other sources
_thrust = glm::vec3(0.0f);
}
/* Keep this code for the short term as reference in case we need to further tune the new model
* to achieve legacy movement response.
void MyAvatar::updateThrust(float deltaTime) {
//
// Gather thrust information from keyboard and sensors to apply to avatar motion
@ -665,10 +804,6 @@ void MyAvatar::updateThrust(float deltaTime) {
}
_lastBodyPenetration = glm::vec3(0.0f);
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
// If thrust keys are being held down, slowly increase thrust to allow reaching great speeds
if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) {
const float THRUST_INCREASE_RATE = 1.05f;
@ -699,8 +834,34 @@ void MyAvatar::updateThrust(float deltaTime) {
if (_isThrustOn || (_speedBrakes && (glm::length(_velocity) < MIN_SPEED_BRAKE_VELOCITY))) {
_speedBrakes = false;
}
_velocity += _thrust * deltaTime;
// Zero thrust out now that we've added it to velocity in this frame
_thrust = glm::vec3(0.0f);
// apply linear damping
const float MAX_STATIC_FRICTION_SPEED = 0.5f;
const float STATIC_FRICTION_STRENGTH = _scale * 20.0f;
applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_SPEED, STATIC_FRICTION_STRENGTH);
const float LINEAR_DAMPING_STRENGTH = 0.5f;
const float SPEED_BRAKE_POWER = _scale * 10.0f;
const float SQUARED_DAMPING_STRENGTH = 0.007f;
const float SLOW_NEAR_RADIUS = 5.0f;
float linearDamping = LINEAR_DAMPING_STRENGTH;
const float NEAR_AVATAR_DAMPING_FACTOR = 50.0f;
if (_distanceToNearestAvatar < _scale * SLOW_NEAR_RADIUS) {
linearDamping *= 1.0f + NEAR_AVATAR_DAMPING_FACTOR *
((SLOW_NEAR_RADIUS - _distanceToNearestAvatar) / SLOW_NEAR_RADIUS);
}
if (_speedBrakes) {
applyDamping(deltaTime, _velocity, linearDamping * SPEED_BRAKE_POWER, SQUARED_DAMPING_STRENGTH * SPEED_BRAKE_POWER);
} else {
applyDamping(deltaTime, _velocity, linearDamping, SQUARED_DAMPING_STRENGTH);
}
}
*/
void MyAvatar::updateHandMovementAndTouching(float deltaTime) {
glm::quat orientation = getOrientation();
@ -747,7 +908,6 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
if (Application::getInstance()->getEnvironment()->findCapsulePenetration(
_position - up * (pelvisFloatingHeight - radius),
_position + up * (getSkeletonHeight() - pelvisFloatingHeight + radius), radius, penetration)) {
_lastCollisionPosition = _position;
updateCollisionSound(penetration, deltaTime, ENVIRONMENT_COLLISION_FREQUENCY);
applyHardCollision(penetration, ENVIRONMENT_SURFACE_ELASTICITY, ENVIRONMENT_SURFACE_DAMPING);
}
@ -759,12 +919,59 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
myCollisions.clear();
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) {
const float VOXEL_ELASTICITY = 0.4f;
const float VOXEL_ELASTICITY = 0.0f;
const float VOXEL_DAMPING = 0.0f;
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
if (glm::length2(_gravity) > EPSILON) {
if (myCollisions.size() == 1) {
// trivial case
CollisionInfo* collision = myCollisions[0];
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
_lastFloorContactPoint = collision->_contactPoint - collision->_penetration;
} else {
// This is special collision handling for when walking on a voxel field which
// prevents snagging at corners and seams.
// sift through the collisions looking for one against the "floor"
int floorIndex = 0;
float distanceToFloor = 0.0f;
float penetrationWithFloor = 0.0f;
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
float distance = glm::dot(_gravity, collision->_contactPoint - _position);
if (distance > distanceToFloor) {
distanceToFloor = distance;
penetrationWithFloor = glm::dot(_gravity, collision->_penetration);
floorIndex = i;
}
}
// step through the collisions again and apply each that is not redundant
glm::vec3 oldPosition = _position;
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
if (i == floorIndex) {
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
_lastFloorContactPoint = collision->_contactPoint - collision->_penetration;
} else {
float distance = glm::dot(_gravity, collision->_contactPoint - oldPosition);
float penetration = glm::dot(_gravity, collision->_penetration);
if (fabsf(distance - distanceToFloor) > penetrationWithFloor || penetration > penetrationWithFloor) {
// resolution of the deepest penetration would not resolve this one
// so we apply the collision
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
}
}
}
}
} else {
// no gravity -- apply all collisions
for (int i = 0; i < myCollisions.size(); ++i) {
CollisionInfo* collision = myCollisions[i];
applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING);
}
}
const float VOXEL_COLLISION_FREQUENCY = 0.5f;
updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY);
}
@ -1128,8 +1335,7 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
}
}
void MyAvatar::updateMotionBehaviors() {
_motionBehaviors = 0;
void MyAvatar::updateMotionBehaviorsFromMenu() {
if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
@ -1149,8 +1355,14 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) {
menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES));
}
void MyAvatar::setMotionBehaviors(quint32 flags) {
_motionBehaviors = flags;
void MyAvatar::setMotionBehaviorsByScript(quint32 flags) {
// start with the defaults
_motionBehaviors = AVATAR_MOTION_DEFAULTS;
// add the set scriptable bits
_motionBehaviors += flags & AVATAR_MOTION_SCRIPTABLE_BITS;
// reconcile incompatible settings from menu (if any)
Menu* menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::ObeyEnvironmentalGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY));
// Environmental and Local gravities are incompatible. Environmental setting trumps local.

View file

@ -28,7 +28,7 @@ enum AvatarHandState
class MyAvatar : public Avatar {
Q_OBJECT
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviors WRITE setMotionBehaviors)
Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviorsForScript WRITE setMotionBehaviorsByScript)
Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity)
public:
@ -88,8 +88,9 @@ public:
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setCollisionGroups(quint32 collisionGroups);
void setMotionBehaviors(quint32 flags);
quint32 getMotionBehaviors() const { return _motionBehaviors; }
void setMotionBehaviorsByScript(quint32 flags);
quint32 getMotionBehaviorsForScript() const { return _motionBehaviors & AVATAR_MOTION_SCRIPTABLE_BITS; }
void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration);
@ -107,7 +108,7 @@ public slots:
glm::vec3 getThrust() { return _thrust; };
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void updateMotionBehaviors();
void updateMotionBehaviorsFromMenu();
signals:
void transformChanged();
@ -121,17 +122,18 @@ private:
glm::vec3 _gravity;
glm::vec3 _environmentGravity;
float _distanceToNearestAvatar; // How close is the nearest avatar?
// motion stuff
glm::vec3 _lastCollisionPosition;
bool _speedBrakes;
glm::vec3 _thrust; // final acceleration for the current frame
bool _isThrustOn;
float _thrustMultiplier;
bool _wasPushing;
bool _isPushing;
glm::vec3 _thrust; // final acceleration from outside sources for the current frame
glm::vec3 _motorVelocity; // intended velocity of avatar motion
float _motorTimescale; // timescale for avatar motor to achieve its desired velocity
float _maxMotorSpeed;
quint32 _motionBehaviors;
glm::vec3 _lastBodyPenetration;
glm::vec3 _lastFloorContactPoint;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
bool _shouldRender;
@ -139,7 +141,11 @@ private:
float _oculusYawOffset;
// private methods
void updateThrust(float deltaTime);
void updateOrientation(float deltaTime);
void updateMotorFromKeyboard(float deltaTime, bool walking);
float computeMotorTimescale();
void applyMotor(float deltaTime);
void applyThrust(float deltaTime);
void updateHandMovementAndTouching(float deltaTime);
void updateCollisionWithAvatars(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime, float radius);

View file

@ -12,7 +12,7 @@
#include <glm/gtx/quaternion.hpp>
#include "InterfaceConfig.h"
#include "Menu.h"
#include "ModelTreeRenderer.h"
ModelTreeRenderer::ModelTreeRenderer() :
@ -118,6 +118,15 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
}
}
float ModelTreeRenderer::getSizeScale() const {
return Menu::getInstance()->getVoxelSizeScale();
}
int ModelTreeRenderer::getBoundaryLevelAdjust() const {
return Menu::getInstance()->getBoundaryLevelAdjust();
}
void ModelTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
static_cast<ModelTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
}

View file

@ -36,6 +36,8 @@ public:
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; }
virtual void renderElement(OctreeElement* element, RenderArgs* args);
virtual float getSizeScale() const;
virtual int getBoundaryLevelAdjust() const;
void update();

View file

@ -434,6 +434,17 @@ bool Model::getNeckRotation(glm::quat& neckRotation) const {
return isActive() && getJointRotation(_geometry->getFBXGeometry().neckJointIndex, neckRotation);
}
bool Model::getNeckParentRotation(glm::quat& neckParentRotation) const {
if (!isActive()) {
return false;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.neckJointIndex == -1) {
return false;
}
return getJointRotation(geometry.joints.at(geometry.neckJointIndex).parentIndex, neckParentRotation);
}
bool Model::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
if (!isActive()) {
return false;
@ -581,18 +592,10 @@ void Model::rebuildShapes() {
capsule->setRotation(combinedRotations[i] * joint.shapeRotation);
_jointShapes.push_back(capsule);
glm::vec3 endPoint;
capsule->getEndPoint(endPoint);
glm::vec3 startPoint;
capsule->getStartPoint(startPoint);
// add some points that bound a sphere at the center of the capsule
glm::vec3 axis = glm::vec3(radius);
shapeExtents.addPoint(worldPosition + axis);
shapeExtents.addPoint(worldPosition - axis);
// add the two furthest surface points of the capsule
axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint);
glm::vec3 axis;
capsule->computeNormalizedAxis(axis);
axis = halfHeight * axis + glm::vec3(radius);
shapeExtents.addPoint(worldPosition + axis);
shapeExtents.addPoint(worldPosition - axis);
@ -626,7 +629,7 @@ void Model::rebuildShapes() {
glm::quat inverseRotation = glm::inverse(_rotation);
glm::vec3 rootPosition = extractTranslation(transforms[rootIndex]);
_boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition);
_boundingShape.setPosition(_translation - _rotation * _boundingShapeLocalOffset);
_boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}

View file

@ -132,6 +132,10 @@ public:
/// \return whether or not the neck was found
bool getNeckRotation(glm::quat& neckRotation) const;
/// Returns the rotation of the neck joint's parent.
/// \return whether or not the neck was found
bool getNeckParentRotation(glm::quat& neckRotation) const;
/// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;

View file

@ -19,7 +19,7 @@ ScriptLineNumberArea::ScriptLineNumberArea(ScriptEditBox* scriptEditBox) :
_scriptEditBox = scriptEditBox;
}
QSize ScriptLineNumberArea::sizeHint() {
QSize ScriptLineNumberArea::sizeHint() const {
return QSize(_scriptEditBox->lineNumberAreaWidth(), 0);
}

View file

@ -19,7 +19,7 @@ class ScriptLineNumberArea : public QWidget {
public:
ScriptLineNumberArea(ScriptEditBox* scriptEditBox);
QSize sizeHint();
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent* event);

View file

@ -51,8 +51,24 @@ typedef unsigned long long quint64;
#include "HandData.h"
// avatar motion behaviors
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 0;
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 1;
const quint32 AVATAR_MOTION_MOTOR_ENABLED = 1U << 0;
const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 1;
const quint32 AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME = 1U << 2;
const quint32 AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY = 1U << 3;
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 4;
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 5;
const quint32 AVATAR_MOTION_DEFAULTS =
AVATAR_MOTION_MOTOR_ENABLED |
AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED |
AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME;
// these bits will be expanded as features are exposed
const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY |
AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
// First bitset
const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits

View file

@ -406,7 +406,7 @@ QVariantHash parseMapping(QIODevice* device) {
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
QVector<glm::vec3> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) {
for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 3 * 3); it != end; ) {
float x = *it++;
float y = *it++;
float z = *it++;
@ -417,7 +417,7 @@ QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
QVector<glm::vec2> createVec2Vector(const QVector<double>& doubleVector) {
QVector<glm::vec2> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) {
for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 2 * 2); it != end; ) {
float s = *it++;
float t = *it++;
values.append(glm::vec2(s, -t));
@ -432,58 +432,59 @@ glm::mat4 createMat4(const QVector<double>& doubleVector) {
doubleVector.at(12), doubleVector.at(13), doubleVector.at(14), doubleVector.at(15));
}
QVector<int> getIntVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
QVector<int> getIntVector(const FBXNode& node) {
foreach (const FBXNode& child, node.children) {
if (child.name == "a") {
return getIntVector(child);
}
}
if (node.properties.isEmpty()) {
return QVector<int>();
}
QVector<int> vector = properties.at(index).value<QVector<int> >();
QVector<int> vector = node.properties.at(0).value<QVector<int> >();
if (!vector.isEmpty()) {
return vector;
}
for (; index < properties.size(); index++) {
vector.append(properties.at(index).toInt());
for (int i = 0; i < node.properties.size(); i++) {
vector.append(node.properties.at(i).toInt());
}
return vector;
}
QVector<qlonglong> getLongVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
return QVector<qlonglong>();
QVector<float> getFloatVector(const FBXNode& node) {
foreach (const FBXNode& child, node.children) {
if (child.name == "a") {
return getFloatVector(child);
}
}
QVector<qlonglong> vector = properties.at(index).value<QVector<qlonglong> >();
if (!vector.isEmpty()) {
return vector;
}
for (; index < properties.size(); index++) {
vector.append(properties.at(index).toLongLong());
}
return vector;
}
QVector<float> getFloatVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
if (node.properties.isEmpty()) {
return QVector<float>();
}
QVector<float> vector = properties.at(index).value<QVector<float> >();
QVector<float> vector = node.properties.at(0).value<QVector<float> >();
if (!vector.isEmpty()) {
return vector;
}
for (; index < properties.size(); index++) {
vector.append(properties.at(index).toFloat());
for (int i = 0; i < node.properties.size(); i++) {
vector.append(node.properties.at(i).toFloat());
}
return vector;
}
QVector<double> getDoubleVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
QVector<double> getDoubleVector(const FBXNode& node) {
foreach (const FBXNode& child, node.children) {
if (child.name == "a") {
return getDoubleVector(child);
}
}
if (node.properties.isEmpty()) {
return QVector<double>();
}
QVector<double> vector = properties.at(index).value<QVector<double> >();
QVector<double> vector = node.properties.at(0).value<QVector<double> >();
if (!vector.isEmpty()) {
return vector;
}
for (; index < properties.size(); index++) {
vector.append(properties.at(index).toDouble());
for (int i = 0; i < node.properties.size(); i++) {
vector.append(node.properties.at(i).toDouble());
}
return vector;
}
@ -697,21 +698,30 @@ public:
};
void appendIndex(MeshData& data, QVector<int>& indices, int index) {
if (index >= data.polygonIndices.size()) {
return;
}
int vertexIndex = data.polygonIndices.at(index);
if (vertexIndex < 0) {
vertexIndex = -vertexIndex - 1;
}
Vertex vertex;
vertex.originalIndex = vertexIndex;
glm::vec3 position;
if (vertexIndex < data.vertices.size()) {
position = data.vertices.at(vertexIndex);
}
glm::vec3 normal;
if (data.normalIndices.isEmpty()) {
normal = data.normals.at(data.normalsByVertex ? vertexIndex : index);
} else {
int normalIndex = data.normalIndices.at(data.normalsByVertex ? vertexIndex : index);
if (normalIndex >= 0) {
int normalIndex = data.normalsByVertex ? vertexIndex : index;
if (data.normalIndices.isEmpty()) {
if (normalIndex < data.normals.size()) {
normal = data.normals.at(normalIndex);
}
} else if (normalIndex < data.normalIndices.size()) {
normalIndex = data.normalIndices.at(normalIndex);
if (normalIndex >= 0 && normalIndex < data.normals.size()) {
normal = data.normals.at(normalIndex);
}
}
@ -720,9 +730,9 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
if (index < data.texCoords.size()) {
vertex.texCoord = data.texCoords.at(index);
}
} else {
} else if (index < data.texCoordIndices.size()) {
int texCoordIndex = data.texCoordIndices.at(index);
if (texCoordIndex >= 0) {
if (texCoordIndex >= 0 && texCoordIndex < data.texCoords.size()) {
vertex.texCoord = data.texCoords.at(texCoordIndex);
}
}
@ -733,7 +743,7 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
indices.append(newIndex);
data.indices.insert(vertex, newIndex);
data.extracted.newIndices.insert(vertexIndex, newIndex);
data.extracted.mesh.vertices.append(data.vertices.at(vertexIndex));
data.extracted.mesh.vertices.append(position);
data.extracted.mesh.normals.append(normal);
data.extracted.mesh.texCoords.append(vertex.texCoord);
@ -749,44 +759,51 @@ ExtractedMesh extractMesh(const FBXNode& object) {
QVector<int> textures;
foreach (const FBXNode& child, object.children) {
if (child.name == "Vertices") {
data.vertices = createVec3Vector(getDoubleVector(child.properties, 0));
data.vertices = createVec3Vector(getDoubleVector(child));
} else if (child.name == "PolygonVertexIndex") {
data.polygonIndices = getIntVector(child.properties, 0);
data.polygonIndices = getIntVector(child);
} else if (child.name == "LayerElementNormal") {
data.normalsByVertex = false;
bool indexToDirect = false;
foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "Normals") {
data.normals = createVec3Vector(getDoubleVector(subdata.properties, 0));
data.normals = createVec3Vector(getDoubleVector(subdata));
} else if (subdata.name == "NormalsIndex") {
data.normalIndices = getIntVector(subdata.properties, 0);
data.normalIndices = getIntVector(subdata);
} else if (subdata.name == "MappingInformationType" &&
subdata.properties.at(0) == "ByVertice") {
} else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == "ByVertice") {
data.normalsByVertex = 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.normalsByVertex = true;
}
} else if (child.name == "LayerElementUV" && child.properties.at(0).toInt() == 0) {
foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "UV") {
data.texCoords = createVec2Vector(getDoubleVector(subdata.properties, 0));
data.texCoords = createVec2Vector(getDoubleVector(subdata));
} else if (subdata.name == "UVIndex") {
data.texCoordIndices = getIntVector(subdata.properties, 0);
data.texCoordIndices = getIntVector(subdata);
}
}
} else if (child.name == "LayerElementMaterial") {
foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "Materials") {
materials = getIntVector(subdata.properties, 0);
materials = getIntVector(subdata);
}
}
} else if (child.name == "LayerElementTexture") {
foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "TextureId") {
textures = getIntVector(subdata.properties, 0);
textures = getIntVector(subdata);
}
}
}
@ -797,7 +814,7 @@ ExtractedMesh extractMesh(const FBXNode& object) {
QHash<QPair<int, int>, int> materialTextureParts;
for (int beginIndex = 0; beginIndex < data.polygonIndices.size(); polygonIndex++) {
int endIndex = beginIndex;
while (data.polygonIndices.at(endIndex++) >= 0);
while (endIndex < data.polygonIndices.size() && data.polygonIndices.at(endIndex++) >= 0);
QPair<int, int> materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0,
(polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0);
@ -820,7 +837,7 @@ ExtractedMesh extractMesh(const FBXNode& object) {
appendIndex(data, part.triangleIndices, beginIndex);
appendIndex(data, part.triangleIndices, nextIndex++);
appendIndex(data, part.triangleIndices, nextIndex);
if (data.polygonIndices.at(nextIndex) < 0) {
if (nextIndex >= data.polygonIndices.size() || data.polygonIndices.at(nextIndex) < 0) {
break;
}
}
@ -835,13 +852,13 @@ FBXBlendshape extractBlendshape(const FBXNode& object) {
FBXBlendshape blendshape;
foreach (const FBXNode& data, object.children) {
if (data.name == "Indexes") {
blendshape.indices = getIntVector(data.properties, 0);
blendshape.indices = getIntVector(data);
} else if (data.name == "Vertices") {
blendshape.vertices = createVec3Vector(getDoubleVector(data.properties, 0));
blendshape.vertices = createVec3Vector(getDoubleVector(data));
} else if (data.name == "Normals") {
blendshape.normals = createVec3Vector(getDoubleVector(data.properties, 0));
blendshape.normals = createVec3Vector(getDoubleVector(data));
}
}
return blendshape;
@ -1016,7 +1033,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
foreach (const FBXNode& object, child.children) {
if (object.name == "SceneInfo") {
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "Properties70") {
if (subobject.name == "MetaData") {
foreach (const FBXNode& subsubobject, subobject.children) {
if (subsubobject.name == "Author") {
geometry.author = subsubobject.properties.at(0).toString();
}
}
} else if (subobject.name == "Properties70") {
foreach (const FBXNode& subsubobject, subobject.children) {
if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 &&
subsubobject.properties.at(0) == "Original|ApplicationName") {
@ -1262,13 +1285,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
Cluster cluster;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "Indexes") {
cluster.indices = getIntVector(subobject.properties, 0);
cluster.indices = getIntVector(subobject);
} else if (subobject.name == "Weights") {
cluster.weights = getDoubleVector(subobject.properties, 0);
cluster.weights = getDoubleVector(subobject);
} else if (subobject.name == "TransformLink") {
QVector<double> values = getDoubleVector(subobject.properties, 0);
QVector<double> values = getDoubleVector(subobject);
cluster.transformLink = createMat4(values);
}
}
@ -1290,7 +1313,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
AnimationCurve curve;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "KeyValueFloat") {
curve.values = getFloatVector(subobject.properties, 0);
curve.values = getFloatVector(subobject);
}
}
animationCurves.insert(getID(object.properties), curve);

View file

@ -177,6 +177,7 @@ public:
class FBXGeometry {
public:
QString author;
QString applicationName; ///< the name of the application that generated the model
QVector<FBXJoint> joints;

View file

@ -79,7 +79,7 @@ qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) {
statsPacketStream << statsObject.toVariantMap();
return writeDatagram(statsPacket, _domainHandler.getSockAddr(), QUuid());
return writeUnverifiedDatagram(statsPacket, _domainHandler.getSockAddr());
}
void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) {

View file

@ -1208,6 +1208,7 @@ ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) c
// By doing this, we don't need to test each child voxel's position vs the LOD boundary
bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const {
bool shouldRender = false;
if (hasContent()) {
float furthestDistance = furthestDistanceToCamera(*viewFrustum);
float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize);

View file

@ -140,19 +140,22 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar
bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
RenderArgs* args = static_cast<RenderArgs*>(extraData);
//if (true || element->isInView(*args->_viewFrustum)) {
if (element->isInView(*args->_viewFrustum)) {
if (element->hasContent()) {
args->_renderer->renderElement(element, args);
if (element->calculateShouldRender(args->_viewFrustum, args->_sizeScale, args->_boundaryLevelAdjust)) {
args->_renderer->renderElement(element, args);
} else {
return false; // if we shouldn't render, then we also should stop recursing.
}
}
return true;
return true; // continue recursing
}
// if not in view stop recursing
return false;
}
void OctreeRenderer::render() {
RenderArgs args = { 0, this, _viewFrustum };
RenderArgs args = { 0, this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust() };
if (_tree) {
_tree->lockForRead();
_tree->recurseTreeWithOperation(renderOperation, &args);

View file

@ -31,6 +31,8 @@ public:
int _renderedItems;
OctreeRenderer* _renderer;
ViewFrustum* _viewFrustum;
float _sizeScale;
int _boundaryLevelAdjust;
};
@ -46,6 +48,8 @@ public:
virtual PacketType getMyQueryMessageType() const = 0;
virtual PacketType getExpectedPacketType() const = 0;
virtual void renderElement(OctreeElement* element, RenderArgs* args) = 0;
virtual float getSizeScale() const { return DEFAULT_OCTREE_SIZE_SCALE; }
virtual int getBoundaryLevelAdjust() const { return 0; }
virtual void setTree(Octree* newTree);

View file

@ -34,6 +34,7 @@ CapsuleShape::CapsuleShape(float radius, float halfHeight, const glm::vec3& posi
CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint) :
Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(0.0f) {
glm::vec3 axis = endPoint - startPoint;
_position = 0.5f * (endPoint + startPoint);
float height = glm::length(axis);
if (height > EPSILON) {
_halfHeight = 0.5f * height;
@ -50,12 +51,12 @@ CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm:
/// \param[out] startPoint is the center of start cap
void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
startPoint = getPosition() - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
startPoint = _position - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
}
/// \param[out] endPoint is the center of the end cap
void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
endPoint = getPosition() + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
endPoint = _position + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
}
void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {

View file

@ -14,7 +14,6 @@
#include "Shape.h"
// adebug bookmark TODO: convert to new world-frame approach
// default axis of CapsuleShape is Y-axis
class CapsuleShape : public Shape {

View file

@ -23,6 +23,12 @@ CollisionInfo* CollisionList::getNewCollision() {
return (_size < _maxSize) ? &(_collisions[_size++]) : NULL;
}
void CollisionList::deleteLastCollision() {
if (_size > 0) {
--_size;
}
}
CollisionInfo* CollisionList::getCollision(int index) {
return (index > -1 && index < _size) ? &(_collisions[index]) : NULL;
}

View file

@ -81,6 +81,9 @@ public:
/// \return pointer to next collision. NULL if list is full.
CollisionInfo* getNewCollision();
/// \forget about collision at the end
void deleteLastCollision();
/// \return pointer to collision by index. NULL if index out of bounds.
CollisionInfo* getCollision(int index);

View file

@ -591,7 +591,95 @@ bool listList(const ListShape* listA, const ListShape* listB, CollisionList& col
}
// helper function
bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter,
float cubeSide, CollisionList& collisions) {
// sphere is A
// cube is B
// BA = B - A = from center of A to center of B
float halfCubeSide = 0.5f * cubeSide;
glm::vec3 BA = cubeCenter - sphereCenter;
float distance = glm::length(BA);
if (distance > EPSILON) {
float maxBA = glm::max(glm::max(glm::abs(BA.x), glm::abs(BA.y)), glm::abs(BA.z));
if (maxBA > halfCubeSide + sphereRadius) {
// sphere misses cube entirely
return false;
}
CollisionInfo* collision = collisions.getNewCollision();
if (!collision) {
return false;
}
if (maxBA > halfCubeSide) {
// sphere hits cube but its center is outside cube
// compute contact anti-pole on cube (in cube frame)
glm::vec3 cubeContact = glm::abs(BA);
if (cubeContact.x > halfCubeSide) {
cubeContact.x = halfCubeSide;
}
if (cubeContact.y > halfCubeSide) {
cubeContact.y = halfCubeSide;
}
if (cubeContact.z > halfCubeSide) {
cubeContact.z = halfCubeSide;
}
glm::vec3 signs = glm::sign(BA);
cubeContact.x *= signs.x;
cubeContact.y *= signs.y;
cubeContact.z *= signs.z;
// compute penetration direction
glm::vec3 direction = BA - cubeContact;
float lengthDirection = glm::length(direction);
if (lengthDirection < EPSILON) {
// sphereCenter is touching cube surface, so we can't use the difference between those two
// points to compute the penetration direction. Instead we use the unitary components of
// cubeContact.
direction = cubeContact / halfCubeSide;
glm::modf(BA, direction);
lengthDirection = glm::length(direction);
} else if (lengthDirection > sphereRadius) {
collisions.deleteLastCollision();
return false;
}
direction /= lengthDirection;
// compute collision details
collision->_contactPoint = sphereCenter + sphereRadius * direction;
collision->_penetration = sphereRadius * direction - (BA - cubeContact);
} else {
// sphere center is inside cube
// --> push out nearest face
glm::vec3 direction;
BA /= maxBA;
glm::modf(BA, direction);
direction = glm::normalize(direction);
// compute collision details
collision->_penetration = (halfCubeSide + sphereRadius - distance * glm::dot(BA, direction)) * direction;
collision->_contactPoint = sphereCenter + sphereRadius * direction;
}
return true;
} else if (sphereRadius + halfCubeSide > distance) {
// NOTE: for cocentric approximation we collide sphere and cube as two spheres which means
// this algorithm will probably be wrong when both sphere and cube are very small (both ~EPSILON)
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
// the penetration and contactPoint are undefined, so we pick a penetration direction (-yAxis)
collision->_penetration = (sphereRadius + halfCubeSide) * glm::vec3(0.0f, -1.0f, 0.0f);
// contactPoint is on surface of A
collision->_contactPoint = sphereCenter + collision->_penetration;
return true;
}
}
return false;
}
// helper function
/* KEEP THIS CODE -- this is how to collide the cube with stark face normals (no rounding).
* We might want to use this code later for sealing boundaries between adjacent voxels.
bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter,
float cubeSide, CollisionList& collisions) {
glm::vec3 BA = cubeCenter - sphereCenter;
float distance = glm::length(BA);
if (distance > EPSILON) {
@ -606,50 +694,16 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
if (glm::dot(surfaceAB, BA) > 0.f) {
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
/* KEEP THIS CODE -- this is how to collide the cube with stark face normals (no rounding).
* We might want to use this code later for sealing boundaries between adjacent voxels.
// penetration is parallel to box side direction
BA /= maxBA;
glm::vec3 direction;
glm::modf(BA, direction);
direction = glm::normalize(direction);
*/
// For rounded normals at edges and corners:
// At this point imagine that sphereCenter touches a "normalized" cube with rounded edges.
// This cube has a sidelength of 2 and its smoothing radius is sphereRadius/maxBA.
// We're going to try to compute the "negative normal" (and hence direction of penetration)
// of this surface.
float radius = sphereRadius / (distance * maxBA); // normalized radius
float shortLength = maxBA - radius;
glm::vec3 direction = BA;
if (shortLength > 0.0f) {
direction = glm::abs(BA) - glm::vec3(shortLength);
// Set any negative components to zero, and adopt the sign of the original BA component.
// Unfortunately there isn't an easy way to make this fast.
if (direction.x < 0.0f) {
direction.x = 0.f;
} else if (BA.x < 0.f) {
direction.x = -direction.x;
}
if (direction.y < 0.0f) {
direction.y = 0.f;
} else if (BA.y < 0.f) {
direction.y = -direction.y;
}
if (direction.z < 0.0f) {
direction.z = 0.f;
} else if (BA.z < 0.f) {
direction.z = -direction.z;
}
}
direction = glm::normalize(direction);
// penetration is the projection of surfaceAB on direction
collision->_penetration = glm::dot(surfaceAB, direction) * direction;
// contactPoint is on surface of A
collision->_contactPoint = sphereCenter - sphereRadius * direction;
collision->_contactPoint = sphereCenter + sphereRadius * direction;
return true;
}
}
@ -667,6 +721,7 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
}
return false;
}
*/
bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
return sphereAACube(sphereA->getPosition(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);

View file

@ -681,58 +681,164 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
}
}
void ShapeColliderTests::sphereTouchesAACube() {
void ShapeColliderTests::sphereTouchesAACubeFaces() {
CollisionList collisions(16);
glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f);
float cubeSide = 2.34f;
float sphereRadius = 1.13f;
glm::vec3 sphereCenter(0.0f);
SphereShape sphere(sphereRadius, sphereCenter);
QVector<glm::vec3> axes;
axes.push_back(xAxis);
axes.push_back(-xAxis);
axes.push_back(yAxis);
axes.push_back(-yAxis);
axes.push_back(zAxis);
axes.push_back(-zAxis);
for (int i = 0; i < axes.size(); ++i) {
glm::vec3 axis = axes[i];
// outside
{
collisions.clear();
float overlap = 0.25f;
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
sphereCenter = cubeCenter + sphereOffset * axis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
}
CollisionInfo* collision = collisions[0];
if (!collision) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: no CollisionInfo. axis = " << axis << std::endl;
}
glm::vec3 expectedPenetration = - overlap * axis;
if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration
<< " expected " << expectedPenetration
<< " axis = " << axis
<< std::endl;
}
glm::vec3 expectedContact = sphereCenter - sphereRadius * axis;
if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint
<< " expected " << expectedContact
<< " axis = " << axis
<< std::endl;
}
}
// inside
{
collisions.clear();
float overlap = 1.25f * sphereRadius;
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
sphereCenter = cubeCenter + sphereOffset * axis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube."
<< " axis = " << axis
<< std::endl;
}
CollisionInfo* collision = collisions[0];
if (!collision) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: no CollisionInfo on y-axis."
<< " axis = " << axis
<< std::endl;
}
glm::vec3 expectedPenetration = - overlap * axis;
if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration
<< " expected " << expectedPenetration
<< " axis = " << axis
<< std::endl;
}
glm::vec3 expectedContact = sphereCenter - sphereRadius * axis;
if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint
<< " expected " << expectedContact
<< " axis = " << axis
<< std::endl;
}
}
}
}
void ShapeColliderTests::sphereTouchesAACubeEdges() {
CollisionList collisions(20);
glm::vec3 cubeCenter(0.0f, 0.0f, 0.0f);
float cubeSide = 2.0f;
float sphereRadius = 1.0f;
glm::vec3 sphereCenter(0.0f);
SphereShape sphere(sphereRadius, sphereCenter);
float sphereOffset = (0.5f * cubeSide + sphereRadius - 0.25f);
QVector<glm::vec3> axes;
// edges
axes.push_back(glm::vec3(0.0f, 1.0f, 1.0f));
axes.push_back(glm::vec3(0.0f, 1.0f, -1.0f));
axes.push_back(glm::vec3(0.0f, -1.0f, 1.0f));
axes.push_back(glm::vec3(0.0f, -1.0f, -1.0f));
axes.push_back(glm::vec3(1.0f, 1.0f, 0.0f));
axes.push_back(glm::vec3(1.0f, -1.0f, 0.0f));
axes.push_back(glm::vec3(-1.0f, 1.0f, 0.0f));
axes.push_back(glm::vec3(-1.0f, -1.0f, 0.0f));
axes.push_back(glm::vec3(1.0f, 0.0f, 1.0f));
axes.push_back(glm::vec3(1.0f, 0.0f, -1.0f));
axes.push_back(glm::vec3(-1.0f, 0.0f, 1.0f));
axes.push_back(glm::vec3(-1.0f, 0.0f, -1.0f));
// and corners
axes.push_back(glm::vec3(1.0f, 1.0f, 1.0f));
axes.push_back(glm::vec3(1.0f, 1.0f, -1.0f));
axes.push_back(glm::vec3(1.0f, -1.0f, 1.0f));
axes.push_back(glm::vec3(1.0f, -1.0f, -1.0f));
axes.push_back(glm::vec3(-1.0f, 1.0f, 1.0f));
axes.push_back(glm::vec3(-1.0f, 1.0f, -1.0f));
axes.push_back(glm::vec3(-1.0f, -1.0f, 1.0f));
axes.push_back(glm::vec3(-1.0f, -1.0f, -1.0f));
// top
sphereCenter = cubeCenter + sphereOffset * yAxis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
}
for (int i =0; i < axes.size(); ++i) {
glm::vec3 axis = axes[i];
float lengthAxis = glm::length(axis);
axis /= lengthAxis;
float overlap = 0.25f;
// bottom
sphereCenter = cubeCenter - sphereOffset * yAxis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
}
// left
sphereCenter = cubeCenter + sphereOffset * xAxis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
}
// right
sphereCenter = cubeCenter - sphereOffset * xAxis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
}
// forward
sphereCenter = cubeCenter + sphereOffset * zAxis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
}
// back
sphereCenter = cubeCenter - sphereOffset * zAxis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl;
sphereCenter = cubeCenter + (lengthAxis * 0.5f * cubeSide + sphereRadius - overlap) * axis;
sphere.setPosition(sphereCenter);
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
}
CollisionInfo* collision = collisions[i];
if (!collision) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: no CollisionInfo. axis = " << axis << std::endl;
}
glm::vec3 expectedPenetration = - overlap * axis;
if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration
<< " expected " << expectedPenetration
<< " axis = " << axis
<< std::endl;
}
glm::vec3 expectedContact = sphereCenter - sphereRadius * axis;
if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint
<< " expected " << expectedContact
<< " axis = " << axis
<< std::endl;
}
}
}
@ -802,6 +908,7 @@ void ShapeColliderTests::runAllTests() {
capsuleMissesCapsule();
capsuleTouchesCapsule();
sphereTouchesAACube();
sphereTouchesAACubeFaces();
sphereTouchesAACubeEdges();
sphereMissesAACube();
}

View file

@ -23,7 +23,8 @@ namespace ShapeColliderTests {
void capsuleMissesCapsule();
void capsuleTouchesCapsule();
void sphereTouchesAACube();
void sphereTouchesAACubeFaces();
void sphereTouchesAACubeEdges();
void sphereMissesAACube();
void runAllTests();