mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 08:33:12 +02:00
merge fix
This commit is contained in:
commit
da0b36ed5c
34 changed files with 799 additions and 369 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -369,7 +369,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
statsObject["average_mixes_per_listener"] = 0.0;
|
||||
}
|
||||
|
||||
// ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||
|
||||
_sumListeners = 0;
|
||||
_sumMixes = 0;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <NodeList.h>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Avatar.h"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -19,7 +19,7 @@ ScriptLineNumberArea::ScriptLineNumberArea(ScriptEditBox* scriptEditBox) :
|
|||
_scriptEditBox = scriptEditBox;
|
||||
}
|
||||
|
||||
QSize ScriptLineNumberArea::sizeHint() {
|
||||
QSize ScriptLineNumberArea::sizeHint() const {
|
||||
return QSize(_scriptEditBox->lineNumberAreaWidth(), 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class ScriptLineNumberArea : public QWidget {
|
|||
|
||||
public:
|
||||
ScriptLineNumberArea(ScriptEditBox* scriptEditBox);
|
||||
QSize sizeHint();
|
||||
QSize sizeHint() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -177,6 +177,7 @@ public:
|
|||
class FBXGeometry {
|
||||
public:
|
||||
|
||||
QString author;
|
||||
QString applicationName; ///< the name of the application that generated the model
|
||||
|
||||
QVector<FBXJoint> joints;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ namespace ShapeColliderTests {
|
|||
void capsuleMissesCapsule();
|
||||
void capsuleTouchesCapsule();
|
||||
|
||||
void sphereTouchesAACube();
|
||||
void sphereTouchesAACubeFaces();
|
||||
void sphereTouchesAACubeEdges();
|
||||
void sphereMissesAACube();
|
||||
|
||||
void runAllTests();
|
||||
|
|
Loading…
Reference in a new issue