mirror of
https://github.com/overte-org/overte.git
synced 2025-04-16 23:26:25 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into atp
This commit is contained in:
commit
72c64532f5
87 changed files with 1722 additions and 1055 deletions
|
@ -272,6 +272,18 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
||||
}
|
||||
|
||||
void EntityServer::nodeAdded(SharedNodePointer node) {
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
tree->knowAvatarID(node->getUUID());
|
||||
OctreeServer::nodeAdded(node);
|
||||
}
|
||||
|
||||
void EntityServer::nodeKilled(SharedNodePointer node) {
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
tree->deleteDescendantsOfAvatar(node->getUUID());
|
||||
tree->forgetAvatarID(node->getUUID());
|
||||
OctreeServer::nodeKilled(node);
|
||||
}
|
||||
|
||||
// FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad
|
||||
// set of stats to have, but we'd probably want a different data structure if we keep it very long.
|
||||
|
|
|
@ -58,6 +58,8 @@ public:
|
|||
virtual void trackViewerGone(const QUuid& sessionID) override;
|
||||
|
||||
public slots:
|
||||
virtual void nodeAdded(SharedNodePointer node);
|
||||
virtual void nodeKilled(SharedNodePointer node);
|
||||
void pruneDeletedEntities();
|
||||
|
||||
protected:
|
||||
|
|
|
@ -127,8 +127,8 @@ public:
|
|||
public slots:
|
||||
/// runs the octree server assignment
|
||||
void run();
|
||||
void nodeAdded(SharedNodePointer node);
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
virtual void nodeAdded(SharedNodePointer node);
|
||||
virtual void nodeKilled(SharedNodePointer node);
|
||||
void sendStatsPacket();
|
||||
|
||||
private slots:
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -152,9 +152,19 @@ function maybeMoveOverlay() {
|
|||
|
||||
// MAIN CONTROL
|
||||
var wasMuted, isAway;
|
||||
var wasOverlaysVisible = Menu.isOptionChecked("Overlays");
|
||||
var eventMappingName = "io.highfidelity.away"; // goActive on hand controller button events, too.
|
||||
var eventMapping = Controller.newMapping(eventMappingName);
|
||||
|
||||
// backward compatible version of getting HMD.mounted, so it works in old clients
|
||||
function safeGetHMDMounted() {
|
||||
if (HMD.mounted === undefined) {
|
||||
return true;
|
||||
}
|
||||
return HMD.mounted;
|
||||
}
|
||||
var wasHmdMounted = safeGetHMDMounted();
|
||||
|
||||
function goAway() {
|
||||
if (isAway) {
|
||||
return;
|
||||
|
@ -169,12 +179,20 @@ function goAway() {
|
|||
playAwayAnimation(); // animation is still seen by others
|
||||
showOverlay();
|
||||
|
||||
// remember the View > Overlays state...
|
||||
wasOverlaysVisible = Menu.isOptionChecked("Overlays");
|
||||
|
||||
// show overlays so that people can see the "Away" message
|
||||
Menu.setIsOptionChecked("Overlays", true);
|
||||
|
||||
// tell the Reticle, we want to stop capturing the mouse until we come back
|
||||
Reticle.allowMouseCapture = false;
|
||||
if (HMD.active) {
|
||||
Reticle.visible = false;
|
||||
}
|
||||
wasHmdMounted = safeGetHMDMounted(); // always remember the correct state
|
||||
}
|
||||
|
||||
function goActive() {
|
||||
if (!isAway) {
|
||||
return;
|
||||
|
@ -188,12 +206,16 @@ function goActive() {
|
|||
stopAwayAnimation();
|
||||
hideOverlay();
|
||||
|
||||
// restore overlays state to what it was when we went "away"
|
||||
Menu.setIsOptionChecked("Overlays", wasOverlaysVisible);
|
||||
|
||||
// tell the Reticle, we are ready to capture the mouse again and it should be visible
|
||||
Reticle.allowMouseCapture = true;
|
||||
Reticle.visible = true;
|
||||
if (HMD.active) {
|
||||
Reticle.position = HMD.getHUDLookAtPosition2D();
|
||||
}
|
||||
wasHmdMounted = safeGetHMDMounted(); // always remember the correct state
|
||||
}
|
||||
|
||||
function maybeGoActive(event) {
|
||||
|
@ -206,6 +228,7 @@ function maybeGoActive(event) {
|
|||
goActive();
|
||||
}
|
||||
}
|
||||
|
||||
var wasHmdActive = HMD.active;
|
||||
var wasMouseCaptured = Reticle.mouseCaptured;
|
||||
|
||||
|
@ -225,6 +248,13 @@ function maybeGoAway() {
|
|||
goAway();
|
||||
}
|
||||
}
|
||||
|
||||
// If you've removed your HMD from your head, and we can detect it, we will also go away...
|
||||
var hmdMounted = safeGetHMDMounted();
|
||||
if (HMD.active && !hmdMounted && wasHmdMounted) {
|
||||
wasHmdMounted = hmdMounted;
|
||||
goAway();
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(maybeMoveOverlay);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
60
examples/data_visualization/photo_sphere.js
Normal file
60
examples/data_visualization/photo_sphere.js
Normal 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();
|
|
@ -21,7 +21,7 @@ var MINIMUM_DEPTH_ADJUST = 0.01;
|
|||
var NON_LINEAR_DIVISOR = 2;
|
||||
var MINIMUM_SEEK_DISTANCE = 0.01;
|
||||
|
||||
var lastMouseMove = Date.now();
|
||||
var lastMouseMoveOrClick = Date.now();
|
||||
var lastMouseX = Reticle.position.x;
|
||||
var lastMouseY = Reticle.position.y;
|
||||
var HIDE_STATIC_MOUSE_AFTER = 3000; // 3 seconds
|
||||
|
@ -32,6 +32,14 @@ var WEIGHTING = 1/20; // simple moving average over last 20 samples
|
|||
var ONE_MINUS_WEIGHTING = 1 - WEIGHTING;
|
||||
var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50;
|
||||
|
||||
function showReticleOnMouseClick() {
|
||||
Reticle.visible = true;
|
||||
lastMouseMoveOrClick = Date.now(); // move or click
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(showReticleOnMouseClick);
|
||||
Controller.mouseDoublePressEvent.connect(showReticleOnMouseClick);
|
||||
|
||||
Controller.mouseMoveEvent.connect(function(mouseEvent) {
|
||||
var now = Date.now();
|
||||
|
||||
|
@ -47,7 +55,7 @@ Controller.mouseMoveEvent.connect(function(mouseEvent) {
|
|||
if (HMD.active && !shouldSeekToLookAt && Reticle.allowMouseCapture) {
|
||||
var dx = Reticle.position.x - lastMouseX;
|
||||
var dy = Reticle.position.y - lastMouseY;
|
||||
var dt = Math.max(1, (now - lastMouseMove)); // mSecs since last mouse move
|
||||
var dt = Math.max(1, (now - lastMouseMoveOrClick)); // mSecs since last mouse move
|
||||
var mouseMoveDistance = Math.sqrt((dx*dx) + (dy*dy));
|
||||
var mouseVelocity = mouseMoveDistance / dt;
|
||||
averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * mouseVelocity);
|
||||
|
@ -56,7 +64,7 @@ Controller.mouseMoveEvent.connect(function(mouseEvent) {
|
|||
}
|
||||
}
|
||||
}
|
||||
lastMouseMove = now;
|
||||
lastMouseMoveOrClick = now;
|
||||
lastMouseX = mouseEvent.x;
|
||||
lastMouseY = mouseEvent.y;
|
||||
});
|
||||
|
@ -94,7 +102,7 @@ function autoHideReticle() {
|
|||
// system overlay (like a window), then hide the reticle
|
||||
if (Reticle.visible && !Reticle.pointingAtSystemOverlay) {
|
||||
var now = Date.now();
|
||||
var timeSinceLastMouseMove = now - lastMouseMove;
|
||||
var timeSinceLastMouseMove = now - lastMouseMoveOrClick;
|
||||
if (timeSinceLastMouseMove > HIDE_STATIC_MOUSE_AFTER) {
|
||||
Reticle.visible = false;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
96
examples/playTestSound.js
Normal file
96
examples/playTestSound.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// playTestSound.js
|
||||
// examples
|
||||
//
|
||||
// Created by Philip Rosedale
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Creates an object in front of you that changes color and plays a light
|
||||
// at the start of a drum clip that loops. As you move away it will tell you in the
|
||||
// log how many meters you are from the source.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var sound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Drums/deepdrum1.wav");
|
||||
|
||||
var position = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Quat.getFront(MyAvatar.orientation));
|
||||
|
||||
var time;
|
||||
var soundPlaying = null;
|
||||
|
||||
var baseColor = { red: 100, green: 100, blue: 100 };
|
||||
var litColor = { red: 255, green: 100, blue: 0 };
|
||||
|
||||
var lightTime = 250;
|
||||
|
||||
var distance = 0.0;
|
||||
|
||||
// Create object for visual reference
|
||||
var box = Entities.addEntity({
|
||||
type: "Box",
|
||||
dimensions: { x: 0.25, y: 0.5, z: 0.25 },
|
||||
color: baseColor,
|
||||
position: position
|
||||
});
|
||||
|
||||
|
||||
function checkSound(deltaTime) {
|
||||
var started = false;
|
||||
if (!sound.downloaded) {
|
||||
return;
|
||||
}
|
||||
if (soundPlaying == null) {
|
||||
soundPlaying = Audio.playSound(sound, {
|
||||
position: position,
|
||||
volume: 1.0,
|
||||
loop: false } );
|
||||
started = true;
|
||||
} else if (!soundPlaying.isPlaying) {
|
||||
soundPlaying.restart();
|
||||
started = true;
|
||||
}
|
||||
if (started) {
|
||||
Entities.editEntity(box, { color: litColor });
|
||||
Entities.addEntity({
|
||||
type: "Light",
|
||||
intensity: 5.0,
|
||||
falloffRadius: 10.0,
|
||||
dimensions: {
|
||||
x: 40,
|
||||
y: 40,
|
||||
z: 40
|
||||
},
|
||||
position: Vec3.sum(position, { x: 0, y: 1, z: 0 }),
|
||||
color: litColor,
|
||||
lifetime: lightTime / 1000
|
||||
});
|
||||
Script.setTimeout(resetColor, lightTime);
|
||||
}
|
||||
var currentDistance = Vec3.distance(MyAvatar.position, position);
|
||||
if (Math.abs(currentDistance - distance) > 1.0) {
|
||||
print("Distance from source: " + currentDistance);
|
||||
distance = currentDistance;
|
||||
}
|
||||
}
|
||||
|
||||
function resetColor() {
|
||||
Entities.editEntity(box, { color: baseColor });
|
||||
}
|
||||
|
||||
|
||||
function scriptEnding() {
|
||||
Entities.deleteEntity(box);
|
||||
if (soundPlaying) {
|
||||
print("stop injector");
|
||||
soundPlaying.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.update.connect(checkSound);
|
||||
|
|
@ -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";
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
//
|
||||
// MessageDialog.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
|
@ -94,8 +104,8 @@ Item {
|
|||
|
||||
function buildMenu(items, targetPosition) {
|
||||
var model = toModel(items);
|
||||
// Menu's must be childed to desktop for Z-ordering
|
||||
var newMenu = menuViewMaker.createObject(desktop, { model: model, z: topMenu ? topMenu.z + 1 : desktop.zLevels.menu });
|
||||
// Menus must be childed to desktop for Z-ordering
|
||||
var newMenu = menuViewMaker.createObject(desktop, { model: model, z: topMenu ? topMenu.z + 1 : desktop.zLevels.menu, isSubMenu: topMenu !== null });
|
||||
if (targetPosition) {
|
||||
newMenu.x = targetPosition.x
|
||||
newMenu.y = targetPosition.y - newMenu.height / 3 * 1
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls 1.3
|
||||
import QtQuick.Controls.Styles 1.3
|
||||
//
|
||||
// VrMenuItem.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 29 Apr 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import "../controls"
|
||||
import "../styles"
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
@ -11,54 +21,93 @@ Item {
|
|||
property alias text: label.text
|
||||
property var source
|
||||
|
||||
implicitHeight: source.visible ? label.implicitHeight * 1.5 : 0
|
||||
implicitWidth: label.width + label.height * 2.5
|
||||
implicitHeight: source.visible ? 2 * label.implicitHeight : 0
|
||||
implicitWidth: 2 * hifi.dimensions.menuPadding.x + check.width + label.width + tail.width
|
||||
visible: source.visible
|
||||
width: parent.width
|
||||
|
||||
FontAwesome {
|
||||
clip: true
|
||||
CheckBox {
|
||||
id: check
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: label.color
|
||||
text: checkText()
|
||||
size: label.height
|
||||
visible: source.visible
|
||||
font.pixelSize: size
|
||||
function checkText() {
|
||||
if (!source || source.type != 1 || !source.checkable) {
|
||||
return ""
|
||||
// FIXME: Should use radio buttons if source.exclusiveGroup.
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.menuPadding.x
|
||||
top: label.top
|
||||
topMargin: 0
|
||||
}
|
||||
width: 20
|
||||
visible: source.visible && source.type === 1 && source.checkable
|
||||
checked: setChecked()
|
||||
function setChecked() {
|
||||
if (!source || source.type !== 1 || !source.checkable) {
|
||||
return false;
|
||||
}
|
||||
// FIXME this works for native QML menus but I don't think it will
|
||||
// for proxied QML menus
|
||||
if (source.exclusiveGroup) {
|
||||
return source.checked ? "\uF05D" : "\uF10C"
|
||||
}
|
||||
return source.checked ? "\uF046" : "\uF096"
|
||||
return source.checked;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
RalewaySemiBold {
|
||||
id: label
|
||||
size: hifi.fontSizes.rootMenu
|
||||
font.capitalization: isSubMenu ? Font.MixedCase : Font.AllUppercase
|
||||
anchors.left: check.right
|
||||
anchors.leftMargin: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: source.enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||
color: source.enabled ? hifi.colors.baseGrayShadow : hifi.colors.baseGrayShadow50
|
||||
enabled: source.visible && (source.type !== 0 ? source.enabled : false)
|
||||
visible: source.visible
|
||||
}
|
||||
|
||||
FontAwesome {
|
||||
id: tag
|
||||
x: root.parent.width - width
|
||||
size: label.height
|
||||
width: implicitWidth
|
||||
visible: source.visible && (source.type == 2)
|
||||
text: "\uF0DA"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: label.color
|
||||
Item {
|
||||
id: separator
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: hifi.dimensions.menuPadding.x + check.width
|
||||
rightMargin: hifi.dimensions.menuPadding.x + tail.width
|
||||
}
|
||||
visible: source.type === MenuItemType.Separator
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
height: 1
|
||||
color: hifi.colors.lightGray50
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: tail
|
||||
width: 48 + (shortcut.visible ? shortcut.width : 0)
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.menuPadding.x
|
||||
}
|
||||
|
||||
RalewayLight {
|
||||
id: shortcut
|
||||
text: source.shortcut ? source.shortcut : ""
|
||||
size: hifi.fontSizes.shortcutText
|
||||
color: hifi.colors.baseGrayShadow
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
visible: source.visible && text != ""
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
text: hifi.glyphs.disclosureExpand
|
||||
color: source.enabled ? hifi.colors.baseGrayShadow : hifi.colors.baseGrayShadow25
|
||||
size: 2 * hifi.fontSizes.rootMenuDisclosure
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
horizontalAlignment: Text.AlignRight
|
||||
visible: source.visible && (source.type === 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,59 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls 1.3
|
||||
import QtQuick.Controls.Styles 1.3
|
||||
//
|
||||
// VrMenuView.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import "../styles"
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
implicitHeight: border.height
|
||||
implicitWidth: border.width
|
||||
implicitHeight: background.height
|
||||
implicitWidth: background.width
|
||||
|
||||
property alias currentItem: listView.currentItem
|
||||
property alias model: listView.model
|
||||
property bool isSubMenu: false
|
||||
signal selected(var item)
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Border {
|
||||
id: border
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: listView
|
||||
anchors.margins: -8
|
||||
border.color: hifi.colors.hifiBlue
|
||||
color: hifi.colors.window
|
||||
// color: "#7f7f7f7f"
|
||||
radius: hifi.dimensions.borderRadius
|
||||
border.width: hifi.dimensions.borderWidth
|
||||
border.color: hifi.colors.lightGrayText80
|
||||
color: isSubMenu ? hifi.colors.faintGray : hifi.colors.faintGray80
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
x: 8; y: 8
|
||||
HifiConstants { id: hifi }
|
||||
width: 128
|
||||
height: count * 32
|
||||
topMargin: hifi.dimensions.menuPadding.y
|
||||
onEnabledChanged: recalcSize();
|
||||
onVisibleChanged: recalcSize();
|
||||
onCountChanged: recalcSize();
|
||||
focus: true
|
||||
|
||||
highlight: Rectangle {
|
||||
width: listView.currentItem ? listView.currentItem.width : 0
|
||||
height: listView.currentItem ? listView.currentItem.height : 0
|
||||
color: "lightsteelblue"; radius: 3
|
||||
anchors {
|
||||
left: parent ? parent.left : undefined
|
||||
right: parent ? parent.right : undefined
|
||||
leftMargin: hifi.dimensions.borderWidth
|
||||
rightMargin: hifi.dimensions.borderWidth
|
||||
}
|
||||
color: hifi.colors.white
|
||||
}
|
||||
|
||||
delegate: VrMenuItem {
|
||||
|
@ -75,6 +90,7 @@ FocusScope {
|
|||
newHeight += currentItem.implicitHeight
|
||||
}
|
||||
}
|
||||
newHeight += 2 * hifi.dimensions.menuPadding.y; // White space at top and bottom.
|
||||
if (maxWidth > width) {
|
||||
width = maxWidth;
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// FontAwesome.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 24 Apr 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
Text {
|
||||
id: root
|
||||
FontLoader { id: iconFont; source: "../../fonts/fontawesome-webfont.ttf"; }
|
||||
property int size: 32
|
||||
width: size
|
||||
height: size
|
||||
font.pixelSize: size
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.family: iconFont.name
|
||||
}
|
||||
|
|
@ -67,9 +67,13 @@ Item {
|
|||
readonly property color darkGray30: "#4d121212"
|
||||
readonly property color darkGray0: "#00121212"
|
||||
readonly property color baseGrayShadow60: "#99252525"
|
||||
readonly property color baseGrayShadow50: "#80252525"
|
||||
readonly property color baseGrayShadow25: "#40252525"
|
||||
readonly property color baseGrayHighlight40: "#66575757"
|
||||
readonly property color baseGrayHighlight15: "#26575757"
|
||||
readonly property color lightGray50: "#806a6a6a"
|
||||
readonly property color lightGrayText80: "#ccafafaf"
|
||||
readonly property color faintGray80: "#cce3e3e3"
|
||||
readonly property color faintGray50: "#80e3e3e3"
|
||||
|
||||
// Other colors
|
||||
|
@ -136,6 +140,7 @@ Item {
|
|||
readonly property real modalDialogTitleHeight: 40
|
||||
readonly property real controlLineHeight: 29 // Height of spinbox control on 1920 x 1080 monitor
|
||||
readonly property real controlInterlineHeight: 22 // 75% of controlLineHeight
|
||||
readonly property vector2d menuPadding: Qt.vector2d(14, 12)
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -153,6 +158,7 @@ Item {
|
|||
readonly property real logs: dimensions.largeScreen ? 16 : 12
|
||||
readonly property real code: dimensions.largeScreen ? 16 : 12
|
||||
readonly property real rootMenu: dimensions.largeScreen ? 15 : 11
|
||||
readonly property real rootMenuDisclosure: dimensions.largeScreen ? 20 : 16
|
||||
readonly property real menuItem: dimensions.largeScreen ? 15 : 11
|
||||
readonly property real shortcutText: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real carat: dimensions.largeScreen ? 38 : 30
|
||||
|
|
|
@ -3180,7 +3180,6 @@ void Application::update(float deltaTime) {
|
|||
|
||||
auto myAvatar = getMyAvatar();
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
userInputMapper->update(deltaTime);
|
||||
|
||||
controller::InputCalibrationData calibrationData = {
|
||||
myAvatar->getSensorToWorldMatrix(),
|
||||
|
@ -3188,9 +3187,12 @@ void Application::update(float deltaTime) {
|
|||
myAvatar->getHMDSensorMatrix()
|
||||
};
|
||||
|
||||
InputPluginPointer keyboardMousePlugin;
|
||||
bool jointsCaptured = false;
|
||||
for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
|
||||
if (inputPlugin->isActive()) {
|
||||
if (inputPlugin->getName() == KeyboardMouseDevice::NAME) {
|
||||
keyboardMousePlugin = inputPlugin;
|
||||
} else if (inputPlugin->isActive()) {
|
||||
inputPlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured);
|
||||
if (inputPlugin->isJointController()) {
|
||||
jointsCaptured = true;
|
||||
|
@ -3198,6 +3200,12 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
userInputMapper->update(deltaTime);
|
||||
|
||||
if (keyboardMousePlugin && keyboardMousePlugin->isActive()) {
|
||||
keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured);
|
||||
}
|
||||
|
||||
_controllerScriptingInterface->updateInputControllers();
|
||||
|
||||
// Transfer the user inputs to the driveKeys
|
||||
|
@ -3217,16 +3225,14 @@ void Application::update(float deltaTime) {
|
|||
myAvatar->setDriveKeys(ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
|
||||
}
|
||||
|
||||
controller::Pose leftHand = userInputMapper->getPoseState(controller::Action::LEFT_HAND);
|
||||
controller::Pose rightHand = userInputMapper->getPoseState(controller::Action::RIGHT_HAND);
|
||||
Hand* hand = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHand();
|
||||
setPalmData(hand, leftHand, deltaTime, HandData::LeftHand, userInputMapper->getActionState(controller::Action::LEFT_HAND_CLICK));
|
||||
setPalmData(hand, rightHand, deltaTime, HandData::RightHand, userInputMapper->getActionState(controller::Action::RIGHT_HAND_CLICK));
|
||||
controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND);
|
||||
controller::Pose rightHandPose = userInputMapper->getPoseState(controller::Action::RIGHT_HAND);
|
||||
auto myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition());
|
||||
myAvatar->setHandControllerPosesInWorldFrame(leftHandPose.transform(myAvatarMatrix), rightHandPose.transform(myAvatarMatrix));
|
||||
|
||||
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
||||
updateDialogs(deltaTime); // update various stats dialogs if present
|
||||
|
||||
_avatarUpdate->synchronousProcess();
|
||||
|
||||
if (_physicsEnabled) {
|
||||
PerformanceTimer perfTimer("physics");
|
||||
AvatarManager* avatarManager = DependencyManager::get<AvatarManager>().data();
|
||||
|
@ -3298,6 +3304,8 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
_avatarUpdate->synchronousProcess();
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("overlays");
|
||||
_overlays.update(deltaTime);
|
||||
|
@ -4915,49 +4923,6 @@ mat4 Application::getHMDSensorPose() const {
|
|||
return mat4();
|
||||
}
|
||||
|
||||
void Application::setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue) {
|
||||
|
||||
// NOTE: the Hand::modifyPalm() will allow the lambda to modify the palm data while ensuring some other user isn't
|
||||
// reading or writing to the Palms. This is definitely not the best way of handling this, and I'd like to see more
|
||||
// of this palm manipulation in the Hand class itself. But unfortunately the Hand and Palm don't knbow about
|
||||
// controller::Pose. More work is needed to clean this up.
|
||||
hand->modifyPalm(whichHand, [&](PalmData& palm) {
|
||||
palm.setActive(pose.isValid());
|
||||
|
||||
// controller pose is in Avatar frame.
|
||||
glm::vec3 position = pose.getTranslation();
|
||||
glm::quat rotation = pose.getRotation();
|
||||
glm::vec3 rawVelocity = pose.getVelocity();
|
||||
glm::vec3 angularVelocity = pose.getAngularVelocity();
|
||||
|
||||
palm.setRawVelocity(rawVelocity);
|
||||
palm.setRawAngularVelocity(angularVelocity);
|
||||
|
||||
if (controller::InputDevice::getLowVelocityFilter()) {
|
||||
// Use a velocity sensitive filter to damp small motions and preserve large ones with
|
||||
// no latency.
|
||||
float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f);
|
||||
position = palm.getRawPosition() * velocityFilter + position * (1.0f - velocityFilter);
|
||||
rotation = safeMix(palm.getRawRotation(), rotation, 1.0f - velocityFilter);
|
||||
}
|
||||
palm.setRawPosition(position);
|
||||
palm.setRawRotation(rotation);
|
||||
|
||||
// Store the one fingertip in the palm structure so we can track velocity
|
||||
const float FINGER_LENGTH = 0.3f; // meters
|
||||
const glm::vec3 FINGER_VECTOR(0.0f, FINGER_LENGTH, 0.0f);
|
||||
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
|
||||
glm::vec3 oldTipPosition = palm.getTipRawPosition();
|
||||
if (deltaTime > 0.0f) {
|
||||
palm.setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
|
||||
} else {
|
||||
palm.setTipVelocity(glm::vec3(0.0f));
|
||||
}
|
||||
palm.setTipPosition(newTipPosition);
|
||||
palm.setTrigger(triggerValue); // FIXME - we want to get rid of this idea of PalmData having a trigger
|
||||
});
|
||||
}
|
||||
|
||||
void Application::crashApplication() {
|
||||
qCDebug(interfaceapp) << "Intentionally crashed Interface";
|
||||
QObject* object = nullptr;
|
||||
|
|
|
@ -300,7 +300,7 @@ private slots:
|
|||
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
|
||||
bool acceptSnapshot(const QString& urlString);
|
||||
bool askToSetAvatarUrl(const QString& url);
|
||||
bool askToLoadScript(const QString& scriptFilenameOrURL);
|
||||
|
@ -326,8 +326,6 @@ private:
|
|||
|
||||
void update(float deltaTime);
|
||||
|
||||
void setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue);
|
||||
|
||||
// Various helper functions called during update()
|
||||
void updateLOD();
|
||||
void updateThreads(float deltaTime);
|
||||
|
|
|
@ -479,7 +479,8 @@ Menu::Menu() {
|
|||
|
||||
// Developer > Hands >>>
|
||||
MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false,
|
||||
avatar, SLOT(setEnableDebugDrawHandControllers(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::LowVelocityFilter, 0, true,
|
||||
qApp, SLOT(setLowVelocityFilter(bool)));
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
#include "Avatar.h"
|
||||
#include "AvatarManager.h"
|
||||
#include "AvatarMotionState.h"
|
||||
#include "Hand.h"
|
||||
#include "Head.h"
|
||||
#include "Menu.h"
|
||||
#include "Physics.h"
|
||||
|
@ -101,7 +100,6 @@ Avatar::Avatar(RigPointer rig) :
|
|||
|
||||
// give the pointer to our head to inherited _headData variable from AvatarData
|
||||
_headData = static_cast<HeadData*>(new Head(this));
|
||||
_handData = static_cast<HandData*>(new Hand(this));
|
||||
}
|
||||
|
||||
Avatar::~Avatar() {
|
||||
|
@ -190,11 +188,6 @@ void Avatar::simulate(float deltaTime) {
|
|||
float boundingRadius = getBoundingRadius();
|
||||
bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("hand");
|
||||
getHand()->simulate(deltaTime, false);
|
||||
}
|
||||
|
||||
if (_shouldAnimate && !_shouldSkipRender && inView) {
|
||||
{
|
||||
PerformanceTimer perfTimer("skeleton");
|
||||
|
@ -578,11 +571,6 @@ void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, floa
|
|||
if (_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable()) {
|
||||
getHead()->render(renderArgs, 1.0f, renderFrustum);
|
||||
}
|
||||
|
||||
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) {
|
||||
getHand()->renderHandTargets(renderArgs, false);
|
||||
}
|
||||
}
|
||||
getHead()->renderLookAts(renderArgs);
|
||||
}
|
||||
|
@ -984,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;
|
||||
|
||||
|
@ -992,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);
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
#include <render/Scene.h>
|
||||
|
||||
#include "Hand.h"
|
||||
#include "Head.h"
|
||||
#include "SkeletonModel.h"
|
||||
#include "world.h"
|
||||
|
@ -58,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;
|
||||
|
@ -91,7 +90,7 @@ public:
|
|||
float getUniformScale() const { return getScale().y; }
|
||||
const Head* getHead() const { return static_cast<const Head*>(_headData); }
|
||||
Head* getHead() { return static_cast<Head*>(_headData); }
|
||||
Hand* getHand() { return static_cast<Hand*>(_handData); }
|
||||
|
||||
glm::quat getWorldAlignedOrientation() const;
|
||||
|
||||
AABox getBounds() const;
|
||||
|
|
|
@ -104,48 +104,57 @@ std::shared_ptr<Avatar> AvatarActionHold::getTarget(float deltaTimeStep, glm::qu
|
|||
|
||||
withReadLock([&]{
|
||||
bool isRightHand = (_hand == "right");
|
||||
|
||||
glm::vec3 palmPosition;
|
||||
glm::quat palmRotation;
|
||||
|
||||
PalmData palmData = holdingAvatar->getHand()->getCopyOfPalmData(isRightHand ? HandData::RightHand : HandData::LeftHand);
|
||||
|
||||
if (palmData.isValid()) {
|
||||
// TODO: adjust according to _relativePosition and _relativeRotation?
|
||||
linearVelocity = palmData.getVelocity();
|
||||
angularVelocity = palmData.getAngularVelocity();
|
||||
}
|
||||
|
||||
if (_ignoreIK && holdingAvatar->isMyAvatar() && palmData.isValid()) {
|
||||
// We cannot ignore other avatars IK and this is not the point of this option
|
||||
// This is meant to make the grabbing behavior more reactive.
|
||||
palmPosition = palmData.getPosition();
|
||||
palmRotation = palmData.getRotation();
|
||||
} else if (holdingAvatar->isMyAvatar()) {
|
||||
glm::vec3 avatarRigidBodyPosition;
|
||||
glm::quat avatarRigidBodyRotation;
|
||||
getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
|
||||
|
||||
// the offset and rotation between the avatar's rigid body and the palm were determined earlier
|
||||
// in prepareForPhysicsSimulation. At this point, the avatar's rigid body has been moved by bullet
|
||||
// and the data in the Avatar class is stale. This means that the result of get*PalmPosition will
|
||||
// be stale. Instead, determine the current palm position with the current avatar's rigid body
|
||||
// location and the saved offsets.
|
||||
|
||||
// this line is more correct but breaks for the current way avatar data is updated.
|
||||
// palmPosition = avatarRigidBodyPosition + avatarRigidBodyRotation * _palmOffsetFromRigidBody;
|
||||
// instead, use this for now:
|
||||
palmPosition = avatarRigidBodyPosition + _palmOffsetFromRigidBody;
|
||||
|
||||
// the item jitters the least by getting the rotation based on the opinion of Avatar.h rather
|
||||
// than that of the rigid body. leaving this next line here for future reference:
|
||||
// palmRotation = avatarRigidBodyRotation * _palmRotationFromRigidBody;
|
||||
if (holdingAvatar->isMyAvatar()) {
|
||||
|
||||
// fetch the hand controller pose
|
||||
controller::Pose pose;
|
||||
if (isRightHand) {
|
||||
palmRotation = holdingAvatar->getRightPalmRotation();
|
||||
pose = avatarManager->getMyAvatar()->getRightHandControllerPoseInWorldFrame();
|
||||
} else {
|
||||
palmRotation = holdingAvatar->getLeftPalmRotation();
|
||||
pose = avatarManager->getMyAvatar()->getLeftHandControllerPoseInWorldFrame();
|
||||
}
|
||||
} else {
|
||||
|
||||
if (pose.isValid()) {
|
||||
linearVelocity = pose.getVelocity();
|
||||
angularVelocity = pose.getAngularVelocity();
|
||||
}
|
||||
|
||||
if (_ignoreIK && pose.isValid()) {
|
||||
// We cannot ignore other avatars IK and this is not the point of this option
|
||||
// This is meant to make the grabbing behavior more reactive.
|
||||
palmPosition = pose.getTranslation();
|
||||
palmRotation = pose.getRotation();
|
||||
} else {
|
||||
glm::vec3 avatarRigidBodyPosition;
|
||||
glm::quat avatarRigidBodyRotation;
|
||||
getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
|
||||
|
||||
// the offset and rotation between the avatar's rigid body and the palm were determined earlier
|
||||
// in prepareForPhysicsSimulation. At this point, the avatar's rigid body has been moved by bullet
|
||||
// and the data in the Avatar class is stale. This means that the result of get*PalmPosition will
|
||||
// be stale. Instead, determine the current palm position with the current avatar's rigid body
|
||||
// location and the saved offsets.
|
||||
|
||||
// this line is more correct but breaks for the current way avatar data is updated.
|
||||
// palmPosition = avatarRigidBodyPosition + avatarRigidBodyRotation * _palmOffsetFromRigidBody;
|
||||
// instead, use this for now:
|
||||
palmPosition = avatarRigidBodyPosition + _palmOffsetFromRigidBody;
|
||||
|
||||
// the item jitters the least by getting the rotation based on the opinion of Avatar.h rather
|
||||
// than that of the rigid body. leaving this next line here for future reference:
|
||||
// palmRotation = avatarRigidBodyRotation * _palmRotationFromRigidBody;
|
||||
|
||||
if (isRightHand) {
|
||||
palmRotation = holdingAvatar->getRightPalmRotation();
|
||||
} else {
|
||||
palmRotation = holdingAvatar->getLeftPalmRotation();
|
||||
}
|
||||
}
|
||||
} else { // regular avatar
|
||||
if (isRightHand) {
|
||||
palmPosition = holdingAvatar->getRightPalmPosition();
|
||||
palmRotation = holdingAvatar->getRightPalmRotation();
|
||||
|
@ -299,7 +308,6 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
hand = _hand;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
holderID = myAvatar->getSessionUUID();
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
//
|
||||
// Hand.cpp
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Hand.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
#include <RenderArgs.h>
|
||||
|
||||
#include "Avatar.h"
|
||||
#include "AvatarManager.h"
|
||||
#include "MyAvatar.h"
|
||||
#include "Util.h"
|
||||
#include "world.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Hand::Hand(Avatar* owningAvatar) :
|
||||
HandData((AvatarData*)owningAvatar),
|
||||
_owningAvatar(owningAvatar)
|
||||
{
|
||||
}
|
||||
|
||||
void Hand::simulate(float deltaTime, bool isMine) {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
|
||||
float avatarScale = 1.0f;
|
||||
if (_owningAvatar) {
|
||||
avatarScale = _owningAvatar->getUniformScale();
|
||||
}
|
||||
|
||||
const float alpha = 1.0f;
|
||||
const glm::vec3 redColor(1.0f, 0.0f, 0.0f); // Color the hand targets red to be different than skin
|
||||
const glm::vec3 greenColor(0.0f, 1.0f, 0.0f); // Color the hand targets red to be different than skin
|
||||
const glm::vec3 blueColor(0.0f, 0.0f, 1.0f); // Color the hand targets red to be different than skin
|
||||
const glm::vec3 grayColor(0.5f);
|
||||
const float SPHERE_RADIUS = 0.03f * avatarScale;
|
||||
|
||||
auto palms = getCopyOfPalms();
|
||||
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
if (isMine) {
|
||||
for (const auto& palm : palms) {
|
||||
if (!palm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
// draw a gray sphere at the target position of the "Hand" joint
|
||||
glm::vec3 position = palm.getPosition();
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(position);
|
||||
transform.setRotation(palm.getRotation());
|
||||
transform.postScale(SPHERE_RADIUS);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, grayColor);
|
||||
|
||||
// draw a green sphere at the old "finger tip"
|
||||
transform = Transform();
|
||||
position = palm.getTipPosition();
|
||||
transform.setTranslation(position);
|
||||
transform.setRotation(palm.getRotation());
|
||||
transform.postScale(SPHERE_RADIUS);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, greenColor);
|
||||
}
|
||||
}
|
||||
|
||||
const float AXIS_RADIUS = 0.1f * SPHERE_RADIUS;
|
||||
const float AXIS_LENGTH = 10.0f * SPHERE_RADIUS;
|
||||
|
||||
// Draw the coordinate frames of the hand targets
|
||||
for (const auto& palm : palms) {
|
||||
if (palm.isActive()) {
|
||||
glm::vec3 root = palm.getPosition();
|
||||
|
||||
const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
||||
glm::quat palmRotation = palm.getRotation();
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(glm::vec3());
|
||||
batch.setModelTransform(transform);
|
||||
glm::vec3 tip = root + palmRotation * glm::vec3(AXIS_LENGTH, 0.0f, 0.0f);
|
||||
Avatar::renderJointConnectingCone(batch, root, tip, AXIS_RADIUS, AXIS_RADIUS, glm::vec4(redColor.r, redColor.g, redColor.b, alpha));
|
||||
|
||||
tip = root + palmRotation * glm::vec3(0.0f, AXIS_LENGTH, 0.0f);
|
||||
Avatar::renderJointConnectingCone(batch, root, tip, AXIS_RADIUS, AXIS_RADIUS, glm::vec4(greenColor.r, greenColor.g, greenColor.b, alpha));
|
||||
|
||||
tip = root + palmRotation * glm::vec3(0.0f, 0.0f, AXIS_LENGTH);
|
||||
Avatar::renderJointConnectingCone(batch, root, tip, AXIS_RADIUS, AXIS_RADIUS, glm::vec4(blueColor.r, blueColor.g, blueColor.b, alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
//
|
||||
// Hand.h
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Hand_h
|
||||
#define hifi_Hand_h
|
||||
|
||||
#include <HandData.h>
|
||||
|
||||
class Avatar;
|
||||
class RenderArgs;
|
||||
|
||||
class Hand : public HandData {
|
||||
public:
|
||||
Hand(Avatar* owningAvatar);
|
||||
|
||||
void simulate(float deltaTime, bool isMine);
|
||||
void renderHandTargets(RenderArgs* renderArgs, bool isMine);
|
||||
|
||||
private:
|
||||
// disallow copies of the Hand, copy of owning Avatar is disallowed too
|
||||
Hand(const Hand&);
|
||||
Hand& operator= (const Hand&);
|
||||
|
||||
int _controllerButtons; /// Button states read from hand-held controllers
|
||||
|
||||
Avatar* _owningAvatar;
|
||||
};
|
||||
|
||||
#endif // hifi_Hand_h
|
|
@ -28,7 +28,7 @@ class Avatar;
|
|||
|
||||
class Head : public HeadData {
|
||||
public:
|
||||
Head(Avatar* owningAvatar);
|
||||
explicit Head(Avatar* owningAvatar);
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
|
|
|
@ -341,12 +341,6 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
updatePosition(deltaTime);
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("hand");
|
||||
// update avatar skeleton and simulate hand and head
|
||||
getHand()->simulate(deltaTime, true);
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("skeleton");
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
|
@ -522,49 +516,50 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
|||
-MAX_LEAN, MAX_LEAN));
|
||||
}
|
||||
|
||||
|
||||
glm::vec3 MyAvatar::getLeftHandPosition() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
|
||||
return palmData.isValid() ? palmData.getPosition() : glm::vec3(0.0f);
|
||||
auto pose = getLeftHandControllerPoseInAvatarFrame();
|
||||
return pose.isValid() ? pose.getTranslation() : glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getRightHandPosition() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
|
||||
return palmData.isValid() ? palmData.getPosition() : glm::vec3(0.0f);
|
||||
auto pose = getRightHandControllerPoseInAvatarFrame();
|
||||
return pose.isValid() ? pose.getTranslation() : glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getLeftHandTipPosition() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
|
||||
return palmData.isValid() ? palmData.getTipPosition() : glm::vec3(0.0f);
|
||||
const float TIP_LENGTH = 0.3f;
|
||||
auto pose = getLeftHandControllerPoseInAvatarFrame();
|
||||
return pose.isValid() ? pose.getTranslation() * pose.getRotation() + glm::vec3(0.0f, TIP_LENGTH, 0.0f) : glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getRightHandTipPosition() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
|
||||
return palmData.isValid() ? palmData.getTipPosition() : glm::vec3(0.0f);
|
||||
const float TIP_LENGTH = 0.3f;
|
||||
auto pose = getRightHandControllerPoseInAvatarFrame();
|
||||
return pose.isValid() ? pose.getTranslation() * pose.getRotation() + glm::vec3(0.0f, TIP_LENGTH, 0.0f) : glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getLeftHandPose() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
|
||||
return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(),
|
||||
palmData.getVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
|
||||
return getLeftHandControllerPoseInAvatarFrame();
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getRightHandPose() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
|
||||
return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(),
|
||||
palmData.getVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
|
||||
return getRightHandControllerPoseInAvatarFrame();
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getLeftHandTipPose() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
|
||||
return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(),
|
||||
palmData.getTipVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
|
||||
auto pose = getLeftHandControllerPoseInAvatarFrame();
|
||||
glm::vec3 tipTrans = getLeftHandTipPosition();
|
||||
pose.velocity += glm::cross(pose.getAngularVelocity(), pose.getTranslation() - tipTrans);
|
||||
pose.translation = tipTrans;
|
||||
return pose;
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getRightHandTipPose() const {
|
||||
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
|
||||
return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(),
|
||||
palmData.getTipVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
|
||||
auto pose = getRightHandControllerPoseInAvatarFrame();
|
||||
glm::vec3 tipTrans = getRightHandTipPosition();
|
||||
pose.velocity += glm::cross(pose.getAngularVelocity(), pose.getTranslation() - tipTrans);
|
||||
pose.translation = tipTrans;
|
||||
return pose;
|
||||
}
|
||||
|
||||
// virtual
|
||||
|
@ -702,6 +697,15 @@ void MyAvatar::setEnableDebugDrawPosition(bool isEnabled) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawHandControllers(bool isEnabled) {
|
||||
_enableDebugDrawHandControllers = isEnabled;
|
||||
|
||||
if (!isEnabled) {
|
||||
DebugDraw::getInstance().removeMarker("leftHandController");
|
||||
DebugDraw::getInstance().removeMarker("rightHandController");
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawSensorToWorldMatrix(bool isEnabled) {
|
||||
_enableDebugDrawSensorToWorldMatrix = isEnabled;
|
||||
|
||||
|
@ -1092,6 +1096,48 @@ void MyAvatar::rebuildCollisionShape() {
|
|||
_characterController.setLocalBoundingBox(corner, diagonal);
|
||||
}
|
||||
|
||||
static controller::Pose applyLowVelocityFilter(const controller::Pose& oldPose, const controller::Pose& newPose) {
|
||||
controller::Pose finalPose = newPose;
|
||||
if (newPose.isValid()) {
|
||||
// Use a velocity sensitive filter to damp small motions and preserve large ones with
|
||||
// no latency.
|
||||
float velocityFilter = glm::clamp(1.0f - glm::length(oldPose.getVelocity()), 0.0f, 1.0f);
|
||||
finalPose.translation = oldPose.getTranslation() * velocityFilter + newPose.getTranslation() * (1.0f - velocityFilter);
|
||||
finalPose.rotation = safeMix(oldPose.getRotation(), newPose.getRotation(), 1.0f - velocityFilter);
|
||||
}
|
||||
return finalPose;
|
||||
}
|
||||
|
||||
void MyAvatar::setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right) {
|
||||
if (controller::InputDevice::getLowVelocityFilter()) {
|
||||
auto oldLeftPose = getLeftHandControllerPoseInWorldFrame();
|
||||
auto oldRightPose = getRightHandControllerPoseInWorldFrame();
|
||||
_leftHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldLeftPose, left));
|
||||
_rightHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldRightPose, right));
|
||||
} else {
|
||||
_leftHandControllerPoseInWorldFrameCache.set(left);
|
||||
_rightHandControllerPoseInWorldFrameCache.set(right);
|
||||
}
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getLeftHandControllerPoseInWorldFrame() const {
|
||||
return _leftHandControllerPoseInWorldFrameCache.get();
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getRightHandControllerPoseInWorldFrame() const {
|
||||
return _rightHandControllerPoseInWorldFrameCache.get();
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getLeftHandControllerPoseInAvatarFrame() const {
|
||||
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
|
||||
return getLeftHandControllerPoseInWorldFrame().transform(invAvatarMatrix);
|
||||
}
|
||||
|
||||
controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const {
|
||||
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
|
||||
return getRightHandControllerPoseInWorldFrame().transform(invAvatarMatrix);
|
||||
}
|
||||
|
||||
void MyAvatar::prepareForPhysicsSimulation() {
|
||||
relayDriveKeysToCharacterController();
|
||||
|
||||
|
@ -1227,11 +1273,6 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
|
|||
} else {
|
||||
getHead()->renderLookAts(renderArgs);
|
||||
}
|
||||
|
||||
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) {
|
||||
getHand()->renderHandTargets(renderArgs, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visible) {
|
||||
|
@ -1340,6 +1381,23 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
if (_enableDebugDrawHandControllers) {
|
||||
auto leftHandPose = getLeftHandControllerPoseInWorldFrame();
|
||||
auto rightHandPose = getRightHandControllerPoseInWorldFrame();
|
||||
|
||||
if (leftHandPose.isValid()) {
|
||||
DebugDraw::getInstance().addMarker("leftHandController", leftHandPose.getRotation(), leftHandPose.getTranslation(), glm::vec4(1));
|
||||
} else {
|
||||
DebugDraw::getInstance().removeMarker("leftHandController");
|
||||
}
|
||||
|
||||
if (rightHandPose.isValid()) {
|
||||
DebugDraw::getInstance().addMarker("rightHandController", rightHandPose.getRotation(), rightHandPose.getTranslation(), glm::vec4(1));
|
||||
} else {
|
||||
DebugDraw::getInstance().removeMarker("rightHandController");
|
||||
}
|
||||
}
|
||||
|
||||
DebugDraw::getInstance().updateMyAvatarPos(getPosition());
|
||||
DebugDraw::getInstance().updateMyAvatarRot(getOrientation());
|
||||
|
||||
|
@ -1464,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 ||
|
||||
|
@ -1729,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));
|
||||
|
|
|
@ -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;
|
||||
|
@ -249,6 +249,12 @@ public:
|
|||
|
||||
virtual void rebuildCollisionShape() override;
|
||||
|
||||
void setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right);
|
||||
controller::Pose getLeftHandControllerPoseInWorldFrame() const;
|
||||
controller::Pose getRightHandControllerPoseInWorldFrame() const;
|
||||
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
|
||||
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
|
||||
|
||||
public slots:
|
||||
void increaseSize();
|
||||
void decreaseSize();
|
||||
|
@ -271,6 +277,7 @@ public slots:
|
|||
void setEnableDebugDrawDefaultPose(bool isEnabled);
|
||||
void setEnableDebugDrawAnimPose(bool isEnabled);
|
||||
void setEnableDebugDrawPosition(bool isEnabled);
|
||||
void setEnableDebugDrawHandControllers(bool isEnabled);
|
||||
void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled);
|
||||
bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); }
|
||||
void setEnableMeshVisible(bool isEnabled);
|
||||
|
@ -435,6 +442,7 @@ private:
|
|||
|
||||
bool _enableDebugDrawDefaultPose { false };
|
||||
bool _enableDebugDrawAnimPose { false };
|
||||
bool _enableDebugDrawHandControllers { false };
|
||||
bool _enableDebugDrawSensorToWorldMatrix { false };
|
||||
|
||||
AudioListenerMode _audioListenerMode;
|
||||
|
@ -446,6 +454,10 @@ private:
|
|||
bool _hoverReferenceCameraFacingIsCaptured { false };
|
||||
glm::vec3 _hoverReferenceCameraFacing { 0.0f, 0.0f, -1.0f }; // hmd sensor space
|
||||
|
||||
// These are stored in WORLD frame
|
||||
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInWorldFrameCache { controller::Pose() };
|
||||
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInWorldFrameCache { controller::Pose() };
|
||||
|
||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -21,7 +21,7 @@ class MyAvatar;
|
|||
|
||||
class MyCharacterController : public CharacterController {
|
||||
public:
|
||||
MyCharacterController(MyAvatar* avatar);
|
||||
explicit MyCharacterController(MyAvatar* avatar);
|
||||
~MyCharacterController ();
|
||||
|
||||
virtual void updateShapeIfNecessary() override;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "Avatar.h"
|
||||
#include "Hand.h"
|
||||
#include "Menu.h"
|
||||
#include "SkeletonModel.h"
|
||||
#include "Util.h"
|
||||
|
@ -127,20 +126,20 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
Rig::HandParameters handParams;
|
||||
|
||||
auto leftPalm = myAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand);
|
||||
if (leftPalm.isValid() && leftPalm.isActive()) {
|
||||
auto leftPose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
|
||||
if (leftPose.isValid()) {
|
||||
handParams.isLeftEnabled = true;
|
||||
handParams.leftPosition = Quaternions::Y_180 * leftPalm.getRawPosition();
|
||||
handParams.leftOrientation = Quaternions::Y_180 * leftPalm.getRawRotation();
|
||||
handParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation();
|
||||
handParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation();
|
||||
} else {
|
||||
handParams.isLeftEnabled = false;
|
||||
}
|
||||
|
||||
auto rightPalm = myAvatar->getHand()->getCopyOfPalmData(HandData::RightHand);
|
||||
if (rightPalm.isValid() && rightPalm.isActive()) {
|
||||
auto rightPose = myAvatar->getRightHandControllerPoseInAvatarFrame();
|
||||
if (rightPose.isValid()) {
|
||||
handParams.isRightEnabled = true;
|
||||
handParams.rightPosition = Quaternions::Y_180 * rightPalm.getRawPosition();
|
||||
handParams.rightOrientation = Quaternions::Y_180 * rightPalm.getRawRotation();
|
||||
handParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation();
|
||||
handParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation();
|
||||
} else {
|
||||
handParams.isRightEnabled = false;
|
||||
}
|
||||
|
@ -247,17 +246,6 @@ bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) {
|
|||
return firstIndex.value < secondIndex.value;
|
||||
}
|
||||
|
||||
void SkeletonModel::applyPalmData(int jointIndex, const PalmData& palm) {
|
||||
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
|
||||
return;
|
||||
}
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
int parentJointIndex = geometry.joints.at(jointIndex).parentIndex;
|
||||
if (parentJointIndex == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool SkeletonModel::getLeftGrabPosition(glm::vec3& position) const {
|
||||
int knuckleIndex = _rig->indexOfJoint("LeftHandMiddle1");
|
||||
int handIndex = _rig->indexOfJoint("LeftHand");
|
||||
|
|
|
@ -111,7 +111,6 @@ protected:
|
|||
|
||||
void computeBoundingShape();
|
||||
|
||||
void applyPalmData(int jointIndex, const PalmData& palm);
|
||||
private:
|
||||
|
||||
bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <avatar/MyAvatar.h>
|
||||
#include <HandData.h>
|
||||
#include <HFBackEvent.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
|
||||
|
|
|
@ -284,7 +284,8 @@ void ApplicationOverlay::buildFramebufferObject() {
|
|||
|
||||
// If the overlay framebuffer still has no color attachment, no textures were available for rendering, so build a new one
|
||||
if (!_overlayFramebuffer->getRenderBuffer(0)) {
|
||||
auto colorBuffer = gpu::TexturePointer(gpu::Texture::create2D(COLOR_FORMAT, width, height, DEFAULT_SAMPLER));
|
||||
const gpu::Sampler OVERLAY_SAMPLER(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP);
|
||||
auto colorBuffer = gpu::TexturePointer(gpu::Texture::create2D(COLOR_FORMAT, width, height, OVERLAY_SAMPLER));
|
||||
_overlayFramebuffer->setRenderBuffer(0, colorBuffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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} {}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -83,6 +83,7 @@ public:
|
|||
Hover
|
||||
};
|
||||
|
||||
Rig() {}
|
||||
virtual ~Rig() {}
|
||||
|
||||
void destroyAnimGraph();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -57,7 +57,6 @@ AvatarData::AvatarData() :
|
|||
_hasNewJointRotations(true),
|
||||
_hasNewJointTranslations(true),
|
||||
_headData(NULL),
|
||||
_handData(NULL),
|
||||
_faceModelURL("http://invalid.com"),
|
||||
_displayNameTargetAlpha(1.0f),
|
||||
_displayNameAlpha(1.0f),
|
||||
|
@ -74,7 +73,6 @@ AvatarData::AvatarData() :
|
|||
|
||||
AvatarData::~AvatarData() {
|
||||
delete _headData;
|
||||
delete _handData;
|
||||
}
|
||||
|
||||
// We cannot have a file-level variable (const or otherwise) in the header if it uses PathUtils, because that references Application, which will not yet initialized.
|
||||
|
@ -418,11 +416,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
_headData = new HeadData(this);
|
||||
}
|
||||
|
||||
// lazily allocate memory for HandData in case we're not an Avatar instance
|
||||
if (!_handData) {
|
||||
_handData = new HandData(this);
|
||||
}
|
||||
|
||||
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(buffer.data());
|
||||
const unsigned char* sourceBuffer = startPosition;
|
||||
quint64 now = usecTimestampNow();
|
||||
|
@ -1122,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);
|
||||
|
@ -1141,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);
|
||||
|
|
|
@ -52,9 +52,9 @@ typedef unsigned long long quint64;
|
|||
#include <RegisteredMetaTypes.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <SpatiallyNestable.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "AABox.h"
|
||||
#include "HandData.h"
|
||||
#include "HeadData.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
|
@ -290,7 +290,6 @@ public:
|
|||
KeyState keyState() const { return _keyState; }
|
||||
|
||||
const HeadData* getHeadData() const { return _headData; }
|
||||
const HandData* getHandData() const { return _handData; }
|
||||
|
||||
bool hasIdentityChangedAfterParsing(const QByteArray& data);
|
||||
QByteArray identityByteArray();
|
||||
|
@ -383,7 +382,6 @@ protected:
|
|||
bool _hasNewJointTranslations; // set in AvatarData, cleared in Avatar
|
||||
|
||||
HeadData* _headData;
|
||||
HandData* _handData;
|
||||
|
||||
QUrl _faceModelURL; // These need to be empty so that on first time setting them they will not short circuit
|
||||
QUrl _skeletonModelURL; // These need to be empty so that on first time setting them they will not short circuit
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
//
|
||||
// HandData.cpp
|
||||
// libraries/avatars/src
|
||||
//
|
||||
// Created by Stephen Birarda on 5/20/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "AvatarData.h"
|
||||
#include "HandData.h"
|
||||
|
||||
|
||||
HandData::HandData(AvatarData* owningAvatar) :
|
||||
_owningAvatarData(owningAvatar)
|
||||
{
|
||||
addNewPalm(LeftHand);
|
||||
addNewPalm(RightHand);
|
||||
}
|
||||
|
||||
glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const {
|
||||
return glm::inverse(getBaseOrientation()) * worldVector / getBaseScale();
|
||||
}
|
||||
|
||||
PalmData& HandData::addNewPalm(Hand whichHand) {
|
||||
QWriteLocker locker(&_palmsLock);
|
||||
_palms.push_back(PalmData(this, whichHand));
|
||||
return _palms.back();
|
||||
}
|
||||
|
||||
PalmData HandData::getCopyOfPalmData(Hand hand) const {
|
||||
QReadLocker locker(&_palmsLock);
|
||||
|
||||
// the palms are not necessarily added in left-right order,
|
||||
// so we have to search for the correct hand
|
||||
for (const auto& palm : _palms) {
|
||||
if (palm.whichHand() == hand && palm.isActive()) {
|
||||
return palm;
|
||||
}
|
||||
}
|
||||
return PalmData(); // invalid hand
|
||||
}
|
||||
|
||||
PalmData::PalmData(HandData* owningHandData, HandData::Hand hand) :
|
||||
_rawRotation(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
_rawPosition(0.0f),
|
||||
_rawVelocity(0.0f),
|
||||
_rawAngularVelocity(0.0f),
|
||||
_totalPenetration(0.0f),
|
||||
_isActive(false),
|
||||
_numFramesWithoutData(0),
|
||||
_owningHandData(owningHandData),
|
||||
_hand(hand) {
|
||||
}
|
||||
|
||||
void PalmData::addToPosition(const glm::vec3& delta) {
|
||||
_rawPosition += _owningHandData->worldToLocalVector(delta);
|
||||
}
|
||||
|
||||
bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
|
||||
const PalmData*& collidingPalm) const {
|
||||
QReadLocker locker(&_palmsLock);
|
||||
|
||||
for (const auto& palm : _palms) {
|
||||
if (!palm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
glm::vec3 palmPosition = palm.getPosition();
|
||||
const float PALM_RADIUS = 0.05f; // in world (not voxel) coordinates
|
||||
if (findSphereSpherePenetration(penetratorCenter, penetratorRadius, palmPosition, PALM_RADIUS, penetration)) {
|
||||
collidingPalm = &palm;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::quat HandData::getBaseOrientation() const {
|
||||
return _owningAvatarData->getOrientation();
|
||||
}
|
||||
|
||||
glm::vec3 HandData::getBasePosition() const {
|
||||
return _owningAvatarData->getPosition();
|
||||
}
|
||||
|
||||
float HandData::getBaseScale() const {
|
||||
return _owningAvatarData->getTargetScale();
|
||||
}
|
||||
|
||||
glm::vec3 PalmData::getFingerDirection() const {
|
||||
// finger points along yAxis in hand-frame
|
||||
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f);
|
||||
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION));
|
||||
}
|
||||
|
||||
glm::vec3 PalmData::getNormal() const {
|
||||
// palm normal points along zAxis in hand-frame
|
||||
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, 0.0f, 1.0f);
|
||||
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
//
|
||||
// HandData.h
|
||||
// libraries/avatars/src
|
||||
//
|
||||
// Created by Eric Johnston on 6/26/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_HandData_h
|
||||
#define hifi_HandData_h
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QReadWriteLock>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
class AvatarData;
|
||||
class PalmData;
|
||||
|
||||
class HandData {
|
||||
public:
|
||||
enum Hand {
|
||||
LeftHand,
|
||||
RightHand,
|
||||
UnknownHand,
|
||||
NUMBER_OF_HANDS
|
||||
};
|
||||
|
||||
HandData(AvatarData* owningAvatar);
|
||||
virtual ~HandData() {}
|
||||
|
||||
// position conversion
|
||||
glm::vec3 localToWorldPosition(const glm::vec3& localPosition) {
|
||||
return getBasePosition() + getBaseOrientation() * localPosition * getBaseScale();
|
||||
}
|
||||
|
||||
glm::vec3 localToWorldDirection(const glm::vec3& localVector) {
|
||||
return getBaseOrientation() * localVector * getBaseScale();
|
||||
}
|
||||
|
||||
glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const;
|
||||
|
||||
PalmData getCopyOfPalmData(Hand hand) const;
|
||||
|
||||
std::vector<PalmData> getCopyOfPalms() const { QReadLocker locker(&_palmsLock); return _palms; }
|
||||
|
||||
/// Checks for penetration between the described sphere and the hand.
|
||||
/// \param penetratorCenter the center of the penetration test sphere
|
||||
/// \param penetratorRadius the radius of the penetration test sphere
|
||||
/// \param penetration[out] the vector in which to store the penetration
|
||||
/// \param collidingPalm[out] a const PalmData* to the palm that was collided with
|
||||
/// \return whether or not the sphere penetrated
|
||||
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
|
||||
const PalmData*& collidingPalm) const;
|
||||
|
||||
glm::quat getBaseOrientation() const;
|
||||
|
||||
/// Allows a lamda function write access to the specific palm for this Hand, this might
|
||||
/// modify the _palms vector
|
||||
template<typename PalmModifierFunction> void modifyPalm(Hand whichHand, PalmModifierFunction callback);
|
||||
|
||||
friend class AvatarData;
|
||||
protected:
|
||||
AvatarData* _owningAvatarData;
|
||||
std::vector<PalmData> _palms;
|
||||
mutable QReadWriteLock _palmsLock{ QReadWriteLock::Recursive };
|
||||
|
||||
glm::vec3 getBasePosition() const;
|
||||
float getBaseScale() const;
|
||||
|
||||
PalmData& addNewPalm(Hand whichHand);
|
||||
PalmData& getPalmData(Hand hand);
|
||||
|
||||
private:
|
||||
// privatize copy ctor and assignment operator so copies of this object cannot be made
|
||||
HandData(const HandData&);
|
||||
HandData& operator= (const HandData&);
|
||||
};
|
||||
|
||||
|
||||
class PalmData {
|
||||
public:
|
||||
PalmData(HandData* owningHandData = nullptr, HandData::Hand hand = HandData::UnknownHand);
|
||||
glm::vec3 getPosition() const { return _owningHandData->localToWorldPosition(_rawPosition); }
|
||||
glm::vec3 getVelocity() const { return _owningHandData->localToWorldDirection(_rawVelocity); }
|
||||
glm::vec3 getAngularVelocity() const { return _owningHandData->localToWorldDirection(_rawAngularVelocity); }
|
||||
|
||||
const glm::vec3& getRawPosition() const { return _rawPosition; }
|
||||
bool isActive() const { return _isActive; }
|
||||
bool isValid() const { return _owningHandData; }
|
||||
|
||||
void setActive(bool active) { _isActive = active; }
|
||||
|
||||
HandData::Hand whichHand() const { return _hand; }
|
||||
void setHand(HandData::Hand hand) { _hand = hand; }
|
||||
|
||||
void setRawRotation(const glm::quat& rawRotation) { _rawRotation = rawRotation; };
|
||||
glm::quat getRawRotation() const { return _rawRotation; }
|
||||
glm::quat getRotation() const { return _owningHandData->getBaseOrientation() * _rawRotation; }
|
||||
void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; }
|
||||
void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; }
|
||||
const glm::vec3& getRawVelocity() const { return _rawVelocity; }
|
||||
|
||||
void setRawAngularVelocity(const glm::vec3& angularVelocity) { _rawAngularVelocity = angularVelocity; }
|
||||
const glm::vec3& getRawAngularVelocity() const { return _rawAngularVelocity; }
|
||||
|
||||
void addToPosition(const glm::vec3& delta);
|
||||
|
||||
void addToPenetration(const glm::vec3& penetration) { _totalPenetration += penetration; }
|
||||
void resolvePenetrations() { addToPosition(-_totalPenetration); _totalPenetration = glm::vec3(0.0f); }
|
||||
|
||||
void setTipPosition(const glm::vec3& position) { _tipPosition = position; }
|
||||
const glm::vec3 getTipPosition() const { return _owningHandData->localToWorldPosition(_tipPosition); }
|
||||
const glm::vec3& getTipRawPosition() const { return _tipPosition; }
|
||||
|
||||
void setTipVelocity(const glm::vec3& velocity) { _tipVelocity = velocity; }
|
||||
const glm::vec3 getTipVelocity() const { return _owningHandData->localToWorldDirection(_tipVelocity); }
|
||||
const glm::vec3& getTipRawVelocity() const { return _tipVelocity; }
|
||||
|
||||
void incrementFramesWithoutData() { _numFramesWithoutData++; }
|
||||
void resetFramesWithoutData() { _numFramesWithoutData = 0; }
|
||||
int getFramesWithoutData() const { return _numFramesWithoutData; }
|
||||
|
||||
// FIXME - these are used in SkeletonModel::updateRig() the skeleton/rig should probably get this information
|
||||
// from an action and/or the UserInputMapper instead of piping it through here.
|
||||
void setTrigger(float trigger) { _trigger = trigger; }
|
||||
float getTrigger() const { return _trigger; }
|
||||
|
||||
// return world-frame:
|
||||
glm::vec3 getFingerDirection() const;
|
||||
glm::vec3 getNormal() const;
|
||||
|
||||
private:
|
||||
// unless marked otherwise, these are all in the model-frame
|
||||
glm::quat _rawRotation;
|
||||
glm::vec3 _rawPosition;
|
||||
glm::vec3 _rawVelocity;
|
||||
glm::vec3 _rawAngularVelocity;
|
||||
glm::quat _rawDeltaRotation;
|
||||
glm::quat _lastRotation;
|
||||
|
||||
glm::vec3 _tipPosition;
|
||||
glm::vec3 _tipVelocity;
|
||||
glm::vec3 _totalPenetration; /// accumulator for per-frame penetrations
|
||||
|
||||
float _trigger;
|
||||
|
||||
bool _isActive; /// This has current valid data
|
||||
int _numFramesWithoutData; /// after too many frames without data, this tracked object assumed lost.
|
||||
HandData* _owningHandData;
|
||||
HandData::Hand _hand;
|
||||
};
|
||||
|
||||
template<typename PalmModifierFunction> void HandData::modifyPalm(Hand whichHand, PalmModifierFunction callback) {
|
||||
QReadLocker locker(&_palmsLock);
|
||||
for (auto& palm : _palms) {
|
||||
if (palm.whichHand() == whichHand && palm.isValid()) {
|
||||
callback(palm);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // hifi_HandData_h
|
|
@ -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)
|
||||
{
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class QJsonObject;
|
|||
|
||||
class HeadData {
|
||||
public:
|
||||
HeadData(AvatarData* owningAvatar);
|
||||
explicit HeadData(AvatarData* owningAvatar);
|
||||
virtual ~HeadData() { };
|
||||
|
||||
// degrees
|
||||
|
|
|
@ -141,7 +141,7 @@ void EntityTreeRenderer::update() {
|
|||
// check if the texture loaded and apply it
|
||||
if (!updated && (
|
||||
(_pendingSkyboxTexture && (!_skyboxTexture || _skyboxTexture->isLoaded())) ||
|
||||
(_pendingAmbientTexture && (!_ambientTexture && _ambientTexture->isLoaded())))) {
|
||||
(_pendingAmbientTexture && (!_ambientTexture || _ambientTexture->isLoaded())))) {
|
||||
applyZonePropertiesToScene(_bestZone);
|
||||
}
|
||||
|
||||
|
|
|
@ -512,15 +512,17 @@ bool RenderableModelEntityItem::needsToCallUpdate() const {
|
|||
|
||||
void RenderableModelEntityItem::update(const quint64& now) {
|
||||
if (!_dimensionsInitialized && _model && _model->isActive()) {
|
||||
EntityItemProperties properties;
|
||||
auto extents = _model->getMeshExtents();
|
||||
properties.setDimensions(extents.maximum - extents.minimum);
|
||||
|
||||
qCDebug(entitiesrenderer) << "Autoresizing:" << (!getName().isEmpty() ? getName() : getModelURL());
|
||||
QMetaObject::invokeMethod(DependencyManager::get<EntityScriptingInterface>().data(), "editEntity",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QUuid, getEntityItemID()),
|
||||
Q_ARG(EntityItemProperties, properties));
|
||||
const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry();
|
||||
if (renderNetworkGeometry && renderNetworkGeometry->isLoaded()) {
|
||||
EntityItemProperties properties;
|
||||
auto extents = _model->getMeshExtents();
|
||||
properties.setDimensions(extents.maximum - extents.minimum);
|
||||
qCDebug(entitiesrenderer) << "Autoresizing:" << (!getName().isEmpty() ? getName() : getModelURL());
|
||||
QMetaObject::invokeMethod(DependencyManager::get<EntityScriptingInterface>().data(), "editEntity",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QUuid, getEntityItemID()),
|
||||
Q_ARG(EntityItemProperties, properties));
|
||||
}
|
||||
}
|
||||
|
||||
ModelEntityItem::update(now);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,9 @@ public:
|
|||
EntityItem(const EntityItemID& entityItemID);
|
||||
virtual ~EntityItem();
|
||||
|
||||
inline EntityItemPointer getThisPointer() { return std::static_pointer_cast<EntityItem>(shared_from_this()); }
|
||||
inline EntityItemPointer getThisPointer() const {
|
||||
return std::static_pointer_cast<EntityItem>(std::const_pointer_cast<SpatiallyNestable>(shared_from_this()));
|
||||
}
|
||||
|
||||
EntityItemID getEntityItemID() const { return EntityItemID(_id); }
|
||||
|
||||
|
|
|
@ -148,7 +148,11 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
if (entity) {
|
||||
if (propertiesWithSimID.parentRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
propertiesWithSimID.setQueryAACube(entity->getQueryAACube());
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
propertiesWithSimID.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
if (_bidOnSimulationOwnership) {
|
||||
|
|
|
@ -86,6 +86,11 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
|
|||
if (_simulation) {
|
||||
_simulation->addEntity(entity);
|
||||
}
|
||||
|
||||
if (!entity->isParentIDValid()) {
|
||||
_missingParent.append(entity);
|
||||
}
|
||||
|
||||
_isDirty = true;
|
||||
maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL());
|
||||
emit addingEntity(entity->getEntityItemID());
|
||||
|
@ -252,6 +257,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
|||
_missingParent.append(childEntity);
|
||||
continue;
|
||||
}
|
||||
if (!childEntity->isParentIDValid()) {
|
||||
_missingParent.append(childEntity);
|
||||
}
|
||||
|
||||
UpdateEntityOperator theChildOperator(getThisPointer(), containingElement, childEntity, queryCube);
|
||||
recurseTreeWithOperator(&theChildOperator);
|
||||
|
@ -448,6 +456,17 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
|
|||
const RemovedEntities& entities = theOperator.getEntities();
|
||||
foreach(const EntityToDeleteDetails& details, entities) {
|
||||
EntityItemPointer theEntity = details.entity;
|
||||
|
||||
if (getIsServer()) {
|
||||
QSet<EntityItemID> childrenIDs;
|
||||
theEntity->forEachChild([&](SpatiallyNestablePointer child) {
|
||||
if (child->getNestableType() == NestableType::Entity) {
|
||||
childrenIDs += child->getID();
|
||||
}
|
||||
});
|
||||
deleteEntities(childrenIDs, true, true);
|
||||
}
|
||||
|
||||
theEntity->die();
|
||||
|
||||
if (getIsServer()) {
|
||||
|
@ -992,11 +1011,32 @@ void EntityTree::fixupMissingParents() {
|
|||
EntityItemWeakPointer entityWP = iter.next();
|
||||
EntityItemPointer entity = entityWP.lock();
|
||||
if (entity) {
|
||||
bool success;
|
||||
AACube newCube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
// this entity's parent (or ancestry) was previously not fully known, and now is. Update its
|
||||
// location in the EntityTree.
|
||||
bool queryAACubeSuccess;
|
||||
AACube newCube = entity->getQueryAACube(queryAACubeSuccess);
|
||||
if (queryAACubeSuccess) {
|
||||
// make sure queryAACube encompasses maxAACube
|
||||
bool maxAACubeSuccess;
|
||||
AACube maxAACube = entity->getMaximumAACube(maxAACubeSuccess);
|
||||
if (maxAACubeSuccess && !newCube.contains(maxAACube)) {
|
||||
newCube = maxAACube;
|
||||
}
|
||||
}
|
||||
|
||||
bool doMove = false;
|
||||
if (entity->isParentIDValid()) {
|
||||
// this entity's parent was previously not known, and now is. Update its location in the EntityTree...
|
||||
doMove = true;
|
||||
} else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) {
|
||||
// this is a child of an avatar, which the entity server will never have
|
||||
// a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves.
|
||||
if (!_childrenOfAvatars.contains(entity->getParentID())) {
|
||||
_childrenOfAvatars[entity->getParentID()] = QSet<EntityItemID>();
|
||||
}
|
||||
_childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID();
|
||||
doMove = true;
|
||||
}
|
||||
|
||||
if (queryAACubeSuccess && doMove) {
|
||||
moveOperator.addEntityToMoveList(entity, newCube);
|
||||
iter.remove();
|
||||
entity->markAncestorMissing(false);
|
||||
|
@ -1011,7 +1051,13 @@ void EntityTree::fixupMissingParents() {
|
|||
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
||||
recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) {
|
||||
if (_childrenOfAvatars.contains(avatarID)) {
|
||||
deleteEntities(_childrenOfAvatars[avatarID]);
|
||||
_childrenOfAvatars.remove(avatarID);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::update() {
|
||||
|
|
|
@ -241,6 +241,10 @@ public:
|
|||
Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name) const;
|
||||
Q_INVOKABLE QStringList getJointNames(const QUuid& entityID) const;
|
||||
|
||||
void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; }
|
||||
void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; }
|
||||
void deleteDescendantsOfAvatar(QUuid avatarID);
|
||||
|
||||
public slots:
|
||||
void callLoader(EntityItemID entityID);
|
||||
|
||||
|
@ -313,8 +317,11 @@ protected:
|
|||
quint64 _maxEditDelta = 0;
|
||||
quint64 _treeResetTime = 0;
|
||||
|
||||
void fixupMissingParents();
|
||||
QVector<EntityItemWeakPointer> _missingParent;
|
||||
void fixupMissingParents(); // try to hook members of _missingParent to parent instances
|
||||
QVector<EntityItemWeakPointer> _missingParent; // entites with a parentID but no (yet) known parent instance
|
||||
// we maintain a list of avatarIDs to notice when an entity is a child of one.
|
||||
QSet<QUuid> _avatarIDs; // IDs of avatars connected to entity server
|
||||
QHash<QUuid, QSet<EntityItemID>> _childrenOfAvatars; // which entities are children of which avatars
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(¤tACKSubSequenceNumber);
|
||||
|
||||
|
||||
// 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
|
||||
|
|
|
@ -374,7 +374,8 @@ void Procedural::setupUniforms() {
|
|||
v.y = date.month() - 1;
|
||||
// But not the day... go figure
|
||||
v.z = date.day();
|
||||
v.w = (time.hour() * 3600) + (time.minute() * 60) + time.second();
|
||||
float fractSeconds = (time.msec() / 1000.0f);
|
||||
v.w = (time.hour() * 3600) + (time.minute() * 60) + time.second() + fractSeconds;
|
||||
batch._glUniform(_standardUniformSlots[DATE], v);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -392,13 +392,20 @@ void AnimDebugDraw::update() {
|
|||
|
||||
assert(numVerts == (v - verts));
|
||||
|
||||
// The RenderItem culling is not working correctly
|
||||
// Workaround this issue by using the default constructed
|
||||
// item._bound which is a 16 km cube.
|
||||
/*
|
||||
render::Item::Bound theBound;
|
||||
for (int i = 0; i < numVerts; i++) {
|
||||
theBound += verts[i].pos;
|
||||
}
|
||||
data._bound = theBound;
|
||||
*/
|
||||
|
||||
data._isVisible = (numVerts > 0);
|
||||
data._bound = theBound;
|
||||
|
||||
|
||||
data._indexBuffer->resize(sizeof(uint16_t) * numVerts);
|
||||
uint16_t* indices = (uint16_t*)data._indexBuffer->editData();
|
||||
for (int i = 0; i < numVerts; i++) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -115,9 +115,6 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
auto scriptContent = _scriptCache[url];
|
||||
lock.unlock();
|
||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||
#if 1 // def THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "ScriptCache::getScriptContents() about to call contentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||
#endif
|
||||
contentAvailable(url.toString(), scriptContent, true, true);
|
||||
} else {
|
||||
bool alreadyWaiting = _contentCallbacks.contains(url);
|
||||
|
|
|
@ -471,3 +471,11 @@ bool isNaN(glm::quat value) {
|
|||
return isNaN(value.w) || isNaN(value.x) || isNaN(value.y) || isNaN(value.z);
|
||||
}
|
||||
|
||||
glm::mat4 orthoInverse(const glm::mat4& m) {
|
||||
glm::mat4 r = m;
|
||||
r[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
r = glm::transpose(r);
|
||||
r[3] = -(r * m[3]);
|
||||
r[3][3] = 1.0f;
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -232,4 +232,6 @@ glm::vec2 getFacingDir2D(const glm::mat4& m);
|
|||
bool isNaN(glm::vec3 value);
|
||||
bool isNaN(glm::quat value);
|
||||
|
||||
glm::mat4 orthoInverse(const glm::mat4& m);
|
||||
|
||||
#endif // hifi_GLMHelpers_h
|
||||
|
|
|
@ -55,6 +55,7 @@ void SpatiallyNestable::setParentID(const QUuid& parentID) {
|
|||
_parentKnowsMe = false;
|
||||
}
|
||||
});
|
||||
checkAndAdjustQueryAACube();
|
||||
}
|
||||
|
||||
Transform SpatiallyNestable::getParentTransform(bool& success, int depth) const {
|
||||
|
@ -139,6 +140,7 @@ void SpatiallyNestable::forgetChild(SpatiallyNestablePointer newChild) const {
|
|||
|
||||
void SpatiallyNestable::setParentJointIndex(quint16 parentJointIndex) {
|
||||
_parentJointIndex = parentJointIndex;
|
||||
checkAndAdjustQueryAACube();
|
||||
}
|
||||
|
||||
glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position,
|
||||
|
@ -752,6 +754,7 @@ void SpatiallyNestable::forEachDescendant(std::function<void(SpatiallyNestablePo
|
|||
}
|
||||
|
||||
void SpatiallyNestable::locationChanged() {
|
||||
checkAndAdjustQueryAACube();
|
||||
forEachChild([&](SpatiallyNestablePointer object) {
|
||||
object->locationChanged();
|
||||
});
|
||||
|
@ -761,6 +764,14 @@ AACube SpatiallyNestable::getMaximumAACube(bool& success) const {
|
|||
return AACube(getPosition(success) - glm::vec3(defaultAACubeSize / 2.0f), defaultAACubeSize);
|
||||
}
|
||||
|
||||
void SpatiallyNestable::checkAndAdjustQueryAACube() {
|
||||
bool success;
|
||||
AACube maxAACube = getMaximumAACube(success);
|
||||
if (success && (!_queryAACubeSet || !_queryAACube.contains(maxAACube))) {
|
||||
setQueryAACube(maxAACube);
|
||||
}
|
||||
}
|
||||
|
||||
void SpatiallyNestable::setQueryAACube(const AACube& queryAACube) {
|
||||
if (queryAACube.containsNaN()) {
|
||||
qDebug() << "SpatiallyNestable::setQueryAACube -- cube contains NaN";
|
||||
|
@ -770,6 +781,7 @@ void SpatiallyNestable::setQueryAACube(const AACube& queryAACube) {
|
|||
if (queryAACube.getScale() > 0.0f) {
|
||||
_queryAACubeSet = true;
|
||||
}
|
||||
checkAndAdjustQueryAACube();
|
||||
}
|
||||
|
||||
bool SpatiallyNestable::queryAABoxNeedsUpdate() const {
|
||||
|
|
|
@ -81,6 +81,7 @@ public:
|
|||
virtual glm::vec3 getParentAngularVelocity(bool& success) const;
|
||||
|
||||
virtual AACube getMaximumAACube(bool& success) const;
|
||||
virtual void checkAndAdjustQueryAACube();
|
||||
virtual bool computePuffedQueryAACube();
|
||||
|
||||
virtual void setQueryAACube(const AACube& queryAACube);
|
||||
|
@ -156,7 +157,7 @@ protected:
|
|||
mutable QHash<QUuid, SpatiallyNestableWeakPointer> _children;
|
||||
|
||||
virtual void locationChanged(); // called when a this object's location has changed
|
||||
virtual void dimensionsChanged() {} // called when a this object's dimensions have changed
|
||||
virtual void dimensionsChanged() { checkAndAdjustQueryAACube(); } // called when a this object's dimensions have changed
|
||||
|
||||
// _queryAACube is used to decide where something lives in the octree
|
||||
mutable AACube _queryAACube;
|
||||
|
|
|
@ -104,6 +104,7 @@ void updateQmlItemFromAction(QObject* target, QAction* source) {
|
|||
target->setProperty("checkable", source->isCheckable());
|
||||
target->setProperty("enabled", source->isEnabled());
|
||||
target->setProperty("text", source->text());
|
||||
target->setProperty("shortcut", source->shortcut().toString());
|
||||
target->setProperty("checked", source->isChecked());
|
||||
target->setProperty("visible", source->isVisible());
|
||||
}
|
||||
|
@ -190,6 +191,20 @@ void VrMenu::addAction(QMenu* menu, QAction* action) {
|
|||
bindActionToQmlAction(result, action);
|
||||
}
|
||||
|
||||
void VrMenu::addSeparator(QMenu* menu) {
|
||||
Q_ASSERT(MenuUserData::forObject(menu));
|
||||
MenuUserData* userData = MenuUserData::forObject(menu);
|
||||
if (!userData) {
|
||||
return;
|
||||
}
|
||||
QObject* menuQml = findMenuObject(userData->uuid.toString());
|
||||
Q_ASSERT(menuQml);
|
||||
|
||||
bool invokeResult = QMetaObject::invokeMethod(menuQml, "addSeparator", Qt::DirectConnection);
|
||||
Q_ASSERT(invokeResult);
|
||||
Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x
|
||||
}
|
||||
|
||||
void VrMenu::insertAction(QAction* before, QAction* action) {
|
||||
QObject* beforeQml{ nullptr };
|
||||
{
|
||||
|
|
|
@ -28,6 +28,7 @@ public:
|
|||
VrMenu(QObject* parent = nullptr);
|
||||
void addMenu(QMenu* menu);
|
||||
void addAction(QMenu* parent, QAction* action);
|
||||
void addSeparator(QMenu* parent);
|
||||
void insertAction(QAction* before, QAction* action);
|
||||
void removeAction(QAction* action);
|
||||
|
||||
|
|
|
@ -222,8 +222,11 @@ void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
|
|||
return;
|
||||
}
|
||||
QAction* menu = _actionHash.value(menuOption);
|
||||
if (menu) {
|
||||
menu->setChecked(isChecked);
|
||||
if (menu && menu->isCheckable()) {
|
||||
auto wasChecked = menu->isChecked();
|
||||
if (wasChecked != isChecked) {
|
||||
menu->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,7 +514,11 @@ void MenuWrapper::setEnabled(bool enabled) {
|
|||
}
|
||||
|
||||
QAction* MenuWrapper::addSeparator() {
|
||||
return _realMenu->addSeparator();
|
||||
QAction* action = _realMenu->addSeparator();
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addSeparator(_realMenu);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
void MenuWrapper::addAction(QAction* action) {
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (NOT WIN32)
|
||||
# Windows doesn't need this, and building it currently make Linux unstable.
|
||||
# if (NOT WIN32)
|
||||
if (APPLE)
|
||||
|
||||
set(TARGET_NAME oculusLegacy)
|
||||
setup_hifi_plugin()
|
||||
|
@ -19,4 +21,4 @@ if (NOT WIN32)
|
|||
target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES})
|
||||
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -86,13 +86,16 @@ void OpenVrDisplayPlugin::activate() {
|
|||
}
|
||||
|
||||
void OpenVrDisplayPlugin::deactivate() {
|
||||
// Base class deactivate must come before our local deactivate
|
||||
// because the OpenGL base class handles the wait for the present
|
||||
// thread before continuing
|
||||
HmdDisplayPlugin::deactivate();
|
||||
_container->setIsOptionChecked(StandingHMDSensorMode, false);
|
||||
if (_system) {
|
||||
releaseOpenVrSystem();
|
||||
_system = nullptr;
|
||||
}
|
||||
_compositor = nullptr;
|
||||
HmdDisplayPlugin::deactivate();
|
||||
}
|
||||
|
||||
void OpenVrDisplayPlugin::customizeContext() {
|
||||
|
|
|
@ -477,118 +477,130 @@ function performContentMigration() {
|
|||
|
||||
var logWindow = null;
|
||||
|
||||
var labels = {
|
||||
serverState: {
|
||||
label: 'Server - Stopped',
|
||||
enabled: false
|
||||
},
|
||||
version: {
|
||||
label: 'Version - ' + buildInfo.buildIdentifier,
|
||||
enabled: false
|
||||
},
|
||||
restart: {
|
||||
label: 'Start Server',
|
||||
click: function() {
|
||||
homeServer.restart();
|
||||
}
|
||||
},
|
||||
stopServer: {
|
||||
label: 'Stop Server',
|
||||
visible: false,
|
||||
click: function() {
|
||||
homeServer.stop();
|
||||
}
|
||||
},
|
||||
goHome: {
|
||||
label: 'Go Home',
|
||||
click: goHomeClicked,
|
||||
enabled: false
|
||||
},
|
||||
quit: {
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: function() {
|
||||
shutdown();
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
label: 'Settings',
|
||||
click: function() {
|
||||
shell.openExternal('http://localhost:40100/settings');
|
||||
},
|
||||
enabled: false
|
||||
},
|
||||
viewLogs: {
|
||||
label: 'View Logs',
|
||||
click: function() {
|
||||
logWindow.open();
|
||||
}
|
||||
},
|
||||
share: {
|
||||
label: 'Share',
|
||||
click: function() {
|
||||
shell.openExternal('http://localhost:40100/settings/?action=share')
|
||||
}
|
||||
},
|
||||
migrateContent: {
|
||||
label: 'Migrate Stack Manager Content',
|
||||
click: function() {
|
||||
promptToMigrateContent();
|
||||
}
|
||||
},
|
||||
shuttingDown: {
|
||||
label: "Shutting down...",
|
||||
enabled: false
|
||||
},
|
||||
}
|
||||
|
||||
var separator = {
|
||||
type: 'separator'
|
||||
};
|
||||
|
||||
|
||||
function buildMenuArray(serverState) {
|
||||
var menuArray = null;
|
||||
|
||||
updateLabels(serverState);
|
||||
|
||||
var menuArray = [];
|
||||
|
||||
if (isShuttingDown) {
|
||||
menuArray = [
|
||||
{
|
||||
label: "Shutting down...",
|
||||
enabled: false
|
||||
}
|
||||
];
|
||||
menuArray.push(labels.shuttingDown);
|
||||
} else {
|
||||
menuArray = [
|
||||
{
|
||||
label: 'Server - Stopped',
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Go Home',
|
||||
click: goHomeClicked,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Start Server',
|
||||
click: function() { homeServer.restart(); }
|
||||
},
|
||||
{
|
||||
label: 'Stop Server',
|
||||
visible: false,
|
||||
click: function() { homeServer.stop(); }
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
click: function() { shell.openExternal('http://localhost:40100/settings'); },
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
label: 'View Logs',
|
||||
click: function() { logWindow.open(); }
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Share',
|
||||
click: function() { shell.openExternal('http://localhost:40100/settings/?action=share') }
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: function() { shutdown(); }
|
||||
}
|
||||
];
|
||||
menuArray.push(labels.serverState);
|
||||
menuArray.push(labels.version);
|
||||
menuArray.push(separator);
|
||||
menuArray.push(labels.goHome);
|
||||
menuArray.push(separator);
|
||||
menuArray.push(labels.restart);
|
||||
menuArray.push(labels.stopServer);
|
||||
menuArray.push(labels.settings);
|
||||
menuArray.push(labels.viewLogs);
|
||||
menuArray.push(separator);
|
||||
menuArray.push(labels.share);
|
||||
menuArray.push(separator);
|
||||
menuArray.push(labels.quit);
|
||||
|
||||
var foundStackManagerContent = isStackManagerContentPresent();
|
||||
if (foundStackManagerContent) {
|
||||
// add a separator and the stack manager content migration option
|
||||
menuArray.splice(menuArray.length - 1, 0, {
|
||||
label: 'Migrate Stack Manager Content',
|
||||
click: function() { promptToMigrateContent(); }
|
||||
}, {
|
||||
type: 'separator'
|
||||
});
|
||||
menuArray.splice(menuArray.length - 1, 0, labels.migrateContent, separator);
|
||||
}
|
||||
|
||||
updateMenuArray(menuArray, serverState);
|
||||
}
|
||||
|
||||
|
||||
return menuArray;
|
||||
|
||||
}
|
||||
|
||||
const GO_HOME_INDEX = 2;
|
||||
const SERVER_LABEL_INDEX = 0;
|
||||
const RESTART_INDEX = 4;
|
||||
const STOP_INDEX = 5;
|
||||
const SETTINGS_INDEX = 6;
|
||||
function updateLabels(serverState) {
|
||||
|
||||
function updateMenuArray(menuArray, serverState) {
|
||||
// update the tray menu state
|
||||
var running = serverState == ProcessGroupStates.STARTED;
|
||||
|
||||
var serverLabelItem = menuArray[SERVER_LABEL_INDEX];
|
||||
var restartItem = menuArray[RESTART_INDEX];
|
||||
|
||||
// Go Home is only enabled if running
|
||||
menuArray[GO_HOME_INDEX].enabled = running;
|
||||
|
||||
// Stop is only visible if running
|
||||
menuArray[STOP_INDEX].visible = running;
|
||||
|
||||
// Settings is only visible if running
|
||||
menuArray[SETTINGS_INDEX].enabled = running;
|
||||
|
||||
labels.goHome.enabled = running;
|
||||
labels.stopServer.visible = running;
|
||||
labels.settings.enabled = running;
|
||||
if (serverState == ProcessGroupStates.STARTED) {
|
||||
serverLabelItem.label = "Server - Started";
|
||||
restartItem.label = "Restart Server";
|
||||
labels.serverState.label = "Server - Started";
|
||||
labels.restart.label = "Restart Server";
|
||||
} else if (serverState == ProcessGroupStates.STOPPED) {
|
||||
serverLabelItem.label = "Server - Stopped";
|
||||
restartItem.label = "Start Server";
|
||||
labels.serverState.label = "Server - Stopped";
|
||||
labels.restart.label = "Start Server";
|
||||
labels.restart.enabled = true;
|
||||
} else if (serverState == ProcessGroupStates.STOPPING) {
|
||||
serverLabelItem.label = "Server - Stopping";
|
||||
|
||||
restartItem.label = "Restart Server";
|
||||
restartItem.enabled = false;
|
||||
labels.serverState.label = "Server - Stopping";
|
||||
labels.restart.label = "Restart Server";
|
||||
labels.restart.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue