Merge branch 'master' of https://github.com/highfidelity/hifi into reregister-and-scale-after-model-change

This commit is contained in:
howard-stearns 2016-03-15 12:22:36 -07:00
commit 1891062dd2
51 changed files with 1051 additions and 276 deletions

View file

@ -213,7 +213,6 @@ function AttachedEntitiesManager() {
var props = Entities.getEntityProperties(entityID);
if (props.parentID == MyAvatar.sessionUUID) {
grabData = getEntityCustomData('grabKey', entityID, {});
grabbableData = getEntityCustomData('grabbableKey', entityID, {});
var wearableData = getEntityCustomData('wearable', entityID, DEFAULT_WEARABLE_DATA);
var currentJointName = MyAvatar.getJointNames()[props.parentJointIndex];
wearableData.joints[currentJointName] = [props.localPosition, props.localRotation];

View file

@ -1006,7 +1006,7 @@ function MyController(hand) {
// else this thing isn't physical. grab it by reparenting it (but not if we've already
// grabbed it).
if (grabbableData.refCount < 1) {
if (refCount < 1) {
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
return;
} else {
@ -1120,7 +1120,6 @@ function MyController(hand) {
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() &&
this.hasPresetOffsets()) {
@ -1307,7 +1306,6 @@ function MyController(hand) {
this.nearGrabbing = function() {
var now = Date.now();
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
@ -1330,10 +1328,9 @@ function MyController(hand) {
var handRotation = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation();
var handPosition = this.getHandPosition();
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
var hasPresetPosition = false;
if (this.state != STATE_NEAR_GRABBING && this.hasPresetOffsets()) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
// if an object is "equipped" and has a predefined offset, use it.
this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false;
this.offsetPosition = this.getPresetPosition();
@ -1676,7 +1673,6 @@ function MyController(hand) {
};
this.activateEntity = function(entityID, grabbedProperties, wasLoaded) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA);
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
var now = Date.now();

View file

@ -0,0 +1,60 @@
// photo_sphere.js
//
// Created by James B. Pollack @imgntn on 3/11/2015
// Copyright 2016 High Fidelity, Inc.
//
// This script creates a photo sphere around you.
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var photoSphere, light;
//equirectangular
var url = 'http://hifi-content.s3.amazonaws.com/james/projection_objects/IMG_9167.JPG';
var MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/projection_objects/photosphere2.fbx';
function createPhotoSphere() {
var textureString = 'photo:"' + url + '"'
var properties = {
type: 'Model',
modelURL: MODEL_URL,
name: 'hifi-photo-sphere',
dimensions: {
x: 32,
y: 32,
z: 32
},
position: MyAvatar.position,
textures: textureString
}
photoSphere = Entities.addEntity(properties);
}
function createLight() {
var properties = {
name: 'hifi-photo-sphere-light',
type: 'Light',
dimensions: {
x: 36,
y: 36,
z: 36,
},
intensity: 4.0,
falloffRadius: 22,
position: MyAvatar.position
}
light = Entities.addEntity(properties);
}
function cleanup() {
Entities.deleteEntity(photoSphere);
Entities.deleteEntity(light);
}
Script.scriptEnding.connect(cleanup);
createPhotoSphere();
createLight();

View file

@ -243,6 +243,41 @@
}
function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, defaultValue) {
var properties = {};
var parsedData = {};
try {
parsedData = JSON.parse(userDataElement.value);
} catch(e) {}
if (!(groupName in parsedData)) {
parsedData[groupName] = {}
}
delete parsedData[groupName][keyName];
if (checkBoxElement.checked !== defaultValue) {
parsedData[groupName][keyName] = checkBoxElement.checked;
}
if (Object.keys(parsedData[groupName]).length == 0) {
delete parsedData[groupName];
}
if (Object.keys(parsedData).length > 0) {
properties['userData'] = JSON.stringify(parsedData);
} else {
properties['userData'] = '';
}
userDataElement.value = properties['userData'];
EventBridge.emitWebEvent(
JSON.stringify({
type: "update",
properties: properties,
})
);
};
function loaded() {
openEventBridge(function() {
var allSections = [];
@ -305,6 +340,11 @@
var elCollideMyAvatar = document.getElementById("property-collide-myAvatar");
var elCollideOtherAvatar = document.getElementById("property-collide-otherAvatar");
var elCollisionSoundURL = document.getElementById("property-collision-sound-url");
var elGrabbable = document.getElementById("property-grabbable");
var elWantsTrigger = document.getElementById("property-wants-trigger");
var elIgnoreIK = document.getElementById("property-ignore-ik");
var elLifetime = document.getElementById("property-lifetime");
var elScriptURL = document.getElementById("property-script-url");
var elScriptTimestamp = document.getElementById("property-script-timestamp");
@ -408,7 +448,7 @@
var elXTextureURL = document.getElementById("property-x-texture-url");
var elYTextureURL = document.getElementById("property-y-texture-url");
var elZTextureURL = document.getElementById("property-z-texture-url");
var elPreviewCameraButton = document.getElementById("preview-camera-button");
if (window.EventBridge !== undefined) {
@ -518,13 +558,30 @@
elCollisionless.checked = properties.collisionless;
elDynamic.checked = properties.dynamic;
elCollideStatic.checked = properties.collidesWith.indexOf("static") > -1;
elCollideKinematic.checked = properties.collidesWith.indexOf("kinematic") > -1;
elCollideDynamic.checked = properties.collidesWith.indexOf("dynamic") > -1;
elCollideMyAvatar.checked = properties.collidesWith.indexOf("myAvatar") > -1;
elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1;
elGrabbable.checked = properties.dynamic;
elWantsTrigger.checked = false;
elIgnoreIK.checked = false;
var parsedUserData = {}
try {
parsedUserData = JSON.parse(properties.userData);
} catch(e) {}
if ("grabbableKey" in parsedUserData) {
if ("grabbable" in parsedUserData["grabbableKey"]) {
elGrabbable.checked = parsedUserData["grabbableKey"].grabbable;
}
if ("wantsTrigger" in parsedUserData["grabbableKey"]) {
elWantsTrigger.checked = parsedUserData["grabbableKey"].wantsTrigger;
}
if ("ignoreIK" in parsedUserData["grabbableKey"]) {
elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK;
}
}
elCollisionSoundURL.value = properties.collisionSoundURL;
elLifetime.value = properties.lifetime;
@ -737,9 +794,6 @@
elCollisionless.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionless'));
elDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('dynamic'));
elCollideDynamic.addEventListener('change', function() {
updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideDynamic, 'dynamic');
});
@ -758,6 +812,15 @@
updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideOtherAvatar, 'otherAvatar');
});
elGrabbable.addEventListener('change', function() {
userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic);
});
elWantsTrigger.addEventListener('change', function() {
userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false);
});
elIgnoreIK.addEventListener('change', function() {
userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, false);
});
elCollisionSoundURL.addEventListener('change', createEmitTextPropertyUpdateFunction('collisionSoundURL'));
@ -954,7 +1017,7 @@
action: "previewCamera"
}));
});
window.onblur = function() {
// Fake a change event
var ev = document.createEvent("HTMLEvents");
@ -1476,6 +1539,29 @@
</div>
</div>
<div class = "sub-section-header"> Grabbable: </div>
<div class = "sub-props-checkbox-group">
<div class="property">
<span class="label">grabbable</span>
<span class="value">
<input type='checkbox' id="property-grabbable">
</span>
</div>
<div class="property">
<span class="label">triggerable</span>
<span class="value">
<input type='checkbox' id="property-wants-trigger">
</span>
</div>
<div class="property">
<span class="label">ignore inverse-kinematics</span>
<span class="value">
<input type='checkbox' id="property-ignore-ik">
</span>
</div>
</div>
</div>

View file

@ -36,11 +36,11 @@ var AUDIO_LISTENER_MODE_CUSTOM = "Audio from custom position";
// be sure that the audio listener options are in the right order (same as the enumerator)
var AUDIO_LISTENER_OPTIONS = [
// MyAvatar.FROM_HEAD (0)
// MyAvatar.audioListenerModeHead (0)
AUDIO_LISTENER_MODE_FROM_HEAD,
// MyAvatar.FROM_CAMERA (1)
// MyAvatar.audioListenerModeCamera (1)
AUDIO_LISTENER_MODE_FROM_CAMERA,
// MyAvatar.CUSTOM (2)
// MyAvatar.audioListenerCustom (2)
AUDIO_LISTENER_MODE_CUSTOM
];
var AUDIO_STEREO_INPUT = "Stereo Input";

View file

@ -166,7 +166,8 @@ Item {
color: root.fontColor;
font.pixelSize: root.fontSize
visible: root.expanded;
text: "Downloads: ";
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +
", Pending: " + root.downloadsPending;
}
}
}

View file

@ -2058,18 +2058,21 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::MiniMirror);
} else {
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror));
if (!Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
bool isMirrorChecked = Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror);
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !isMirrorChecked);
if (isMirrorChecked) {
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
}
cameraMenuChanged();
}
break;
case Qt::Key_P:
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson));
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson));
cameraMenuChanged();
break;
case Qt::Key_P: {
bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson);
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked);
cameraMenuChanged();
break;
}
case Qt::Key_Slash:
Menu::getInstance()->triggerOption(MenuOption::Stats);

View file

@ -972,7 +972,6 @@ void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, g
glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin));
perpSin = glm::cross(perpCos, axis);
float anglea = 0.0f;
float angleb = 0.0f;
QVector<glm::vec3> points;
@ -980,7 +979,7 @@ void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, g
// the rectangles that comprise the sides of the cone section are
// referenced by "a" and "b" in one dimension, and "1", and "2" in the other dimension.
anglea = angleb;
int anglea = angleb;
angleb = ((float)(i+1) / (float)NUM_BODY_CONE_SIDES) * TWO_PI;
float sa = sinf(anglea);

View file

@ -57,7 +57,7 @@ class Avatar : public AvatarData {
Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
public:
Avatar(RigPointer rig = nullptr);
explicit Avatar(RigPointer rig = nullptr);
~Avatar();
typedef render::Payload<AvatarData> Payload;

View file

@ -308,7 +308,6 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
hand = _hand;
}
ok = true;
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
holderID = myAvatar->getSessionUUID();

View file

@ -63,11 +63,11 @@ void AvatarManager::registerMetaTypes(QScriptEngine* engine) {
}
AvatarManager::AvatarManager(QObject* parent) :
_avatarFades()
_avatarFades(),
_myAvatar(std::make_shared<MyAvatar>(std::make_shared<Rig>()))
{
// register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
_myAvatar = std::make_shared<MyAvatar>(std::make_shared<Rig>());
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");

View file

@ -71,8 +71,8 @@ public slots:
void updateAvatarRenderStatus(bool shouldRenderAvatars);
private:
AvatarManager(QObject* parent = 0);
AvatarManager(const AvatarManager& other);
explicit AvatarManager(QObject* parent = 0);
explicit AvatarManager(const AvatarManager& other);
void simulateAvatarFades(float deltaTime);

View file

@ -16,7 +16,7 @@
#include <display-plugins/DisplayPlugin.h>
#include "InterfaceLogging.h"
AvatarUpdate::AvatarUpdate() : GenericThread(), _lastAvatarUpdate(0) {
AvatarUpdate::AvatarUpdate() : GenericThread(), _lastAvatarUpdate(0), _isHMDMode(false) {
setObjectName("Avatar Update"); // GenericThread::initialize uses this to set the thread name.
Settings settings;
const int DEFAULT_TARGET_AVATAR_SIMRATE = 60;

View file

@ -28,7 +28,7 @@ class Avatar;
class Head : public HeadData {
public:
Head(Avatar* owningAvatar);
explicit Head(Avatar* owningAvatar);
void init();
void reset();

View file

@ -1522,9 +1522,9 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
// (1) braking --> short timescale (aggressive motor assertion)
// (2) pushing --> medium timescale (mild motor assertion)
// (3) inactive --> long timescale (gentle friction for low speeds)
float MIN_KEYBOARD_MOTOR_TIMESCALE = 0.125f;
float MAX_KEYBOARD_MOTOR_TIMESCALE = 0.4f;
float MIN_KEYBOARD_BRAKE_SPEED = 0.3f;
const float MIN_KEYBOARD_MOTOR_TIMESCALE = 0.125f;
const float MAX_KEYBOARD_MOTOR_TIMESCALE = 0.4f;
const float MIN_KEYBOARD_BRAKE_SPEED = 0.3f;
float timescale = MAX_KEYBOARD_MOTOR_TIMESCALE;
bool isThrust = (glm::length2(_thrust) > EPSILON);
if (_isPushing || isThrust ||
@ -1787,7 +1787,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
// orient the user to face the target
glm::quat quatOrientation = newOrientation;
glm::quat quatOrientation = cancelOutRollAndPitch(newOrientation);
if (shouldFaceLocation) {
quatOrientation = newOrientation * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));

View file

@ -63,9 +63,9 @@ class MyAvatar : public Avatar {
Q_PROPERTY(AudioListenerMode audioListenerMode READ getAudioListenerMode WRITE setAudioListenerMode)
Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
Q_PROPERTY(glm::quat customListenOrientation READ getCustomListenOrientation WRITE setCustomListenOrientation)
Q_PROPERTY(AudioListenerMode FROM_HEAD READ getAudioListenerModeHead)
Q_PROPERTY(AudioListenerMode FROM_CAMERA READ getAudioListenerModeCamera)
Q_PROPERTY(AudioListenerMode CUSTOM READ getAudioListenerModeCustom)
Q_PROPERTY(AudioListenerMode audioListenerModeHead READ getAudioListenerModeHead)
Q_PROPERTY(AudioListenerMode audioListenerModeCamera READ getAudioListenerModeCamera)
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
@ -84,7 +84,7 @@ class MyAvatar : public Avatar {
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
public:
MyAvatar(RigPointer rig);
explicit MyAvatar(RigPointer rig);
~MyAvatar();
virtual void simulateAttachments(float deltaTime) override;

View file

@ -46,14 +46,15 @@ void MyCharacterController::updateShapeIfNecessary() {
// NOTE: _shapeLocalOffset is already computed
if (_radius > 0.0f) {
// HACK: use some simple mass property defaults for now
float mass = 100.0f;
btVector3 inertia(30.0f, 8.0f, 30.0f);
// create RigidBody if it doesn't exist
if (!_rigidBody) {
// HACK: use some simple mass property defaults for now
const float DEFAULT_AVATAR_MASS = 100.0f;
const btVector3 DEFAULT_AVATAR_INERTIA_TENSOR(30.0f, 8.0f, 30.0f);
btCollisionShape* shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
_rigidBody = new btRigidBody(mass, nullptr, shape, inertia);
_rigidBody = new btRigidBody(DEFAULT_AVATAR_MASS, nullptr, shape, DEFAULT_AVATAR_INERTIA_TENSOR);
} else {
btCollisionShape* shape = _rigidBody->getCollisionShape();
if (shape) {

View file

@ -21,7 +21,7 @@ class MyAvatar;
class MyCharacterController : public CharacterController {
public:
MyCharacterController(MyAvatar* avatar);
explicit MyCharacterController(MyAvatar* avatar);
~MyCharacterController ();
virtual void updateShapeIfNecessary() override;

View file

@ -193,6 +193,7 @@ void Stats::updateStats(bool force) {
}
STAT_UPDATE(downloads, ResourceCache::getLoadingRequests().size());
STAT_UPDATE(downloadLimit, ResourceCache::getRequestLimit())
STAT_UPDATE(downloadsPending, ResourceCache::getPendingRequestCount());
// TODO fix to match original behavior
//stringstream downloads;

View file

@ -56,6 +56,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, audioMixerKbps, 0)
STATS_PROPERTY(int, audioMixerPps, 0)
STATS_PROPERTY(int, downloads, 0)
STATS_PROPERTY(int, downloadLimit, 0)
STATS_PROPERTY(int, downloadsPending, 0)
STATS_PROPERTY(int, triangles, 0)
STATS_PROPERTY(int, quads, 0)
@ -135,6 +136,7 @@ signals:
void audioMixerKbpsChanged();
void audioMixerPpsChanged();
void downloadsChanged();
void downloadLimitChanged();
void downloadsPendingChanged();
void trianglesChanged();
void quadsChanged();

View file

@ -21,7 +21,7 @@
class AnimExpression {
public:
friend class AnimTests;
AnimExpression(const QString& str);
explicit AnimExpression(const QString& str);
protected:
struct Token {
enum Type {
@ -49,8 +49,8 @@ protected:
Comma,
Error
};
Token(Type type) : type {type} {}
Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
explicit Token(Type type) : type {type} {}
explicit Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
explicit Token(int val) : type {Type::Int}, intVal {val} {}
explicit Token(bool val) : type {Type::Bool}, intVal {val} {}
explicit Token(float val) : type {Type::Float}, floatVal {val} {}
@ -82,7 +82,7 @@ protected:
Modulus,
UnaryMinus
};
OpCode(Type type) : type {type} {}
explicit OpCode(Type type) : type {type} {}
explicit OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
explicit OpCode(const QString& str) : type {Type::Identifier}, strVal {str} {}
explicit OpCode(int val) : type {Type::Int}, intVal {val} {}

View file

@ -23,6 +23,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN
AnimInverseKinematics::~AnimInverseKinematics() {
clearConstraints();
_accumulators.clear();
_targetVarVec.clear();
}
void AnimInverseKinematics::loadDefaultPoses(const AnimPoseVec& poses) {
@ -394,6 +396,17 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
}
_relativePoses[i].trans = underPoses[i].trans;
}
if (!_relativePoses.empty()) {
// Sometimes the underpose itself can violate the constraints. Rather than
// clamp the animation we dynamically expand each constraint to accomodate it.
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
while (constraintItr != _constraints.end()) {
int index = constraintItr->first;
constraintItr->second->dynamicallyAdjustLimits(_relativePoses[index].rot);
++constraintItr;
}
}
}
if (!_relativePoses.empty()) {

View file

@ -25,7 +25,7 @@ class RotationConstraint;
class AnimInverseKinematics : public AnimNode {
public:
AnimInverseKinematics(const QString& id);
explicit AnimInverseKinematics(const QString& id);
virtual ~AnimInverseKinematics() override;
void loadDefaultPoses(const AnimPoseVec& poses);

View file

@ -25,7 +25,7 @@ class AnimNodeLoader : public QObject {
Q_OBJECT
public:
AnimNodeLoader(const QUrl& url);
explicit AnimNodeLoader(const QUrl& url);
signals:
void success(AnimNode::Pointer node);

View file

@ -23,8 +23,8 @@ public:
using Pointer = std::shared_ptr<AnimSkeleton>;
using ConstPointer = std::shared_ptr<const AnimSkeleton>;
AnimSkeleton(const FBXGeometry& fbxGeometry);
AnimSkeleton(const std::vector<FBXJoint>& joints);
explicit AnimSkeleton(const FBXGeometry& fbxGeometry);
explicit AnimSkeleton(const std::vector<FBXJoint>& joints);
int nameToJointIndex(const QString& jointName) const;
const QString& getJointName(int jointIndex) const;
int getNumJoints() const;

View file

@ -110,7 +110,7 @@ protected:
public:
AnimStateMachine(const QString& id);
explicit AnimStateMachine(const QString& id);
virtual ~AnimStateMachine() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;

View file

@ -37,12 +37,12 @@ public:
static const AnimVariant False;
AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); }
AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; }
AnimVariant(float value) : _type(Type::Float) { _val.floats[0] = value; }
AnimVariant(const glm::vec3& value) : _type(Type::Vec3) { *reinterpret_cast<glm::vec3*>(&_val) = value; }
AnimVariant(const glm::quat& value) : _type(Type::Quat) { *reinterpret_cast<glm::quat*>(&_val) = value; }
AnimVariant(const QString& value) : _type(Type::String) { _stringVal = value; }
explicit AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
explicit AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; }
explicit AnimVariant(float value) : _type(Type::Float) { _val.floats[0] = value; }
explicit AnimVariant(const glm::vec3& value) : _type(Type::Vec3) { *reinterpret_cast<glm::vec3*>(&_val) = value; }
explicit AnimVariant(const glm::quat& value) : _type(Type::Quat) { *reinterpret_cast<glm::quat*>(&_val) = value; }
explicit AnimVariant(const QString& value) : _type(Type::String) { _stringVal = value; }
bool isBool() const { return _type == Type::Bool; }
bool isInt() const { return _type == Type::Int; }
@ -250,7 +250,7 @@ public:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
break;
default:
assert("AnimVariant::Type" == "valid");
assert(("invalid AnimVariant::Type", false));
}
}
}

View file

@ -38,7 +38,7 @@ protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
private:
AnimationCache(QObject* parent = NULL);
explicit AnimationCache(QObject* parent = NULL);
virtual ~AnimationCache() { }
};
@ -51,7 +51,7 @@ class Animation : public Resource {
public:
Animation(const QUrl& url);
explicit Animation(const QUrl& url);
const FBXGeometry& getGeometry() const { return *_geometry; }

View file

@ -40,6 +40,7 @@ AnimationLoop::AnimationLoop(const AnimationDetails& animationDetails) :
_lastFrame(animationDetails.lastFrame),
_running(animationDetails.running),
_currentFrame(animationDetails.currentFrame),
_maxFrameIndexHint(MAXIMUM_POSSIBLE_FRAME),
_resetOnRunning(true),
_lastSimulated(usecTimestampNow())
{
@ -55,6 +56,7 @@ AnimationLoop::AnimationLoop(float fps, bool loop, bool hold, bool startAutomati
_lastFrame(lastFrame),
_running(running),
_currentFrame(currentFrame),
_maxFrameIndexHint(MAXIMUM_POSSIBLE_FRAME),
_resetOnRunning(true),
_lastSimulated(usecTimestampNow())
{

View file

@ -19,7 +19,7 @@ public:
static const float MAXIMUM_POSSIBLE_FRAME;
AnimationLoop();
AnimationLoop(const AnimationDetails& animationDetails);
explicit AnimationLoop(const AnimationDetails& animationDetails);
AnimationLoop(float fps, bool loop, bool hold, bool startAutomatically, float firstFrame,
float lastFrame, bool running, float currentFrame);

View file

@ -37,17 +37,7 @@ static bool isEqual(const glm::quat& p, const glm::quat& q) {
return 1.0f - fabsf(glm::dot(p, q)) <= EPSILON;
}
#ifdef NDEBUG
#define ASSERT(cond)
#else
#define ASSERT(cond) \
do { \
if (!(cond)) { \
int* ptr = nullptr; \
*ptr = 10; \
} \
} while (0)
#endif
#define ASSERT(cond) assert(cond)
// 2 meter tall dude
const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f);

View file

@ -83,6 +83,7 @@ public:
Hover
};
Rig() {}
virtual ~Rig() {}
void destroyAnimGraph();

View file

@ -31,6 +31,10 @@ public:
/// \return true if this constraint is part of lower spine
virtual bool isLowerSpine() const { return false; }
/// \param rotation rotation to allow
/// \brief clear previous adjustment and adjust constraint limits to allow rotation
virtual void dynamicallyAdjustLimits(const glm::quat& rotation) {}
protected:
glm::quat _referenceRotation = glm::quat();
};

View file

@ -13,6 +13,7 @@
#include <math.h>
#include <GeometryUtil.h>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
@ -24,32 +25,152 @@ const int LAST_CLAMP_NO_BOUNDARY = 0;
const int LAST_CLAMP_HIGH_BOUNDARY = 1;
SwingTwistConstraint::SwingLimitFunction::SwingLimitFunction() {
setCone(PI);
_minDots.push_back(-1.0f);
_minDots.push_back(-1.0f);
_minDotIndexA = -1;
_minDotIndexB = -1;
}
void SwingTwistConstraint::SwingLimitFunction::setCone(float maxAngle) {
_minDots.clear();
float minDot = glm::clamp(maxAngle, MIN_MINDOT, MAX_MINDOT);
_minDots.push_back(minDot);
// push the first value to the back to establish cyclic boundary conditions
_minDots.push_back(minDot);
}
// In order to support the dynamic adjustment to swing limits we require
// that minDots have a minimum number of elements:
const int MIN_NUM_DOTS = 8;
void SwingTwistConstraint::SwingLimitFunction::setMinDots(const std::vector<float>& minDots) {
uint32_t numDots = (uint32_t)minDots.size();
int numDots = (int)minDots.size();
_minDots.clear();
if (numDots == 0) {
// push two copies of MIN_MINDOT
_minDots.push_back(MIN_MINDOT);
// push multiple copies of MIN_MINDOT
for (int i = 0; i < MIN_NUM_DOTS; ++i) {
_minDots.push_back(MIN_MINDOT);
}
// push one more for cyclic boundary conditions
_minDots.push_back(MIN_MINDOT);
} else {
_minDots.reserve(numDots);
for (uint32_t i = 0; i < numDots; ++i) {
_minDots.push_back(glm::clamp(minDots[i], MIN_MINDOT, MAX_MINDOT));
// for minimal fidelity in the dynamic adjustment we expand the swing limit data until
// we have enough data points
int trueNumDots = numDots;
int numFiller = 0;
while(trueNumDots < MIN_NUM_DOTS) {
numFiller++;
trueNumDots += numDots;
}
// push the first value to the back to establish cyclic boundary conditions
_minDots.reserve(trueNumDots);
for (int i = 0; i < numDots; ++i) {
// push the next value
_minDots.push_back(glm::clamp(minDots[i], MIN_MINDOT, MAX_MINDOT));
if (numFiller > 0) {
// compute endpoints of line segment
float nearDot = glm::clamp(minDots[i], MIN_MINDOT, MAX_MINDOT);
int k = (i + 1) % numDots;
float farDot = glm::clamp(minDots[k], MIN_MINDOT, MAX_MINDOT);
// fill the gap with interpolated values
for (int j = 0; j < numFiller; ++j) {
float delta = (float)(j + 1) / float(numFiller + 1);
_minDots.push_back((1.0f - delta) * nearDot + delta * farDot);
}
}
}
// push the first value to the back to for cyclic boundary conditions
_minDots.push_back(_minDots[0]);
}
_minDotIndexA = -1;
_minDotIndexB = -1;
}
/// \param angle radian angle to update
/// \param minDotAdjustment minimum dot limit at that angle
void SwingTwistConstraint::SwingLimitFunction::dynamicallyAdjustMinDots(float theta, float minDotAdjustment) {
// What does "dynamic adjustment" mean?
//
// Consider a limitFunction that looks like this:
//
// 1+
// | valid space
// |
// +-----+-----+-----+-----+-----+-----+-----+-----+
// |
// | invalid space
// 0+------------------------------------------------
// 0 pi/2 pi 3pi/2 2pi
// theta --->
//
// If we wanted to modify the envelope to accept a single invalid point X
// then we would need to modify neighboring values A and B accordingly:
//
// 1+ adjustment for X at some thetaX
// | |
// | |
// +-----+. V .+-----+-----+-----+-----+
// | - -
// | ' A--X--B '
// 0+------------------------------------------------
// 0 pi/2 pi 3pi/2 2pi
//
// The code below computes the values of A and B such that the line between them
// passes through the point X, and we get reasonable interpolation for nearby values
// of theta. The old AB values are saved for later restore.
if (_minDotIndexA > -1) {
// retstore old values
_minDots[_minDotIndexA] = _minDotA;
_minDots[_minDotIndexB] = _minDotB;
// handle cyclic boundary conditions
int lastIndex = (int)_minDots.size() - 1;
if (_minDotIndexA == 0) {
_minDots[lastIndex] = _minDotA;
} else if (_minDotIndexB == lastIndex) {
_minDots[0] = _minDotB;
}
}
// extract the positive normalized fractional part of the theta
float integerPart;
float normalizedAngle = modff(theta / TWO_PI, &integerPart);
if (normalizedAngle < 0.0f) {
normalizedAngle += 1.0f;
}
// interpolate between the two nearest points in the curve
float delta = modff(normalizedAngle * (float)(_minDots.size() - 1), &integerPart);
int indexA = (int)(integerPart);
int indexB = (indexA + 1) % _minDots.size();
float interpolatedDot = _minDots[indexA] * (1.0f - delta) + _minDots[indexB] * delta;
if (minDotAdjustment < interpolatedDot) {
// minDotAdjustment is outside the existing bounds so we must modify
// remember the indices
_minDotIndexA = indexA;
_minDotIndexB = indexB;
// save the old minDots
_minDotA = _minDots[_minDotIndexA];
_minDotB = _minDots[_minDotIndexB];
// compute replacement values to _minDots that will provide a line segment
// that passes through minDotAdjustment while balancing the distortion between A and B.
// Note: the derivation of these formulae is left as an exercise to the reader.
float twiceUndershoot = 2.0f * (minDotAdjustment - interpolatedDot);
_minDots[_minDotIndexA] -= twiceUndershoot * (delta + 0.5f) * (delta - 1.0f);
_minDots[_minDotIndexB] -= twiceUndershoot * delta * (delta - 1.5f);
// handle cyclic boundary conditions
int lastIndex = (int)_minDots.size() - 1;
if (_minDotIndexA == 0) {
_minDots[lastIndex] = _minDots[_minDotIndexA];
} else if (_minDotIndexB == lastIndex) {
_minDots[0] = _minDots[_minDotIndexB];
}
} else {
// minDotAdjustment is inside bounds so there is nothing to do
_minDotIndexA = -1;
_minDotIndexB = -1;
}
}
float SwingTwistConstraint::SwingLimitFunction::getMinDot(float theta) const {
@ -90,15 +211,14 @@ void SwingTwistConstraint::setSwingLimits(const std::vector<glm::vec3>& swungDir
};
std::vector<SwingLimitData> limits;
uint32_t numLimits = (uint32_t)swungDirections.size();
int numLimits = (int)swungDirections.size();
limits.reserve(numLimits);
// compute the limit pairs: <theta, minDot>
const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
for (uint32_t i = 0; i < numLimits; ++i) {
for (int i = 0; i < numLimits; ++i) {
float directionLength = glm::length(swungDirections[i]);
if (directionLength > EPSILON) {
glm::vec3 swingAxis = glm::cross(yAxis, swungDirections[i]);
glm::vec3 swingAxis = glm::cross(Vectors::UNIT_Y, swungDirections[i]);
float theta = atan2f(-swingAxis.z, swingAxis.x);
if (theta < 0.0f) {
theta += TWO_PI;
@ -108,7 +228,7 @@ void SwingTwistConstraint::setSwingLimits(const std::vector<glm::vec3>& swungDir
}
std::vector<float> minDots;
numLimits = (uint32_t)limits.size();
numLimits = (int)limits.size();
if (numLimits == 0) {
// trivial case: nearly free constraint
std::vector<float> minDots;
@ -126,10 +246,10 @@ void SwingTwistConstraint::setSwingLimits(const std::vector<glm::vec3>& swungDir
// extrapolate evenly distributed limits for fast lookup table
float deltaTheta = TWO_PI / (float)(numLimits);
uint32_t rightIndex = 0;
for (uint32_t i = 0; i < numLimits; ++i) {
int rightIndex = 0;
for (int i = 0; i < numLimits; ++i) {
float theta = (float)i * deltaTheta;
uint32_t leftIndex = (rightIndex - 1) % numLimits;
int leftIndex = (rightIndex - 1 + numLimits) % numLimits;
while (rightIndex < numLimits && theta > limits[rightIndex]._theta) {
leftIndex = rightIndex++;
}
@ -165,51 +285,57 @@ void SwingTwistConstraint::setTwistLimits(float minTwist, float maxTwist) {
_maxTwist = glm::max(minTwist, maxTwist);
_lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY;
_twistAdjusted = false;
}
// private
float SwingTwistConstraint::handleTwistBoundaryConditions(float twistAngle) const {
// adjust measured twistAngle according to clamping history
switch (_lastTwistBoundary) {
case LAST_CLAMP_LOW_BOUNDARY:
// clamp to min
if (twistAngle > _maxTwist) {
twistAngle -= TWO_PI;
}
break;
case LAST_CLAMP_HIGH_BOUNDARY:
// clamp to max
if (twistAngle < _minTwist) {
twistAngle += TWO_PI;
}
break;
default: // LAST_CLAMP_NO_BOUNDARY
// clamp to nearest boundary
float midBoundary = 0.5f * (_maxTwist + _minTwist + TWO_PI);
if (twistAngle > midBoundary) {
// lower boundary is closer --> phase down one cycle
twistAngle -= TWO_PI;
} else if (twistAngle < midBoundary - TWO_PI) {
// higher boundary is closer --> phase up one cycle
twistAngle += TWO_PI;
}
break;
}
return twistAngle;
}
bool SwingTwistConstraint::apply(glm::quat& rotation) const {
// decompose the rotation into first twist about yAxis, then swing about something perp
const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
// NOTE: rotation = postRotation * referenceRotation
glm::quat postRotation = rotation * glm::inverse(_referenceRotation);
glm::quat swingRotation, twistRotation;
swingTwistDecomposition(postRotation, yAxis, swingRotation, twistRotation);
swingTwistDecomposition(postRotation, Vectors::UNIT_Y, swingRotation, twistRotation);
// NOTE: postRotation = swingRotation * twistRotation
// compute twistAngle
// compute raw twistAngle
float twistAngle = 2.0f * acosf(fabsf(twistRotation.w));
const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
glm::vec3 twistedX = twistRotation * xAxis;
twistAngle *= copysignf(1.0f, glm::dot(glm::cross(xAxis, twistedX), yAxis));
glm::vec3 twistedX = twistRotation * Vectors::UNIT_X;
twistAngle *= copysignf(1.0f, glm::dot(glm::cross(Vectors::UNIT_X, twistedX), Vectors::UNIT_Y));
bool somethingClamped = false;
if (_minTwist != _maxTwist) {
// adjust measured twistAngle according to clamping history
switch (_lastTwistBoundary) {
case LAST_CLAMP_LOW_BOUNDARY:
// clamp to min
if (twistAngle > _maxTwist) {
twistAngle -= TWO_PI;
}
break;
case LAST_CLAMP_HIGH_BOUNDARY:
// clamp to max
if (twistAngle < _minTwist) {
twistAngle += TWO_PI;
}
break;
default: // LAST_CLAMP_NO_BOUNDARY
// clamp to nearest boundary
float midBoundary = 0.5f * (_maxTwist + _minTwist + TWO_PI);
if (twistAngle > midBoundary) {
// lower boundary is closer --> phase down one cycle
twistAngle -= TWO_PI;
} else if (twistAngle < midBoundary - TWO_PI) {
// higher boundary is closer --> phase up one cycle
twistAngle += TWO_PI;
}
break;
}
// twist limits apply --> figure out which limit we're hitting, if any
twistAngle = handleTwistBoundaryConditions(twistAngle);
// clamp twistAngle
float clampedTwistAngle = glm::clamp(twistAngle, _minTwist, _maxTwist);
@ -226,15 +352,15 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const {
// clamp the swing
// The swingAxis is always perpendicular to the reference axis (yAxis in the constraint's frame).
glm::vec3 swungY = swingRotation * yAxis;
glm::vec3 swingAxis = glm::cross(yAxis, swungY);
glm::vec3 swungY = swingRotation * Vectors::UNIT_Y;
glm::vec3 swingAxis = glm::cross(Vectors::UNIT_Y, swungY);
float axisLength = glm::length(swingAxis);
if (axisLength > EPSILON) {
// The limit of swing is a function of "theta" which can be computed from the swingAxis
// (which is in the constraint's ZX plane).
float theta = atan2f(-swingAxis.z, swingAxis.x);
float minDot = _swingLimitFunction.getMinDot(theta);
if (glm::dot(swungY, yAxis) < minDot) {
if (glm::dot(swungY, Vectors::UNIT_Y) < minDot) {
// The swing limits are violated so we extract the angle from midDot and
// use it to supply a new rotation.
swingAxis /= axisLength;
@ -245,13 +371,53 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const {
if (somethingClamped) {
// update the rotation
twistRotation = glm::angleAxis(twistAngle, yAxis);
twistRotation = glm::angleAxis(twistAngle, Vectors::UNIT_Y);
rotation = swingRotation * twistRotation * _referenceRotation;
return true;
}
return false;
}
void SwingTwistConstraint::dynamicallyAdjustLimits(const glm::quat& rotation) {
glm::quat postRotation = rotation * glm::inverse(_referenceRotation);
glm::quat swingRotation, twistRotation;
swingTwistDecomposition(postRotation, Vectors::UNIT_Y, swingRotation, twistRotation);
// adjust swing limits
glm::vec3 swungY = swingRotation * Vectors::UNIT_Y;
glm::vec3 swingAxis = glm::cross(Vectors::UNIT_Y, swungY);
float theta = atan2f(-swingAxis.z, swingAxis.x);
_swingLimitFunction.dynamicallyAdjustMinDots(theta, swungY.y);
// restore twist limits
if (_twistAdjusted) {
_minTwist = _oldMinTwist;
_maxTwist = _oldMaxTwist;
_twistAdjusted = false;
}
if (_minTwist != _maxTwist) {
// compute twistAngle
float twistAngle = 2.0f * acosf(fabsf(twistRotation.w));
glm::vec3 twistedX = twistRotation * Vectors::UNIT_X;
twistAngle *= copysignf(1.0f, glm::dot(glm::cross(Vectors::UNIT_X, twistedX), Vectors::UNIT_Y));
twistAngle = handleTwistBoundaryConditions(twistAngle);
if (twistAngle < _minTwist || twistAngle > _maxTwist) {
// expand twist limits
_twistAdjusted = true;
_oldMinTwist = _minTwist;
_oldMaxTwist = _maxTwist;
if (twistAngle < _minTwist) {
_minTwist = twistAngle;
} else if (twistAngle > _maxTwist) {
_maxTwist = twistAngle;
}
}
}
}
void SwingTwistConstraint::clearHistory() {
_lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY;
}

View file

@ -53,24 +53,45 @@ public:
void setLowerSpine(bool lowerSpine) { _lowerSpine = lowerSpine; }
virtual bool isLowerSpine() const override { return _lowerSpine; }
/// \param rotation rotation to allow
/// \brief clear previous adjustment and adjust constraint limits to allow rotation
virtual void dynamicallyAdjustLimits(const glm::quat& rotation) override;
// for testing purposes
const std::vector<float>& getMinDots() { return _swingLimitFunction.getMinDots(); }
// SwingLimitFunction is an implementation of the constraint check described in the paper:
// "The Parameterization of Joint Rotation with the Unit Quaternion" by Quang Liu and Edmond C. Prakash
//
// The "dynamic adjustment" feature allows us to change the limits on the fly for one particular theta angle.
//
class SwingLimitFunction {
public:
SwingLimitFunction();
/// \brief use a uniform conical swing limit
void setCone(float maxAngle);
/// \brief use a vector of lookup values for swing limits
void setMinDots(const std::vector<float>& minDots);
/// \param theta radian angle to new minDot
/// \param minDot minimum dot limit
/// \brief updates swing constraint to permit minDot at theta
void dynamicallyAdjustMinDots(float theta, float minDot);
/// \return minimum dotProduct between reference and swung axes
float getMinDot(float theta) const;
protected:
// for testing purposes
const std::vector<float>& getMinDots() { return _minDots; }
private:
// the limits are stored in a lookup table with cyclic boundary conditions
std::vector<float> _minDots;
// these values used to restore dynamic adjustment
float _minDotA;
float _minDotB;
int8_t _minDotIndexA;
int8_t _minDotIndexB;
};
/// \return reference to SwingLimitFunction instance for unit-testing
@ -79,15 +100,22 @@ public:
/// \brief exposed for unit testing
void clearHistory();
private:
float handleTwistBoundaryConditions(float twistAngle) const;
protected:
SwingLimitFunction _swingLimitFunction;
float _minTwist;
float _maxTwist;
float _oldMinTwist;
float _oldMaxTwist;
// We want to remember the LAST clamped boundary, so we an use it even when the far boundary is closer.
// This reduces "pops" when the input twist angle goes far beyond and wraps around toward the far boundary.
mutable int _lastTwistBoundary;
bool _lowerSpine { false };
bool _twistAdjusted { false };
};
#endif // hifi_SwingTwistConstraint_h

View file

@ -1115,7 +1115,7 @@ void AvatarData::detachOne(const QString& modelURL, const QString& jointName) {
return;
}
QVector<AttachmentData> attachmentData = getAttachmentData();
for (QVector<AttachmentData>::iterator it = attachmentData.begin(); it != attachmentData.end(); it++) {
for (QVector<AttachmentData>::iterator it = attachmentData.begin(); it != attachmentData.end(); ++it) {
if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) {
attachmentData.erase(it);
setAttachmentData(attachmentData);
@ -1134,7 +1134,7 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) {
it = attachmentData.erase(it);
} else {
it++;
++it;
}
}
setAttachmentData(attachmentData);

View file

@ -42,6 +42,8 @@ HeadData::HeadData(AvatarData* owningAvatar) :
_rightEyeBlink(0.0f),
_averageLoudness(0.0f),
_browAudioLift(0.0f),
_audioAverageLoudness(0.0f),
_pupilDilation(0.0f),
_owningAvatar(owningAvatar)
{

View file

@ -32,7 +32,7 @@ class QJsonObject;
class HeadData {
public:
HeadData(AvatarData* owningAvatar);
explicit HeadData(AvatarData* owningAvatar);
virtual ~HeadData() { };
// degrees

View file

@ -515,7 +515,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// we can confidently ignore this packet
EntityTreePointer tree = getTree();
if (tree && tree->isDeletedEntity(_id)) {
qDebug() << "Recieved packet for previously deleted entity [" << _id << "] ignoring. (inside " << __FUNCTION__ << ")";
#ifdef WANT_DEBUG
qDebug() << "Recieved packet for previously deleted entity [" << _id << "] ignoring. "
"(inside " << __FUNCTION__ << ")";
#endif
ignoreServerPacket = true;
}

View file

@ -56,6 +56,15 @@ void ResourceCache::refresh(const QUrl& url) {
}
}
void ResourceCache::setRequestLimit(int limit) {
_requestLimit = limit;
// Now go fill any new request spots
while (attemptHighestPriorityRequest()) {
// just keep looping until we reach the new limit or no more pending requests
}
}
void ResourceCache::getResourceAsynchronously(const QUrl& url) {
qCDebug(networking) << "ResourceCache::getResourceAsynchronously" << url.toString();
_resourcesToBeGottenLock.lockForWrite();
@ -150,31 +159,37 @@ void ResourceCache::clearUnusedResource() {
}
}
void ResourceCache::attemptRequest(Resource* resource) {
bool ResourceCache::attemptRequest(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
// Disable request limiting for ATP
if (resource->getURL().scheme() != URL_SCHEME_ATP) {
if (_requestLimit <= 0) {
if (_requestsActive >= _requestLimit) {
// wait until a slot becomes available
sharedItems->_pendingRequests.append(resource);
return;
return false;
}
--_requestLimit;
}
++_requestsActive;
}
sharedItems->_loadingRequests.append(resource);
resource->makeRequest();
return true;
}
void ResourceCache::requestCompleted(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
sharedItems->_loadingRequests.removeOne(resource);
if (resource->getURL().scheme() != URL_SCHEME_ATP) {
++_requestLimit;
--_requestsActive;
}
attemptHighestPriorityRequest();
}
bool ResourceCache::attemptHighestPriorityRequest() {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
// look for the highest priority pending request
int highestIndex = -1;
float highestPriority = -FLT_MAX;
@ -191,13 +206,12 @@ void ResourceCache::requestCompleted(Resource* resource) {
}
i++;
}
if (highestIndex >= 0) {
attemptRequest(sharedItems->_pendingRequests.takeAt(highestIndex));
}
return (highestIndex >= 0) && attemptRequest(sharedItems->_pendingRequests.takeAt(highestIndex));
}
const int DEFAULT_REQUEST_LIMIT = 10;
int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT;
int ResourceCache::_requestsActive = 0;
Resource::Resource(const QUrl& url, bool delayLoad) :
_url(url),

View file

@ -67,8 +67,10 @@ class ResourceCache : public QObject {
Q_OBJECT
public:
static void setRequestLimit(int limit) { _requestLimit = limit; }
static void setRequestLimit(int limit);
static int getRequestLimit() { return _requestLimit; }
static int getRequestsActive() { return _requestsActive; }
void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize);
qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; }
@ -105,8 +107,11 @@ protected:
void reserveUnusedResource(qint64 resourceSize);
void clearUnusedResource();
Q_INVOKABLE static void attemptRequest(Resource* resource);
/// Attempt to load a resource if requests are below the limit, otherwise queue the resource for loading
/// \return true if the resource began loading, otherwise false if the resource is in the pending queue
Q_INVOKABLE static bool attemptRequest(Resource* resource);
static void requestCompleted(Resource* resource);
static bool attemptHighestPriorityRequest();
private:
friend class Resource;
@ -115,6 +120,7 @@ private:
int _lastLRUKey = 0;
static int _requestLimit;
static int _requestsActive;
void getResourceAsynchronously(const QUrl& url);
QReadWriteLock _resourcesToBeGottenLock;

View file

@ -34,7 +34,6 @@ void CongestionControl::setPacketSendPeriod(double newSendPeriod) {
}
DefaultCC::DefaultCC() :
_slowStartLastAck(_sendCurrSeqNum),
_lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX })
{
_mss = udt::MAX_PACKET_SIZE_WITH_UDP_HEADER;
@ -63,11 +62,11 @@ void DefaultCC::onACK(SequenceNumber ackNum) {
if (_slowStart) {
// we are in slow start phase - increase the congestion window size by the number of packets just ACKed
_congestionWindowSize += seqlen(_slowStartLastAck, ackNum);
_congestionWindowSize += seqlen(_slowStartLastACK, ackNum);
// update the last ACK
_slowStartLastAck = ackNum;
_slowStartLastACK = ackNum;
// check if we can get out of slow start (is our new congestion window size bigger than the max)
if (_congestionWindowSize > _maxCongestionWindowSize) {
_slowStart = false;

View file

@ -50,6 +50,7 @@ protected:
void setMaxCongestionWindowSize(int window) { _maxCongestionWindowSize = window; }
void setBandwidth(int bandwidth) { _bandwidth = bandwidth; }
void setMaxBandwidth(int maxBandwidth) { _maxBandwidth = maxBandwidth; }
virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) = 0;
void setSendCurrentSequenceNumber(SequenceNumber seqNum) { _sendCurrSeqNum = seqNum; }
void setReceiveRate(int rate) { _receiveRate = rate; }
void setRTT(int rtt) { _rtt = rtt; }
@ -104,14 +105,17 @@ public:
virtual void onACK(SequenceNumber ackNum);
virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd);
virtual void onTimeout();
protected:
virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) { _slowStartLastACK = seqNum; }
private:
void stopSlowStart(); // stops the slow start on loss or timeout
p_high_resolution_clock::time_point _lastRCTime = p_high_resolution_clock::now(); // last rate increase time
bool _slowStart { true }; // if in slow start phase
SequenceNumber _slowStartLastAck; // last ACKed seq num
SequenceNumber _slowStartLastACK; // last ACKed seq num from previous slow start check
bool _loss { false }; // if loss happened since last rate increase
SequenceNumber _lastDecreaseMaxSeq; // max pkt seq num sent out when last decrease happened
double _lastDecreasePeriod { 1 }; // value of _packetSendPeriod when last decrease happened

View file

@ -104,6 +104,9 @@ SendQueue& Connection::getSendQueue() {
_sendQueue->setSyncInterval(_synInterval);
_sendQueue->setEstimatedTimeout(estimatedTimeout());
_sendQueue->setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize));
// give the randomized sequence number to the congestion control object
_congestionControl->setInitialSendSequenceNumber(_sendQueue->getCurrentSequenceNumber());
}
return *_sendQueue;
@ -282,7 +285,7 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) {
// grab the up to date packet receive speed and estimated bandwidth
int32_t packetReceiveSpeed = _receiveWindow.getPacketReceiveSpeed();
int32_t estimatedBandwidth = _receiveWindow.getEstimatedBandwidth();
// update those values in our connection stats
_stats.recordReceiveRate(packetReceiveSpeed);
_stats.recordEstimatedBandwidth(estimatedBandwidth);
@ -541,7 +544,7 @@ void Connection::processACK(std::unique_ptr<ControlPacket> controlPacket) {
// read the ACK sub-sequence number
SequenceNumber currentACKSubSequenceNumber;
controlPacket->readPrimitive(&currentACKSubSequenceNumber);
// Check if we need send an ACK2 for this ACK
// This will be the case if it has been longer than the sync interval OR
// it looks like they haven't received our ACK2 for this ACK

View file

@ -21,12 +21,10 @@ in vec3 _normal;
in vec2 _texCoord0;
const float gamma = 2.2;
const float smoothing = 256.0;
const float smoothing = 32.0;
const float interiorCutoff = 0.8;
const float outlineExpansion = 0.2;
void main() {
// retrieve signed distance
float sdf = texture(Font, _texCoord0).g;

View file

@ -240,7 +240,9 @@ public:
const Varying getInput() const { return _input; }
const Varying getOutput() const { return _output; }
Model(const Varying& input, Data data = Data()) : Concept(std::make_shared<C>()), _data(data), _input(input), _output(Output()) {
template <class... A>
Model(const Varying& input, A&&... args) :
Concept(std::make_shared<C>()), _data(Data(std::forward<A>(args)...)), _input(input), _output(Output()) {
applyConfiguration();
}
@ -308,7 +310,10 @@ public:
const Varying getInput() const { return _input; }
const Varying getOutput() const { return _output; }
Model(const Varying& input, Data data = Data()) : Concept(data._config), _data(data), _input(input), _output(Output()) {
template <class... A>
Model(const Varying& input, A&&... args) :
Concept(nullptr), _data(Data(std::forward<A>(args)...)), _input(input), _output(Output()) {
_config = _data._config;
std::static_pointer_cast<Config>(_config)->init(&_data);
applyConfiguration();
}
@ -337,9 +342,7 @@ public:
// Create a new job in the container's queue; returns the job's output
template <class T, class... A> const Varying addJob(std::string name, const Varying& input, A&&... args) {
_jobs.emplace_back(name, std::make_shared<typename T::JobModel>(
input,
typename T::JobModel::Data(std::forward<A>(args)...)));
_jobs.emplace_back(name, std::make_shared<typename T::JobModel>(input, std::forward<A>(args)...));
QConfigPointer config = _jobs.back().getConfiguration();
config->setParent(_config.get());
config->setObjectName(name.c_str());

View file

@ -29,7 +29,7 @@ const glm::quat identity = glm::quat();
const glm::quat quaterTurnAroundZ = glm::angleAxis(0.5f * PI, zAxis);
void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
void makeTestFBXJoints(FBXGeometry& geometry) {
FBXJoint joint;
joint.isFree = false;
joint.freeLineage.clear();
@ -61,29 +61,29 @@ void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
joint.name = "A";
joint.parentIndex = -1;
joint.translation = origin;
fbxJoints.push_back(joint);
geometry.joints.push_back(joint);
joint.name = "B";
joint.parentIndex = 0;
joint.translation = xAxis;
fbxJoints.push_back(joint);
geometry.joints.push_back(joint);
joint.name = "C";
joint.parentIndex = 1;
joint.translation = xAxis;
fbxJoints.push_back(joint);
geometry.joints.push_back(joint);
joint.name = "D";
joint.parentIndex = 2;
joint.translation = xAxis;
fbxJoints.push_back(joint);
geometry.joints.push_back(joint);
// compute each joint's transform
for (int i = 1; i < (int)fbxJoints.size(); ++i) {
FBXJoint& j = fbxJoints[i];
for (int i = 1; i < (int)geometry.joints.size(); ++i) {
FBXJoint& j = geometry.joints[i];
int parentIndex = j.parentIndex;
// World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1)
j.transform = fbxJoints[parentIndex].transform *
j.transform = geometry.joints[parentIndex].transform *
glm::translate(j.translation) *
j.preTransform *
glm::mat4_cast(j.preRotation * j.rotation * j.postRotation) *
@ -94,14 +94,14 @@ void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
}
void AnimInverseKinematicsTests::testSingleChain() {
std::vector<FBXJoint> fbxJoints;
makeTestFBXJoints(fbxJoints);
FBXGeometry geometry;
makeTestFBXJoints(geometry);
// create a skeleton and doll
AnimPose offset;
AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints, offset);
AnimSkeleton::Pointer skeletonPtr(skeleton);
AnimSkeleton::Pointer skeletonPtr = std::make_shared<AnimSkeleton>(geometry);
AnimInverseKinematics ikDoll("doll");
ikDoll.setSkeleton(skeletonPtr);
{ // easy test IK of joint C
@ -113,11 +113,11 @@ void AnimInverseKinematicsTests::testSingleChain() {
pose.rot = identity;
pose.trans = origin;
std::vector<AnimPose> poses;
AnimPoseVec poses;
poses.push_back(pose);
pose.trans = xAxis;
for (int i = 1; i < (int)fbxJoints.size(); ++i) {
for (int i = 1; i < (int)geometry.joints.size(); ++i) {
poses.push_back(pose);
}
ikDoll.loadPoses(poses);
@ -134,7 +134,8 @@ void AnimInverseKinematicsTests::testSingleChain() {
AnimVariantMap varMap;
varMap.set("positionD", targetPosition);
varMap.set("rotationD", targetRotation);
ikDoll.setTargetVars("D", "positionD", "rotationD");
varMap.set("targetType", (int)IKTarget::Type::RotationAndPosition);
ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetType"));
AnimNode::Triggers triggers;
// the IK solution should be:
@ -144,38 +145,49 @@ void AnimInverseKinematicsTests::testSingleChain() {
// |
// A------>B------>C
//
// iterate several times
float dt = 1.0f;
ikDoll.evaluate(varMap, dt, triggers);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
const AnimPoseVec& relativePoses = ikDoll.overlay(varMap, dt, triggers, poses);
// verify absolute results
// NOTE: since we expect this solution to converge very quickly (one loop)
// we can impose very tight error thresholds.
std::vector<AnimPose> absolutePoses;
AnimPoseVec absolutePoses;
for (auto pose : poses) {
absolutePoses.push_back(pose);
}
ikDoll.computeAbsolutePoses(absolutePoses);
float acceptableAngle = 0.0001f;
QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[2].rot, quaterTurnAroundZ, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[3].rot, quaterTurnAroundZ, acceptableAngle);
const float acceptableAngleError = 0.001f;
QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngleError);
QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngleError);
QCOMPARE_QUATS(absolutePoses[2].rot, quaterTurnAroundZ, acceptableAngleError);
QCOMPARE_QUATS(absolutePoses[3].rot, quaterTurnAroundZ, acceptableAngleError);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, EPSILON);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, targetPosition, EPSILON);
const float acceptableTranslationError = 0.025f;
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, acceptableTranslationError);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, acceptableTranslationError);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, acceptableTranslationError);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, targetPosition, acceptableTranslationError);
// verify relative results
const std::vector<AnimPose>& relativePoses = ikDoll.getRelativePoses();
QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[2].rot, quaterTurnAroundZ, acceptableAngle);
QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngleError);
QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngleError);
QCOMPARE_QUATS(relativePoses[2].rot, quaterTurnAroundZ, acceptableAngleError);
QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngleError);
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, acceptableTranslationError);
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, acceptableTranslationError);
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, acceptableTranslationError);
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, acceptableTranslationError);
}
{ // hard test IK of joint C
// load intial poses that look like this:
//
@ -188,8 +200,8 @@ void AnimInverseKinematicsTests::testSingleChain() {
pose.scale = glm::vec3(1.0f);
pose.rot = identity;
pose.trans = origin;
std::vector<AnimPose> poses;
AnimPoseVec poses;
poses.push_back(pose);
pose.trans = xAxis;
@ -211,15 +223,26 @@ void AnimInverseKinematicsTests::testSingleChain() {
AnimVariantMap varMap;
varMap.set("positionD", targetPosition);
varMap.set("rotationD", targetRotation);
ikDoll.setTargetVars("D", "positionD", "rotationD");
varMap.set("targetType", (int)IKTarget::Type::RotationAndPosition);
ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetType"));
AnimNode::Triggers triggers;
// the IK solution should be:
//
// A------>B------>C------>D
//
// iterate several times
float dt = 1.0f;
ikDoll.evaluate(varMap, dt, triggers);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
ikDoll.overlay(varMap, dt, triggers, poses);
const AnimPoseVec& relativePoses = ikDoll.overlay(varMap, dt, triggers, poses);
// verify absolute results
// NOTE: the IK algorithm doesn't converge very fast for full-reach targets,
@ -228,31 +251,33 @@ void AnimInverseKinematicsTests::testSingleChain() {
// NOTE: constraints may help speed up convergence since some joints may get clamped
// to maximum extension. TODO: experiment with tightening the error thresholds when
// constraints are working.
std::vector<AnimPose> absolutePoses;
AnimPoseVec absolutePoses;
for (auto pose : poses) {
absolutePoses.push_back(pose);
}
ikDoll.computeAbsolutePoses(absolutePoses);
float acceptableAngle = 0.1f; // radians
float acceptableAngle = 0.01f; // radians
QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[2].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[3].rot, identity, acceptableAngle);
float acceptableDistance = 0.4f;
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, EPSILON);
float acceptableDistance = 0.03f;
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, 3.0f * xAxis, acceptableDistance);
// verify relative results
const std::vector<AnimPose>& relativePoses = ikDoll.getRelativePoses();
QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[2].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngle);
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, acceptableDistance);
}
}

View file

@ -38,8 +38,9 @@ void AnimTests::testClipInternalState() {
float endFrame = 20.0f;
float timeScale = 1.1f;
bool loopFlag = true;
bool mirrorFlag = false;
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag);
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag);
QVERIFY(clip.getID() == id);
QVERIFY(clip.getType() == AnimNode::Type::Clip);
@ -49,6 +50,7 @@ void AnimTests::testClipInternalState() {
QVERIFY(clip._endFrame == endFrame);
QVERIFY(clip._timeScale == timeScale);
QVERIFY(clip._loopFlag == loopFlag);
QVERIFY(clip._mirrorFlag == mirrorFlag);
}
static float framesToSec(float secs) {
@ -62,12 +64,13 @@ void AnimTests::testClipEvaulate() {
float startFrame = 2.0f;
float endFrame = 22.0f;
float timeScale = 1.0f;
float loopFlag = true;
bool loopFlag = true;
bool mirrorFlag = false;
auto vars = AnimVariantMap();
vars.set("FalseVar", false);
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag);
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag);
AnimNode::Triggers triggers;
clip.evaluate(vars, framesToSec(10.0f), triggers);
@ -97,7 +100,8 @@ void AnimTests::testClipEvaulateWithVars() {
float startFrame = 2.0f;
float endFrame = 22.0f;
float timeScale = 1.0f;
float loopFlag = true;
bool loopFlag = true;
bool mirrorFlag = false;
float startFrame2 = 22.0f;
float endFrame2 = 100.0f;
@ -110,7 +114,7 @@ void AnimTests::testClipEvaulateWithVars() {
vars.set("timeScale2", timeScale2);
vars.set("loopFlag2", loopFlag2);
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag);
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag);
clip.setStartFrameVar("startFrame2");
clip.setEndFrameVar("endFrame2");
clip.setTimeScaleVar("timeScale2");
@ -583,23 +587,23 @@ void AnimTests::testExpressionEvaluator() {
TEST_BOOL_EXPR(false && false);
TEST_BOOL_EXPR(false && true);
TEST_BOOL_EXPR(true || false && true);
TEST_BOOL_EXPR(true || false && false);
TEST_BOOL_EXPR(true || true && true);
TEST_BOOL_EXPR(true || true && false);
TEST_BOOL_EXPR(false || false && true);
TEST_BOOL_EXPR(false || false && false);
TEST_BOOL_EXPR(false || true && true);
TEST_BOOL_EXPR(false || true && false);
TEST_BOOL_EXPR(true || (false && true));
TEST_BOOL_EXPR(true || (false && false));
TEST_BOOL_EXPR(true || (true && true));
TEST_BOOL_EXPR(true || (true && false));
TEST_BOOL_EXPR(false || (false && true));
TEST_BOOL_EXPR(false || (false && false));
TEST_BOOL_EXPR(false || (true && true));
TEST_BOOL_EXPR(false || (true && false));
TEST_BOOL_EXPR(true && false || true);
TEST_BOOL_EXPR(true && false || false);
TEST_BOOL_EXPR(true && true || true);
TEST_BOOL_EXPR(true && true || false);
TEST_BOOL_EXPR(false && false || true);
TEST_BOOL_EXPR(false && false || false);
TEST_BOOL_EXPR(false && true || true);
TEST_BOOL_EXPR(false && true || false);
TEST_BOOL_EXPR((true && false) || true);
TEST_BOOL_EXPR((true && false) || false);
TEST_BOOL_EXPR((true && true) || true);
TEST_BOOL_EXPR((true && true) || false);
TEST_BOOL_EXPR((false && false) || true);
TEST_BOOL_EXPR((false && false) || false);
TEST_BOOL_EXPR((false && true) || true);
TEST_BOOL_EXPR((false && true) || false);
TEST_BOOL_EXPR(t || false);
TEST_BOOL_EXPR(t || true);
@ -610,14 +614,14 @@ void AnimTests::testExpressionEvaluator() {
TEST_BOOL_EXPR(!false);
TEST_BOOL_EXPR(!true || true);
TEST_BOOL_EXPR(!true && !false || !true);
TEST_BOOL_EXPR(!true && !false || true);
TEST_BOOL_EXPR(!true && false || !true);
TEST_BOOL_EXPR(!true && false || true);
TEST_BOOL_EXPR(true && !false || !true);
TEST_BOOL_EXPR(true && !false || true);
TEST_BOOL_EXPR(true && false || !true);
TEST_BOOL_EXPR(true && false || true);
TEST_BOOL_EXPR((!true && !false) || !true);
TEST_BOOL_EXPR((!true && !false) || true);
TEST_BOOL_EXPR((!true && false) || !true);
TEST_BOOL_EXPR((!true && false) || true);
TEST_BOOL_EXPR((true && !false) || !true);
TEST_BOOL_EXPR((true && !false) || true);
TEST_BOOL_EXPR((true && false) || !true);
TEST_BOOL_EXPR((true && false) || true);
TEST_BOOL_EXPR(!(true && f) || !t);
TEST_BOOL_EXPR(!!!(t) && (!!f || true));

View file

@ -13,6 +13,7 @@
#include <glm/glm.hpp>
#include <ElbowConstraint.h>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <SwingTwistConstraint.h>
@ -56,7 +57,7 @@ void RotationConstraintTests::testElbowConstraint() {
float startAngle = minAngle + smallAngle;
float endAngle = maxAngle - smallAngle;
float deltaAngle = (endAngle - startAngle) / (float)(numChecks - 1);
for (float angle = startAngle; angle < endAngle + 0.5f * deltaAngle; angle += deltaAngle) {
glm::quat inputRotation = glm::angleAxis(angle, hingeAxis) * referenceRotation;
glm::quat outputRotation = inputRotation;
@ -115,9 +116,9 @@ void RotationConstraintTests::testSwingTwistConstraint() {
shoulder.setTwistLimits(minTwistAngle, maxTwistAngle);
float lowDot = 0.25f;
float highDot = 0.75f;
// The swing constriants are more interesting: a vector of minimum dot products
// The swing constriants are more interesting: a vector of minimum dot products
// as a function of theta around the twist axis. Our test function will be shaped
// like the square wave with amplitudes 0.25 and 0.75:
// like a square wave with amplitudes 0.25 and 0.75:
//
// |
// 0.75 - o---o---o---o
@ -308,3 +309,344 @@ void RotationConstraintTests::testSwingTwistConstraint() {
}
}
void RotationConstraintTests::testDynamicSwingLimitFunction() {
SwingTwistConstraint::SwingLimitFunction limitFunction;
const float ACCEPTABLE_ERROR = 1.0e-6f;
const float adjustmentDot = -0.5f;
const float MIN_DOT = 0.5f;
{ // initialize limitFunction
std::vector<float> minDots;
minDots.push_back(MIN_DOT);
limitFunction.setMinDots(minDots);
}
std::vector<float> referenceDots;
{ // verify limits and initialize referenceDots
const int MIN_NUM_DOTS = 8;
const std::vector<float>& minDots = limitFunction.getMinDots();
QVERIFY(minDots.size() >= MIN_NUM_DOTS);
int numDots = (int)minDots.size();
for (int i = 0; i < numDots; ++i) {
QCOMPARE_WITH_RELATIVE_ERROR(minDots[i], MIN_DOT, ACCEPTABLE_ERROR);
referenceDots.push_back(minDots[i]);
}
}
{ // dynamically adjust limits
const std::vector<float>& minDots = limitFunction.getMinDots();
int numDots = (int)minDots.size();
float deltaTheta = TWO_PI / (float)(numDots - 1);
int indexA = 2;
int indexB = (indexA + 1) % numDots;
{ // dynamically adjust a data point
float theta = deltaTheta * (float)indexA;
float interpolatedDot = limitFunction.getMinDot(theta);
// change indexA
limitFunction.dynamicallyAdjustMinDots(theta, adjustmentDot);
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), adjustmentDot, ACCEPTABLE_ERROR); // adjustmentDot at theta
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], adjustmentDot, ACCEPTABLE_ERROR); // indexA has changed
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB], ACCEPTABLE_ERROR); // indexB has not changed
// change indexB
theta = deltaTheta * (float)indexB;
limitFunction.dynamicallyAdjustMinDots(theta, adjustmentDot);
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), adjustmentDot, ACCEPTABLE_ERROR); // adjustmentDot at theta
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA], ACCEPTABLE_ERROR); // indexA has been restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], adjustmentDot, ACCEPTABLE_ERROR); // indexB has changed
// restore
limitFunction.dynamicallyAdjustMinDots(theta, referenceDots[indexB] + 0.01f); // restore with a larger dot
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), interpolatedDot, ACCEPTABLE_ERROR); // restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA], ACCEPTABLE_ERROR); // indexA is restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB], ACCEPTABLE_ERROR); // indexB is restored
}
{ // dynamically adjust halfway between data points
float theta = deltaTheta * 0.5f * (float)(indexA + indexB); // halfway between two points
float interpolatedDot = limitFunction.getMinDot(theta);
float deltaDot = adjustmentDot - interpolatedDot;
limitFunction.dynamicallyAdjustMinDots(theta, adjustmentDot);
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), adjustmentDot, ACCEPTABLE_ERROR); // adjustmentDot at theta
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA] + deltaDot, ACCEPTABLE_ERROR); // indexA has changed
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB] + deltaDot, ACCEPTABLE_ERROR); // indexB has changed
limitFunction.dynamicallyAdjustMinDots(theta, interpolatedDot + 0.01f); // reset with something larger
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), interpolatedDot, ACCEPTABLE_ERROR); // restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA], ACCEPTABLE_ERROR); // indexA is restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB], ACCEPTABLE_ERROR); // indexB is restored
}
{ // dynamically adjust one-quarter between data points
float theta = deltaTheta * ((float)indexA + 0.25f); // one quarter past A towards B
float interpolatedDot = limitFunction.getMinDot(theta);
limitFunction.dynamicallyAdjustMinDots(theta, adjustmentDot);
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), adjustmentDot, ACCEPTABLE_ERROR); // adjustmentDot at theta
QVERIFY(minDots[indexA] < adjustmentDot); // indexA should be less than minDot
QVERIFY(minDots[indexB] > adjustmentDot); // indexB should be larger than minDot
QVERIFY(minDots[indexB] < referenceDots[indexB]); // indexB should be less than what it was
limitFunction.dynamicallyAdjustMinDots(theta, interpolatedDot + 0.01f); // reset with something larger
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), interpolatedDot, ACCEPTABLE_ERROR); // restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA], ACCEPTABLE_ERROR); // indexA is restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB], ACCEPTABLE_ERROR); // indexB is restored
}
{ // halfway between first two data points (boundary condition)
indexA = 0;
indexB = 1;
int indexZ = minDots.size() - 1; // far boundary condition
float theta = deltaTheta * 0.5f * (float)(indexA + indexB); // halfway between two points
float interpolatedDot = limitFunction.getMinDot(theta);
float deltaDot = adjustmentDot - interpolatedDot;
limitFunction.dynamicallyAdjustMinDots(theta, adjustmentDot);
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), adjustmentDot, ACCEPTABLE_ERROR); // adjustmentDot at theta
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA] + deltaDot, ACCEPTABLE_ERROR); // indexA has changed
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB] + deltaDot, ACCEPTABLE_ERROR); // indexB has changed
QCOMPARE_WITH_ABS_ERROR(minDots[indexZ], referenceDots[indexZ] + deltaDot, ACCEPTABLE_ERROR); // indexZ has changed
limitFunction.dynamicallyAdjustMinDots(theta, interpolatedDot + 0.01f); // reset with something larger
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), interpolatedDot, ACCEPTABLE_ERROR); // restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA], ACCEPTABLE_ERROR); // indexA is restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB], ACCEPTABLE_ERROR); // indexB is restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexZ], referenceDots[indexZ], ACCEPTABLE_ERROR); // indexZ is restored
}
{ // halfway between first two data points (boundary condition)
indexB = minDots.size() - 1;
indexA = indexB - 1;
int indexZ = 0; // far boundary condition
float theta = deltaTheta * 0.5f * (float)(indexA + indexB); // halfway between two points
float interpolatedDot = limitFunction.getMinDot(theta);
float deltaDot = adjustmentDot - interpolatedDot;
limitFunction.dynamicallyAdjustMinDots(theta, adjustmentDot);
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), adjustmentDot, ACCEPTABLE_ERROR); // adjustmentDot at theta
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA] + deltaDot, ACCEPTABLE_ERROR); // indexA has changed
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB] + deltaDot, ACCEPTABLE_ERROR); // indexB has changed
QCOMPARE_WITH_ABS_ERROR(minDots[indexZ], referenceDots[indexZ] + deltaDot, ACCEPTABLE_ERROR); // indexZ has changed
limitFunction.dynamicallyAdjustMinDots(theta, interpolatedDot + 0.01f); // reset with something larger
QCOMPARE_WITH_ABS_ERROR(limitFunction.getMinDot(theta), interpolatedDot, ACCEPTABLE_ERROR); // restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexA], referenceDots[indexA], ACCEPTABLE_ERROR); // indexA is restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexB], referenceDots[indexB], ACCEPTABLE_ERROR); // indexB is restored
QCOMPARE_WITH_ABS_ERROR(minDots[indexZ], referenceDots[indexZ], ACCEPTABLE_ERROR); // indexZ is restored
}
}
}
void RotationConstraintTests::testDynamicSwing() {
const float ACCEPTABLE_ERROR = 1.0e-6f;
// referenceRotation is the default rotation
float referenceAngle = 1.23f;
glm::vec3 referenceAxis = glm::normalize(glm::vec3(1.0f, 2.0f, -3.0f));
glm::quat referenceRotation = glm::angleAxis(referenceAngle, referenceAxis);
// the angle limits of the constriant about the hinge axis
float minTwistAngle = -PI / 2.0f;
float maxTwistAngle = PI / 2.0f;
// build the constraint
SwingTwistConstraint shoulder;
shoulder.setReferenceRotation(referenceRotation);
shoulder.setTwistLimits(minTwistAngle, maxTwistAngle);
std::vector<float> minDots;
const float MIN_DOT = 0.5f;
minDots.push_back(MIN_DOT);
shoulder.setSwingLimits(minDots);
// verify resolution of the swing limits
const std::vector<float>& shoulderMinDots = shoulder.getMinDots();
const int MIN_NUM_DOTS = 8;
int numDots = shoulderMinDots.size();
QVERIFY(numDots >= MIN_NUM_DOTS);
// verify values of the swing limits
QCOMPARE_WITH_ABS_ERROR(shoulderMinDots[0], shoulderMinDots[numDots - 1], ACCEPTABLE_ERROR); // endpoints should be the same
for (int i = 0; i < numDots; ++i) {
QCOMPARE_WITH_ABS_ERROR(shoulderMinDots[i], MIN_DOT, ACCEPTABLE_ERROR); // all values should be the same
}
float deltaTheta = TWO_PI / (float)(numDots - 1);
float theta = 1.5f * deltaTheta;
glm::vec3 swingAxis(cosf(theta), 0.0f, sinf(theta));
float deltaSwing = 0.1f;
{ // compute rotation that should NOT be constrained
float swingAngle = acosf(MIN_DOT) - deltaSwing;
glm::quat swingRotation = glm::angleAxis(swingAngle, swingAxis);
glm::quat totalRotation = swingRotation * referenceRotation;
// verify rotation is NOT constrained
glm::quat constrainedRotation = totalRotation;
QVERIFY(!shoulder.apply(constrainedRotation));
}
{ // compute a rotation that should be barely constrained
float swingAngle = acosf(MIN_DOT) + deltaSwing;
glm::quat swingRotation = glm::angleAxis(swingAngle, swingAxis);
glm::quat totalRotation = swingRotation * referenceRotation;
// verify rotation is constrained
glm::quat constrainedRotation = totalRotation;
QVERIFY(shoulder.apply(constrainedRotation));
}
{ // make a dynamic adjustment to the swing limits
const float SMALLER_MIN_DOT = -0.5f;
float swingAngle = acosf(SMALLER_MIN_DOT);
glm::quat swingRotation = glm::angleAxis(swingAngle, swingAxis);
glm::quat badRotation = swingRotation * referenceRotation;
{ // verify rotation is constrained
glm::quat constrainedRotation = badRotation;
QVERIFY(shoulder.apply(constrainedRotation));
// now poke the SMALLER_MIN_DOT into the swing limits
shoulder.dynamicallyAdjustLimits(badRotation);
// verify that if rotation is constrained then it is only by a little bit
constrainedRotation = badRotation;
bool constrained = shoulder.apply(constrainedRotation);
if (constrained) {
// Note: Q1 = dQ * Q0 --> dQ = Q1 * Q0^
glm::quat dQ = constrainedRotation * glm::inverse(badRotation);
const float acceptableClampAngle = 0.01f;
float deltaAngle = glm::angle(dQ);
QVERIFY(deltaAngle < acceptableClampAngle);
}
}
{ // verify that other swing axes still use the old non-adjusted limits
float deltaTheta = TWO_PI / (float)(numDots - 1);
float otherTheta = 3.5f * deltaTheta;
glm::vec3 otherSwingAxis(cosf(otherTheta), 0.0f, sinf(otherTheta));
{ // inside rotations should be unconstrained
float goodAngle = acosf(MIN_DOT) - deltaSwing;
glm::quat goodRotation = glm::angleAxis(goodAngle, otherSwingAxis) * referenceRotation;
QVERIFY(!shoulder.apply(goodRotation));
}
{ // outside rotations should be constrained
float badAngle = acosf(MIN_DOT) + deltaSwing;
glm::quat otherBadRotation = glm::angleAxis(badAngle, otherSwingAxis) * referenceRotation;
QVERIFY(shoulder.apply(otherBadRotation));
float constrainedAngle = glm::angle(otherBadRotation);
QCOMPARE_WITH_ABS_ERROR(constrainedAngle, acosf(MIN_DOT), 0.1f * deltaSwing);
}
}
{ // clear dynamic adjustment
float goodAngle = acosf(MIN_DOT) - deltaSwing;
glm::quat goodRotation = glm::angleAxis(goodAngle, swingAxis) * referenceRotation;
// when we update with a goodRotation the dynamic adjustment is cleared
shoulder.dynamicallyAdjustLimits(goodRotation);
// verify that the old badRotation, which was not constrained dynamically, is now constrained
glm::quat constrainedRotation = badRotation;
QVERIFY(shoulder.apply(constrainedRotation));
// and the good rotation should not be constrained
constrainedRotation = goodRotation;
QVERIFY(!shoulder.apply(constrainedRotation));
}
}
}
void RotationConstraintTests::testDynamicTwist() {
// referenceRotation is the default rotation
float referenceAngle = 1.23f;
glm::vec3 referenceAxis = glm::normalize(glm::vec3(1.0f, 2.0f, -3.0f));
glm::quat referenceRotation = glm::angleAxis(referenceAngle, referenceAxis);
// the angle limits of the constriant about the hinge axis
const float minTwistAngle = -PI / 2.0f;
const float maxTwistAngle = PI / 2.0f;
// build the constraint
SwingTwistConstraint shoulder;
shoulder.setReferenceRotation(referenceRotation);
shoulder.setTwistLimits(minTwistAngle, maxTwistAngle);
glm::vec3 twistAxis = Vectors::UNIT_Y;
float deltaTwist = 0.1f;
{ // compute min rotation that should NOT be constrained
float twistAngle = minTwistAngle + deltaTwist;
glm::quat twistRotation = glm::angleAxis(twistAngle, twistAxis);
glm::quat totalRotation = twistRotation * referenceRotation;
// verify rotation is NOT constrained
glm::quat constrainedRotation = totalRotation;
QVERIFY(!shoulder.apply(constrainedRotation));
}
{ // compute max rotation that should NOT be constrained
float twistAngle = maxTwistAngle - deltaTwist;
glm::quat twistRotation = glm::angleAxis(twistAngle, twistAxis);
glm::quat totalRotation = twistRotation * referenceRotation;
// verify rotation is NOT constrained
glm::quat constrainedRotation = totalRotation;
QVERIFY(!shoulder.apply(constrainedRotation));
}
{ // compute a min rotation that should be barely constrained
float twistAngle = minTwistAngle - deltaTwist;
glm::quat twistRotation = glm::angleAxis(twistAngle, twistAxis);
glm::quat totalRotation = twistRotation * referenceRotation;
// verify rotation is constrained
glm::quat constrainedRotation = totalRotation;
QVERIFY(shoulder.apply(constrainedRotation));
// adjust the constraint and verify rotation is NOT constrained
shoulder.dynamicallyAdjustLimits(totalRotation);
constrainedRotation = totalRotation;
bool constrained = shoulder.apply(constrainedRotation);
if (constrained) {
// or, if it is constrained then the adjustment is very small
// Note: Q1 = dQ * Q0 --> dQ = Q1 * Q0^
glm::quat dQ = constrainedRotation * glm::inverse(totalRotation);
const float acceptableClampAngle = 0.01f;
float deltaAngle = glm::angle(dQ);
QVERIFY(deltaAngle < acceptableClampAngle);
}
// clear the adjustment using a null rotation
shoulder.dynamicallyAdjustLimits(glm::quat());
// verify that rotation is constrained again
constrainedRotation = totalRotation;
QVERIFY(shoulder.apply(constrainedRotation));
}
{ // compute a min rotation that should be barely constrained
float twistAngle = maxTwistAngle + deltaTwist;
glm::quat twistRotation = glm::angleAxis(twistAngle, twistAxis);
glm::quat totalRotation = twistRotation * referenceRotation;
// verify rotation is constrained
glm::quat constrainedRotation = totalRotation;
QVERIFY(shoulder.apply(constrainedRotation));
// adjust the constraint and verify rotation is NOT constrained
shoulder.dynamicallyAdjustLimits(totalRotation);
constrainedRotation = totalRotation;
bool constrained = shoulder.apply(constrainedRotation);
if (constrained) {
// or, if it is constrained then the adjustment is very small
// Note: Q1 = dQ * Q0 --> dQ = Q1 * Q0^
glm::quat dQ = constrainedRotation * glm::inverse(totalRotation);
const float acceptableClampAngle = 0.01f;
float deltaAngle = glm::angle(dQ);
QVERIFY(deltaAngle < acceptableClampAngle);
}
// clear the adjustment using a null rotation
shoulder.dynamicallyAdjustLimits(glm::quat());
// verify that rotation is constrained again
constrainedRotation = totalRotation;
QVERIFY(shoulder.apply(constrainedRotation));
}
}

View file

@ -15,10 +15,13 @@
class RotationConstraintTests : public QObject {
Q_OBJECT
private slots:
void testElbowConstraint();
void testSwingTwistConstraint();
void testDynamicSwingLimitFunction();
void testDynamicSwing();
void testDynamicTwist();
};
#endif // hifi_RotationConstraintTests_h

View file

@ -1,18 +1,18 @@
//
// createTank.js
//
//
//
// created by James b. Pollack @imgntn on 3/9/2016
// Copyright 2016 High Fidelity, Inc.
//
// Adds a fish tank and base, decorations, particle bubble systems, and a bubble sound. Attaches a script that does fish swimming.
//
// Copyright 2016 High Fidelity, Inc.
//
// Adds a fish tank and base, decorations, particle bubble systems, and a bubble sound. Attaches a script that does fish swimming.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var fishTank, tankBase, bubbleSystem, secondBubbleSystem, thirdBubbleSystem, innerContainer, bubbleInjector, lowerCorner, upperCorner, urchin, treasure, rocks;
var fishTank, tankBase, bubbleSystem, secondBubbleSystem, thirdBubbleSystem, innerContainer, bubbleInjector, lowerCorner, upperCorner, anemone, treasure, rocks;
var CLEANUP = true;
var TANK_DIMENSIONS = {
@ -34,7 +34,15 @@ var DEBUG_COLOR = {
blue: 255
}
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 1 * TANK_WIDTH));
var centerVertical = {
x: 0,
y: 1,
z: 0
}
var upCenter = Vec3.sum(centerVertical, MyAvatar.position);
var center = Vec3.sum(upCenter, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 2));
var TANK_POSITION = center;
@ -52,7 +60,7 @@ var TANK_BASE_DIMENSIONS = {
z: 2.1936
};
var BASE_VERTICAL_OFFSET = 0.42;
var BASE_VERTICAL_OFFSET = 0.47;
var BUBBLE_SYSTEM_FORWARD_OFFSET = TANK_DIMENSIONS.x + 0.06;
var BUBBLE_SYSTEM_LATERAL_OFFSET = 0.025;
@ -68,14 +76,14 @@ var BUBBLE_SOUND_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/
var bubbleSound = SoundCache.getSound(BUBBLE_SOUND_URL);
var URCHIN_FORWARD_OFFSET = TANK_DIMENSIONS.x - 0.35;
var URCHIN_LATERAL_OFFSET = -0.05;
var URCHIN_VERTICAL_OFFSET = -0.12;
var ANEMONE_FORWARD_OFFSET = TANK_DIMENSIONS.x - 0.35;
var ANEMONE_LATERAL_OFFSET = -0.05;
var ANEMONE_VERTICAL_OFFSET = -0.12;
var URCHIN_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/Urchin.fbx';
var URCHIN_DIMENSIONS = {
var ANEMONE_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/anemone.fbx';
var ANEMONE_ANIMATION_URL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/anemone.fbx';
var ANEMONE_DIMENSIONS = {
x: 0.4,
y: 0.4,
z: 0.4
@ -303,19 +311,29 @@ function createRocks() {
}
function createUrchin() {
var finalPosition = getOffsetFromTankCenter(URCHIN_VERTICAL_OFFSET, URCHIN_FORWARD_OFFSET, URCHIN_LATERAL_OFFSET);
var finalPosition = getOffsetFromTankCenter(ANEMONE_VERTICAL_OFFSET, ANEMONE_FORWARD_OFFSET, ANEMONE_LATERAL_OFFSET);
var properties = {
name: 'hifi-home-fishtank-urchin',
name: 'hifi-home-fishtank-anemone',
type: 'Model',
animationURL: ANEMONE_ANIMATION_URL,
animationIsPlaying: true,
animationFPS: 15,
animationSettings: JSON.stringify({
hold: false,
loop: true,
running: true,
startAutomatically: true
}),
parentID: fishTank,
modelURL: URCHIN_MODEL_URL,
modelURL: ANEMONE_MODEL_URL,
position: finalPosition,
shapeType: 'Sphere',
dimensions: URCHIN_DIMENSIONS
rotation: Quat.fromPitchYawRollDegrees(0, 90, 0),
dimensions: ANEMONE_DIMENSIONS
}
urchin = Entities.addEntity(properties);
anemone = Entities.addEntity(properties);
}
@ -398,7 +416,7 @@ function cleanup() {
Entities.deleteEntity(innerContainer);
Entities.deleteEntity(lowerCorner);
Entities.deleteEntity(upperCorner);
Entities.deleteEntity(urchin);
Entities.deleteEntity(anemone);
Entities.deleteEntity(rocks);
bubbleInjector.stop();
bubbleInjector = null;
@ -449,4 +467,4 @@ function getEntityCustomData(customKey, id, defaultValue) {
} else {
return defaultValue;
}
}
}